Use high-level MVC and POJOs with Swing

An introduction to the TikeSwing framework

The rise of rich Internet applications (RIAs) has lately become a hot topic in the Java community. In addition to new technologies like AJAX (Asynchronous JavaScript and XML), MacroMedia Flex, and Laszlo, the good old Swing with Java Web Start has also been proposed as an RIA technology.

However, many in the Java community criticize Java Foundation Classes (JFC) and Swing. Swing doesn't offer much help in creating a high-level Model-View-Controller (MVC) client architecture. This proves frustrating in the J2EE world, where any reasonable server application returns transfer objects, or so-called plain old Java objects (POJOs), to a client. A lot of manual coding is required to map POJO fields to Swing components and vice versa.

Also, implementing other features like thread handling and field validation with Swing often proves quite laborious. And sometimes Swing components are just hard to use: creating a suitable table or a tree model usually requires a lot of code and investigation into the Swing Javadoc API.

TikeSwing is an open source Swing framework that provides a high-level MVC architecture and automates communication between models, views, and controllers. The framework simplifies the usage of Swing components and supports the POJO programming model by connecting view components directly to JavaBeans.

This article demonstrates how to build a clear MVC architecture with TikeSwing. It also presents principles for creating TikeSwing components and briefly describes best practices and mechanisms included in the framework.

MVC architecture

The well-known MVC paradigm is generally recommended as the fundamental architecture for GUI development. Many variations of the pattern are available, like MVC++, HMVC (Hierarchical MVC), MVC Model 2, MVC Push, and MVC Pull, with each emphasizing slightly different issues. TikeSwing is based on the following MVC principles:

  • Model:
    • An abstraction of some real-world process or system
    • Encapsulates data and functions that operate on it
    • Notifies observers when data changes
  • View:
    • The system's user interface
    • Attaches to the model and renders its contents to the display surface
    • Automatically redraws the affected part when the model changes
  • Controller:
    • Controls the application flow
    • Accepts input from the user and instructs the model and the view to perform actions based on that input

The following diagram represents the MVC class structure in TikeSwing.

Figure 1. MVC class diagram of an application using TikeSwing

The MyModel, MyView, and MyController classes are implemented by an application using the framework. MyModel and MyController extend the TikeSwing classes YModel and YController. A view class can be any java.awt.Component that implements the YIComponent interface.

TikeSwing does not use any configuration files for setting up the class structure. Extending the appropriate classes is enough since YController, YModel, and view components provide the required functionality. The following sections describe how to implement the model, view, and controller classes with TikeSwing.

Model

The TikeSwing model is a JavaBeans component that contains data for the view. A model class may include nested JavaBeans, arrays, maps, and collections. All model fields must have appropriate get and set methods as described in the JavaBeans standard. In this sense, TikeSwing works like many Web application frameworks, so it should be easy to reuse model classes between different technologies.

