by Tomi Tuomainen

Use high-level MVC and POJOs with Swing

news
Jun 20, 200514 mins

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.

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.

A TikeSwing component can basically be any java.awt.Component. However, a component must implement suitable TikeSwing interfaces so that it can be integrated into the framework’s MVC architecture. This should be quite a trivial task since it usually involves extending a standard Swing component with four simple methods. See the code below for an example. Integration with the model is implemented in the getModelValue() and setModelValue() methods. Notification of component value changes is implemented in the addViewListener() method. The method getYProperty() must be implemented for the framework’s internal use.

The following code represents a simple text field that supports Integer objects:

 

public class YIntegerField extends JTextField implements YIModelComponent {

/** Gets value of this field for the model. */ public Object getModelValue() { try { return new Integer(getText()); } catch (Exception ex) { return null; } }

/** Sets the model value into this field. */ public void setModelValue(Object obj) { if (obj == null) { setText(""); } else { setText(obj.toString()); } }

/** Notifies the framework when the component value might have changed. */ public void addViewListener(final YController controller) { this.addFocusListener(new FocusAdapter() { public void focusLost(FocusEvent ev) { controller.updateModelAndController(YIntegerField.this); } }); }

// The rest is for the framework internal use, // the implementation must be copied to each new component: private YProperty myProperty = new YProperty();

public YProperty getYProperty() { return myProperty; } }

Other features

In addition to the MVC architecture, TikeSwing offers a wide set of other features that help Swing development. These features are not revolutionary and can be found in many already implemented Swing applications. But since it is unnecessary to reinvent the wheel in every application, some best practices of Swing development are included in the framework.

TikeSwing supports the creation of hierarchical structures for controllers, as described in the patterns HMVC and MVC++. The framework offers methods that create parent-child relationships between controllers, which leads to a consistent and clear class structure. These relationships also help communication within a client application and can be used together with well-known design patterns. TikeSwing supports the Chain of Responsibility pattern, where a request is passed until one of the objects (controllers) handles the event. TikeSwing also supports the Observer/Observable pattern: a controller class may send an event that can be handled by all the registered controllers.

TikeSwing also includes a mechanism for lazy data retrieval for tabbed panes. In a distributed system, getting data from a server for all the tabs at once might take a long time. To optimize the performance, it might prove necessary to get data for each tab just once, right after the tab is selected. The framework provides a mechanism that simplifies the implementation of this functionality, so the code complexity, especially in nested tabbed panes, is reduced.

Some applications check unsaved changes when the user triggers an event that could lose data in just edited fields. This event could be, for example, closing a window, changing a tabbed pane tab, or selecting a table row. TikeSwing provides tools that enable the checking in specific events. TikeSwing also automatically pops up an “Unsaved changes?” dialog box and delegates saving to a controller method. In addition, the framework remembers a view’s state in a specific moment and can return the view to that state later. This means that the framework cancels changes without retrieving data from its original source (a server application).

Swing actions might prove useful when two or more components perform the same function. An Action object provides centralized event handling, but if the action is used in separate classes, the code gets easily more complex with increased coupling. TikeSwing includes a factory that centralizes the creation of actions, so an action can be used in separate view classes without direct coupling.

Swing components should be created, modified, and queried only from the event-dispatching thread, which complicates thread handling in a Swing application. The Swing Tutorial presents the SwingWorker class to provide some help for the issue. TikeSwing encapsulates SwingWorker and simplifies thread handling further. For example, some applications don’t freeze during remote calls or I/O operations. With TikeSwing, a manageable, paint-able dialog can pop up during such operation with just a few lines of code.

Summary

TikeSwing simplifies Swing development with a high-level MVC architecture and POJO support. Using TikeSwing is reasonable, especially in a distributed environment, because POJOs returned by a server application can be used directly in a model class, which is automatically connected to a view class. The framework also includes several best practices that solve complex development issues. Therefore, TikeSwing reduces the code that must be written for a Swing client and speeds up development.

Swing itself offers a rich platform-independent user interface library. Swing development has been a part of all the important IDEs for years, so WYSIWYG layout editing, unit testing, and debugging are widely supported. Previous issues with workstation performance should not be a problem nowadays, and Java Web Start eases the distribution of Java applications. Compared to Web application frameworks, Swing provides tools to build more user-friendly interfaces without JavaScript support problems and reduces network traffic by executing client logic in a workstation.

Still, criticism against Swing complexity is justified. But with high-level MVC frameworks like TikeSwing, that complexity can be reduced, and Swing can be turned into a highly productive client technology. I hope that the Java community will adopt and develop a widely tried and tested open source MVC framework for Swing, which would make it a serious candidate among RIA technologies. Maybe projects like spring-richclient will come closer to this goal. While waiting, check out TikeSwing and evaluate how it would fit in your RIA project.

Tomi Tuomainen is a consultant and an architect for Entra e-Solutions and has worked with J2EE applications and Java client frameworks since 1999. He has a master’s of science in computer science and he is a Sun Certified Enterprise Architect. His interests include (in addition to Java technologies) music, guitars, and gym training. You could say he is one of the strongest IT consultants in Finland.