AbstractView
Introduction
AbstractView is the principal template ( Template , GoF) of JDAL Swing. Although it is possible to use the different functions provided by JDAL Swing in an independent form, AbstractView permits you condense the development work of forms in one unique job: Implement the method buildPanel().
AbstractView supports the automatic way of the following commune jobs in the development of Swing forms:
- Automatic Binding: between the user interface controls and the domain models. Following simple rules of nomenclature, could eliminate completely in code the data binding in the making of the swing forms.
- Validating the model: Actually the unique supported interface is org.springframework.validation.Validator. But this is enough to use JSR-303 annotations.
- Processing validation errors: The interface ErrorProcessor represents a contract for the processing of the validation errors. The default implementation, BackgroundErrorProcessor changes the colour of the components and adds a tooltip with informatión about the error.
- Nesting or grouping of other views: AbstractView supports the delegatión of the operations towards other views by SubViews (Vistas on the same model) or views of the properties of the model
- Modification status of the controls: The method isDirty() proporcions information about the state of the modification of the view controls.
- Activate/Deactivate all the view controls: Simply call the method enableView(boolean value) to enable or disable the control on which there is a binding.
- Initialization of the controls: AbstractView detects JPA annotations in the form backup model and could initialise the data controls in an automatic way.
The process of building a form based on Abstractview can be summarised in the following steps:
- Extend the class AbstracView parameterized with the model type on which the form back ups the data.
- Create the JComponent that contains the form in the buildPanel() method.
- Calling at somepoint the method autobind(), generally by the configuration of a method of initializatión in the IoC container (init-method, @PostConstruct, @Autowire...).
Data Binding
AbstractView supports at the moment two forms of realizing the binding of data between user interface controls and the domain models:
- Manual: For binding manual we refer to the need to call at any moment, the method bind(Object control, String propertyName) to associate a model property to a user interface control.
- Automatic: In automátic mode, AbstractView make the association between controls and the properties of the model via the method autobind()to associate the view controls with the properties of the model of the same name. That's to say, if the model contains a property name which fulfills the specification Java Beans and you want to associate the property to a JTextField in view, simply declare the JTextField with the same name in the view, and call the method autobind() sometime.
In both cases, in creating binding between a Swing component and a property of the model, it will undertake the following actions:
- The form will recieve notifications about changes in the control via the method controlChange(). The default implementation only changes the state of the form to dirty. You can override this method to personalise the behaviour. Remember call the method of the super class if you want to maintain this behaviour.
- Initalizatión of the controls. The Swing controls can be initialized by the interface ControlInitializer. The default implementaion detects JPA and JDAL annotations.
Finally, AbstractView provides three template methods to customize the data binding process:
- doRefresh(): called from refresh()
- doUpdate(): called from update().
- onSetModel(): called from setModel().
Structure
AbstractView delegates much of the work in strategies or support classes of JDAL. It is interesting to take a look as you might be interested in using them directly o personalising the behaviours.
CompositeBinder
Performs all work in data binding between the conttols and models. It can be used independently outside of AbstractView. It is not necessary to extend JDAL classes to dispose of data binding system. You can find more information in the data binding chapter..
ErrorProcessor
Processing strategy of binding errors. The implementation by default, BackgroundErrorProcessor changes the colour of the background of the controls that have failed and add a tooltip with the information of the error.
BinderFactory
PropertyBinder factory, CompositeBinder uses it to find the Binder appropriate for each type of control.
ControlAccessorFactory
Allow access to generic Swing controls. That is to say, perform control operations without the specific knowledge of the type of control.
ControlInitializer
Strategy of control initialization, usually with data from the respositories or other entities. The implementatión by default DefaultControlInitializer is able to detect annotationes JPA and the annotation @Reference of JDAL Core.
Example
Model
We will create a View for the following Project entity:
@Entity @Table(name="project") public class Project { @Id @GeneratedValue(strategy=GenerationType.AUTO) protected Long id; @NotEmpty protected String name = ""; @ManyToOne private Company customer; @NotNull private Double amount; @ManyToOne private Bid bid; private String description; @OneToMany(mappedBy="project") private Set<Work> works; @OneToMany(mappedBy="project", targetEntity=ProjectAttachment.class, cascade=CascadeType.ALL, orphanRemoval=true) private List<Attachment> attachments; @ManyToMany(cascade=CascadeType.ALL) private Set<User> users; // Accessors, Mutators and Behavior Omited.
View
The target objective is to create a form to edit projects with the following specification:/p>
- The bid combo should be linked to the customer combo.
- The customer combo should load all customers from database
- Should have a user editor to assign users that work on the project.
- Shoul allow to add attachments to the project
The following is the code of the projects form using AbstracView
public class ProjectView extends AbstractView<Project> { private JTextField name = new JTextField(); private JComboBox customer = new JComboBox(); private JComboBox bid = new JComboBox(); private JTextField amount = new JTextField(); private JTextArea description = new JTextArea(5, 0); /** ManyToMany editor */ private Selector<User> users = new Selector<User>(); @Autowired /** inner view to show edit attachments */ private AttachmentView attachments; /** service to initialize entities */ private PersistentService<Project, Long> service; public ProjectView() { this(new Project()); } /** * @param model */ public ProjectView(Project model) { super(model); } /** * Init method, called by container after property sets */ public void init() { service.initialize(getModel()); // initialize model, ie drop Hibernate Proxies getControlInitializer().setInitializeEntities(true); / users.init(); // initialize Selector autobind(); // the binding code FormUtils.link(customer, bid, "bids"); // link customer and bids } /** * {@inheritDoc} */ @Override protected JComponent buildPanel() { // create a Box form builder to build the form BoxFormBuilder fb = new BoxFormBuilder(FormUtils.createTitledBorder(getMessage("Project.title"))); fb.row(); fb.startBox(); // first Box (name, customer, bid and amount fb.setFixedHeight(true); // fixed height on resizes fb.add(getMessage("Name"), name); fb.row(); fb.add(getMessage("Customer"), customer); fb.row(); fb.add(getMessage("Bid"), bid); fb.row(); fb.add(getMessage("Amount"), amount); fb.endBox(); // end first box fb.row(); fb.startBox(); // second box for description fb.add(FormUtils.newLabelForBox(getMessage("Description"))); fb.row(Short.MAX_VALUE); // let this row to be as higher as it can. fb.add(new JScrollPane(description)); fb.endBox(); // end second box fb.row(); fb.startBox(); // third box for user selector fb.row(); fb.add(new SeparatorTitled(getMessage("Users"))); fb.row(Short.MAX_VALUE); // let this row to be as higher as it can. fb.add(users); fb.endBox(); // end third box fb.row(); fb.startBox(); // last box for attachment fb.row(); fb.add(new SeparatorTitled(getMessage("Attachments"))); fb.row(Short.MAX_VALUE); // let this row to be as higher as it can. fb.add(attachments.getPanel()); fb.endBox(); // end last box. return = fb.getForm(); // return the JCompoenent } /** * {@inheritDoc} */ @Override public void onSetModel(Project project) { if (service != null) service.initialize(project); } /** * @return the service */ public PersistentService<Project, Long> getService() { return service; } /** * @param service the service to set */ public void setService(PersistentService<Project, Long> service) { this.service = service; } }
Context Configuration
... <!-- Persistent Serivice for Projects --> <jdal:service entity="info.joseluismartin.gefa.model.Project"/> <!-- Generic context service --> <bean id="contextService" class="info.joseluismartin.logic.ContextPersistentManager" /> <!-- Configure factories and related swing dependences --> <swing:defaults /> <!-- JSR-303 Validation --> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" /> <bean id="errorProcessor" class="info.joseluismartin.gui.validation.BackgroundErrorProcessor" /> <!-- Actions --> <bean id="acceptAction" class="info.joseluismartin.gui.action.ViewSaveAction" scope="prototype"> <property name="icon" value="/images/16x16/dialog-ok.png"/> <property name="service" ref="objectService"/> <property name="name" value="Accept"/> </bean> <bean id="cancelAction" class="info.joseluismartin.gui.action.DialogCancelAction" scope="prototype"> <property name="icon" value="/images/16x16/dialog-cancel.png"/> <property name="name" value="Cancel"/> </bean> <!-- Base definition for Editors --> <bean id="editor" abstract="true" > <property name="acceptAction" ref="acceptAction" /> <property name="cancelAction" ref="cancelAction" /> <property name="dialogWidth" value="800" /> <property name="dialogHeight" value="600" /> </bean> <!-- Base definition for Views --> <bean id="view" abstract="true" > <property name="controlAccessorFactory" ref="controlAccessorFactory" /> <property name="binderFactory" ref="binderFactory" /> <property name="controlInitializer" ref="controlInitializer" /> <property name="validator" ref="validator" /> <property name="errorProcessors"> <list> <ref bean="errorProcessor" /> </list> </property> </bean> <!-- Default Control Initializer --> <bean id="controlInitializer" class="info.joseluismartin.gui.bind.AnnotationControlInitializer"> <property name="persistentService" ref="contextService" /> </bean> <!-- Our Project View --> <bean id="projectView" class="info.joseluismartin.gefa.ui.ProjectView" parent="view" scope="prototype" > <property name="service" ref="projectService" /> </bean> <!-- Our Project Editor --> <bean id="projectEditor" class="info.joseluismartin.gui.ViewDialog" parent="editor" scope="prototype"> <property name="view" ref="projectView" /> </bean> ...
Now, we can get instances of the project editor by the bean "projectEditor".