AbstractView


Introducción


AbstractView es la clase plantilla ( Template , GoF) principal de JDAL para la creación de formularios Swing. Aunque es posible utilizar las diferentes funcionalidades que proporciona JDAL Swing de forma independiente, AbstractView le permite condensar el trabajo de desarrollo de formularios en una única tarea: Implementar el método buildPanel()

AbstractView soporta de forma automática las siguientes tareas comunes en el desarrollo de formularios Swing.


El proceso de construcción de un formulario basado en AbstractView puede resumirse en los siguientes pasos:



Data Binding


AbstractView soporta actualmente dos formas de realizar el binding de datos entre los controles de la interfaz de usuario y los modelos del dominio:

En ambos casos, al crear un binding entre un componente Swing y una propiedad del modelo, se realizarán las siguientes acciones:

Finalmente, AbstractView proporciona tres métodos plantilla que permiten personalizar el proceso de enlazado de datos para un caso determinado:



Estructura Interna


AbstractView delega gran parte del trabajo en estrategias o clases de ayuda de JDAL. Es interesante echarles un vistazo ya que podría usarlas directamente o bien personalizar alguno de los comportamientos.



CompositeBinder

Realiza todo el trabajo de data binding entre los controles y modelos. Puede utilizarse de forma independiente fuera de la jerarquía de AbstractView. No es necesario extender clases propias de JDAL para disponer del sistema de enlazado de datos. Puede encontrar más información en el capítulo de enlazado de datos.

ErrorProcessor

Estrategia de procesamiento de errores de binding o validación. La implementación por defecto, BackgroundErrorProcessor Cambia de color el fondo de los controles que han fallado y añade un tooltip con la información del error.

BinderFactory

Fábrica de PropertyBinders CompositeBinder la utiliza para encontrar el Binder apropiado para cada tipo de control.

ControlAccessorFactory

Permite acceder a los controles Swing de forma genérica. Es decir, realizar operaciones sobre controles sin el conocimiento específico del tipo de control.

ControlInitializer

Estrategia de inicialización de los controles, normalmente con datos de los repositorios o de otras entidades. La implementación por defecto DefaultControlInitializer es capaz de detectar anotaciones JPA y la anotación @Reference de JDAL Core.



Ejemplo


Modelo

Crearemos una vista para la entidad Project que se muestra a continuación:

@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.

Vista

El objetivo es crear el siguiente formulario para la edición de proyectos, que nos permite establecer el nombre, el cliente, la oferta que se hizo al cliente, el presupuesto inicial, una descripción, la relación de usuarios asignados al proyecto y por último una lista de archivos binarios adjuntados al proyecto con la siguiente especificación:




A continuación sigue el código de la vista del formulario de proyectos utilizando 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;
	}
 
}
 

Configuración del Contexto

Como acabamos de ver, podemos escribir la vista del proyecto evitando el código de binding, prácticamente todo el código de inicialización de los componentes, el de validación y tampoco ha sido necesario añadir Listeners a los controles para recibir notificaciones de cambio en los valores. A cambio, deberemos dedicar algún tiempo a la configuración del contexto de Spring.


El nuevo namespace de jdal-swing simplifica considerablemente esta tarea respecto a versiones anteriores. Note que la mayoría de los beans mostrados a continuación sólo es necesario definirlos una vez por cada aplicación. Los beans específicos de este ejemplo son projectService, projectView y projectEditor

...
 
<!-- 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>
 
...

Ahora podemos obtener instancias del editor de proyectos mediante el bean "projectEditor".