The YModel is the base class for models. It provides methods for reporting data changes. When a notification is triggered, the framework updates the changes to the connected views. In a distributed environment, a model class has methods that get POJOs from a server application (or usually from a business delegate that hides a business service's implementation details). The POJOs are stored in the model itself, which is responsible for notifying observers. In some MVC architectures, a controller class communicates with a server, and POJOs are stored in the controller. However, the TikeSwing approach, with a separate YModel class, has benefits: the controller concentrates purely on the flow, and additional methods (that operate on model data) can be added on the client side. YModel also follows the traditional MVC pattern, so the responsibilities of the MVC classes are cleanly divided.

The following code represents a model class that finds customers with given parameters. The model fields name and id are used for search criteria, and customers is a collection of Customer POJOs containing the search results. The method findCustomers() gets customers from a server application via customerServiceDelegate. The framework automatically updates the connected views, when the method notifyObservers() is called.

 

public class FindCustomerModel extends YModel { private String name; private String id; private Collection customers;

private CustomerServiceDelegate delegate = new CustomerServiceDelegate(); public void findCustomers() { setCustomers(delegate.findCustomers(id, name)); notifyObservers("customers"); }

public void setCustomers(Collection customers) { this.customers = customers; }

public Collection getCustomers() { return customers; } public void setId(String id) { this.id = id; }

public String getId() { return id; }

public void setName(String name) { this.name = name; } public String getName() { return name; } }

View

The TikeSwing view is a Swing component containing other Swing components. Usually a view class is a panel, a dialog, or a frame, which creates child components and adds them to itself (just like in traditional Swing development). However, all the components used in a TikeSwing application must implement appropriate interfaces to connect to the framework's MVC architecture. Fortunately, the framework includes a wide set of already implemented components for this purpose.

A special name must be set to a view component so that the framework can copy data between the component and the named model field. The naming convention is similar to those used in Web application frameworks and the Apache BeanUtils library (which is actually used in the framework implementation). The following naming formats are supported:

  • Simple: A component is connected to a model field directly; for example, field1
  • Nested: A component is connected to a JavaBeans field inside the model; for example, field1.field2
  • Indexed: A component is connected to an array field inside the model; for example, myArray[1]
  • Mapped: A component is connected to a map field inside the model; for example, myHashMap("foo")
  • Combined: A component is connected to a field inside the model via combined notations; for example, field.myArray[1].myHashMap["foo"]

In addition to get and set methods in a model class, a view class must also contain a get method for every view component.

The following example is a view class for FindCustomerModel. The view uses TikeSwing components that extend base Swing classes (JLabel to YLabel, JTextField to YTextField, etc.). The code looks like a standard Swing view, only the setMVCNames() method contains TikeSwing-specific code. It sets up the component-model connection according to the principles described above. The resultTable columns are connected to POJO fields in the customers collection via YColumn objects. findButton doesn't show any data from the model, but the MVC name is set for TikeSwing event handling (described later).

 

public class FindCustomerView extends YPanel { private YLabel idLabel = new YLabel("Id");

private YLabel nameLabel = new YLabel ("Name"); private YTextField idField = new YTextField(); private YTextField nameField = new YTextField(); private YPanel criteriaPanel = new YPanel(); private YTable resultTable = new YTable(); private YButton findButton = new YButton("Find"); public FindCustomerView () { addComponents(); setMVCNames(); } private void setMVCNames() { idField.getYProperty().put(YIComponent.MVC_NAME,"id"); nameField.getYProperty().put(YIComponent.MVC_NAME,"name"); resultTable.getYProperty().put(YIComponent.MVC_NAME,"customers"); findButton.getYProperty().put(YIComponent.MVC_NAME,"findButton"); YColumn[] columns = { new YColumn("id"), new YColumn("name")}; resultTable.setColumns(columns); } private void addComponents() { this.setLayout(new BorderLayout()); this.add(criteriaPanel, BorderLayout.NORTH); idField.setPreferredSize(new Dimension(100, 19)); nameField.setPreferredSize(new Dimension(100, 19)); criteriaPanel.add(idLabel); criteriaPanel.add(idField); criteriaPanel.add(nameLabel); criteriaPanel.add(nameField); criteriaPanel.add(findButton); this.add(resultTable, BorderLayout.CENTER); }

public YTextField getIdField() { return idField; } public YLabel getIdLabel() { return idLabel; } public YTextField getNameField() { return nameField; } public YLabel getNameLabel() { return nameLabel; } public YTable getResultTable() { return resultTable; } public YButton getFindButton() { return findButton; } }

Now every time the user edits the idField or the nameField, the changes are automatically updated to the model. Also, when notifyObservers() is called in the FindCustomerModel, the framework updates changes to the resultTable. Yet a controller must be specified to wire up the structure.

Controller

The TikeSwing controller handles application flow by calling methods of the view and the model. A controller class must extend YController, which provides necessary methods for controlling the triangle. Usually, controllers also create view and model objects, but note that several views and controllers may share the same model object.

A controller class may catch user events in several ways. The TikeSwing components include reflection-based event handling: an event can be handled in a controller class by implementing a method with the appropriate signature. For example, a button with the MVC name myButton calls the method myButtonPressed() in a controller class (if implemented) when the user presses the button. This is often quite convenient compared to the standard Swing event listener interfaces and adapters.

On the other hand, typos in an event method signature are not revealed by the compiler, but so is the case with the Swing adapter classes: the compiler doesn't tell whether public void actionperformed is a new method or the overridden method. And since the listener interfaces often require many empty method implementations, the simplicity of reflection-based event handling should speed up the coding process. Alternatively, you could use the standard listeners in a view class and call a controller method manually.

The following code is an example controller for FindCustomerModel and FindCustomerView. The controller wires up the MVC structure by calling the setUpMVC() method and handles a reflection-based event triggered by findButton.

 

public class FindCustomerController extends YController { private FindCustomerView view = new FindCustomerView(); private FindCustomerModel model = new FindCustomerModel();

public FindCustomerController() { super(); setUpMVC(model, view); } public void findButtonPressed() { model.findCustomers(); } }

The YController is the heart of functionality in TikeSwing. In addition to the features described above, it provides many useful methods that can be used for:

  • Catching changes in a particular field
  • Sending and receiving messages between controllers
  • Keeping track of user changes
  • Canceling user changes
  • Catching exceptions thrown by a model class
  • Checking valid field values

TikeSwing components

TikeSwing is based on the idea that the component is responsible for handling the connected object in the model. This idea is reminiscent of the WholeNumberField demo in Sun's Swing Tutorial. The component must know how to present a model value on the screen and how to convert the user-given value back to the model.

The framework currently offers a wide set of components that should be enough for most applications to get started. The behavior of the framework components resembles that of the base Swing components, but of course you should read the Javadoc to understand the component interaction with the MVC classes (what kind of model fields the component can handle and what event methods it provides). TikeSwing components also offer additional features that simplify development. For example, a collection of POJOs can be used in YTable and YTree directly without creating any special component models.

1 2 Page 1