Java GUI Development: Reintroducing MVC
Model-View-Controller (MVC) paradigm is an intuitive and widely accepted strategy in UI design, be it web or rich client. In fact it is so well established as a de-facto standard, that "compliance with MVC" is often used as a way to measure quality of UI frameworks, and even as a marketing slogan.
Model-View-Controller (MVC) paradigm is an intuitive and widely accepted strategy in UI design, be it web or rich client. In fact it is so well established as a de-facto standard, that "compliance with MVC" is often used as a way to measure quality of UI frameworks, and even as a marketing slogan.
What is so attractive about MVC is that it is a natural way of doing things. It simply makes sense to split UI code into object graph that represents the data ("model"), UI toolkit-specific widgets that render the output ("view"), and a set of actions performed in response to view or model changes ("controller"). These are the three main orthogonal concerns that allow to partition code into three logical groups for easier understanding and maintenance. This idea is intuitive to anyone who has ever written any application that includes interaction with users.
Swing (and AWT) pioneered the use of MVC in Java. Surprisingly Swing stays one of the most convoluted and counterintuitive MVC frameworks with very little guidance on how to do things right. This article discusses problems with Swing application design and shows where and how to apply MVC principles to solve them.
JStaple Note: All examples in this article are taken from the JStaple project at http://objectstyle.org/jstaple/. JStaple deals with the issues discussed here and is still very much in research stage. The article can be seen as an informal JStaple white paper. |
Closer Look at Swing
Where is MVC?
As a reminder to the reader here is a few basic Swing facts. Internally Swing implements a common flavor of MVC called "model-delegate", coalescing view and controller into "UI delegate". Developers rarely deal with view/controller part when writing applications. Instead they work with something called "components". Swing components by themselves are not the part of MVC triad, rather they work as mediators between model, view and controller.
Most custom coding goes into creation of models and building components to do the layout, communicate with subcomponents and synchronize the view and the model. Resulting components usually contain a mix of code dealing with the view tasks (e.g. laying out subcomponents or painting), controller tasks (performing actions), and model tasks (wrapping domain objects into the custom models).
So while Swing components do a great job in separating look-and-feel from the application logic, they loose clean MVC split at the application level and end up being a spaghetti of code performing a number of unrelated tasks. Problem gets bigger in complex applications where each component may contain dozens of subelements, each requiring its own handling.
For those who are not convinced that this is an issue, it is sufficient to look at the code samples from any popular Swing book (supposedly showing the best practices in Swing development). A fairly complex custom component class usually takes somewhere between 4 and 8 pages of Java code. In real life components grow even bigger as developers have to handle many cases omitted in book examples.
Problem #1: Application components mix many unrelated things in a single component class, resulting in the code that is hard to understand and maintain. Following "recommended" Swing patterns does not produce a clean MVC application. |
Custom Models
Data and operations on such data usually come to UI application in a form of "domain" or "business" objects. A common application goal is to display this data and interact with it. The following examples show how such interaction is implemented in Swing.
If there is an array of Strings and you need to display them in a JComboBox:
String[] choices = ..;
// internally combo box wraps the array into DefaultComboBoxModel
// but as long as we don't have to deal with it, this is OK.
JComboBox combo = new JComboBox(choices);
Not bad, lets move on. Now what if there is a list of Departments and you need to display their names, allowing user to select one of the departments from the list? Sounds trivial, just a little bit harder than the first example? Think again - you can't do it directly in Swing. Instead you have to go through quiet a lot of trouble. One possible way is this:
public class DepartmentsComboModel extends AbstractListModel implements ComboBoxModel {
protected List departments;
protected Object label;
protected Map departmentsByLabel;
public Object getSelectedItem() {
return label;
}
public void setSelectedItem(Object item) {
this.label = item;
}
public Object getElementAt(int index) {
Department department = (Department) departments.get(index);
return department.getName();
}
public int getSize() {
return (departments != null) ? departments.size() : 0;
}
public Department getSelectedDepartment() {
if (label == null || departmentsByLabel == null) {
return null;
}
return (Department) departmentsByLabel.get(label);
}
public List getDepartments() {
return departments;
}
public void setDepartments(List departments) {
this.departments = departments;
// index departments by name
departmentsByLabel = new HashMap();
...
fireContentsChanged(this, 0, departments.size());
}
public void setSelectedDepartment(Department department) {
this.label = (department != null) ? department.getName() : null;
}
}
This whole class is required to do one simple thing - display a property of an object! If you have other combo boxes that display different types of objects or different properties of the same object type, you'll have to create other custom ComboBoxModels, with no end in sight. The solution above is how the JComboBox is "supposed" to work and is 100% pure MVC. And yet it seems like the most unnatural thing to do. If you move from JComboBox to JTable, the level of complexity of the custom model goes up. So there is a "domain model" that is actually the objects you care about, and there is a "model" as in "MVC". The two are not the same.
Problem #2: Swing components can't easily access data from an arbitrary domain model and require specific model interface, so developers end up writing countless adapters for the existing domain classes. |
Updating the Model: When Events Become an Overkill
Most elements of data entry forms are not doing anything fancy with this data. Usually all that is required is to update a property of some model object with an entered value, and maybe do some validation before that. Unless a model wrapper is involved as discussed above, property update procedure is not automatic. A component has to implement and install a listener for widget events, and such listener must conform to predefined interfaces. If we want to edit department name using a JTextField, user input has to be captured using code like that:
final Department department = ...;
final JTextField nameField = ...;
...
nameField.getDocument().addDocumentListener(
new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
department.setName(nameField.getText());
}
public void removeUpdate(DocumentEvent e) {
department.setName(nameField.getText());
}
public void changedUpdate(DocumentEvent e) {
department.setName(nameField.getText());
}
});
...and then this code has to be copied and pasted for each text field on screen. Note that we are not writing an MS Word replacement here, we simply want to update an object property with whatever user typed in a text field. Instead we end up with a bloat of inner classes that leaves uninitiated in a total confusion, breaks or badly mingles OO design and is simply ugly. Same thing goes for all other Swing widgets - JButtons, JTextAreas, etc.
Problem #3: Swing components have to register as event listeners for the most trivial things. As each listener must conform to a specific interface, code gets polluted with anonymous inner classes. |
Cleaning It Up: MVC on Top of Swing
The discussion above suggests that developer experience with Swing is far from ideal. As rewriting Swing is out of the question, it would be nice to build something on top to make it more usable. In particular the following areas need real improvement - components require further decomposition and a simplified way to interact with domain model. Yes, solving this means creating yet another framework.
Application framework note: It is worth mentioning that any GUI application beyond "Hello World" may need quiet a few extra things not provided by Swing (or Java) out of the box. The most important one is an application container with consistent set of services for the components. A notion of an "application object" is something that is missing from Swing entirely. Implementing a solution based on an IoC container such as Spring or Pico should address that. However this is a separate topic, so instead the article will concentrate on improving Swing component model. |
To address the problems above we suggest to decompose Swing components following the MVC principles (the closest approach to the one discussed here is taken by WebObjects and Tapestry web frameworks). In this design traditional Swing components takes the role of the "View" and are stripped of all model and controller code. View only includes laying out subcomponents and/or painting. Model can be any Java class, thus domain objects can be used directly. The most important part is controller. It consists of two parts: a class containing all custom logic for the component and a declarative bindings layer. The rest of the article shows how the parts of this design work together in JStaple prototype implementation.
View: java.awt.Component
As mentioned above, a View is usually a subclass of java.awt.Component
. It can be a standard Swing widget such as JButton (no further subclassing is required) or a custom component. It is a "View" from the application perspective, but in all other respects it is a normal Swing/AWT component. Here is a simple example, demonstrating that the view only deals with arranging subcomponents, and doesn't include any custom listeners, models or direct references to the application data:
public class MainFrameView extends JFrame {
protected JButton newDepartmentButton;
protected JComboBox departmentsCombo;
protected JLabel departmentsLabel;
public MainFrameView() {
// create widgets
departmentsLabel = new JLabel("Departments:");
newDepartmentButton = new JButton("New Department...");
departmentsCombo = new JComboBox();
getContentPane().setLayout(new BorderLayout());
// layout subcomponents, not shown here, as this is normal Swing code
// ...
setSize(600, 400);
}
public JButton getNewDepartmentButton() {
return newDepartmentButton;
}
public JComboBox getDepartmentsCombo() {
return departmentsCombo;
}
public JLabel getDepartmentsLabel() {
return departmentsLabel;
}
}
Controller: Implementing Application Logic in STComponent
STComponent part of controller is a place for coding custom logic of a graphic component. It serves as a facade for any number of domain objects that are declared as STComponent read/write bean properties. STComponent may also declare a number of action methods. Here is a simplified example of STComponent. Just like the view example above it contains no listeners or special models, just some domain objects (this example shows references to persistent objects and Cayenne DataContext used for database access), and action methods:
public class MainFrame extends BaseComponent {
// declare domain objects needed for this component as ivars
protected DataContext dataContext;
protected List departments;
protected Department selectedDepartment;
public MainFrame(Component view) {
super(view);
}
// simple and "calculated" properties...
public List getDepartments() {
return departments;
}
public Department getSelectedDepartment() {
return selectedDepartment;
}
public void setSelectedDepartment(Department selectedDepartment) {
this.selectedDepartment = selectedDepartment;
}
public String departmentLabel(Department department) {
return " - " + department.getName();
}
// actions
public void addDepartmentAction() {
// create new department and pass control to the DepartmentEditor dialog
Department department = (Department) dataContext
.createAndRegisterNewObject(Department.class);
DepartmentEditor editor = new DepartmentEditor(
new DepartmentEditorView(),
this,
department);
// binding a callback action to the dialog.
// bindings are discussed in detail below.
editor.bindOkAction("departmentAddedAction(#actionParameter)");
editor.startupAction();
}
// ....
}
Controller: Binding Things Together
Neither view nor STComponent shown above contain any code to communicate with each other, and of course domain model is completely independent from both. Communication task is performed by the bindings layer. It does all the wiring without endless custom decorators and adapters. Internally it consists of a set of generic adapters for Swing components that allow to "plug" certain model properties or controller actions to the known "sockets". Another term for such "plug/socket" pair is "binding". In the most simple case a binding establishes a data channel between "bound" property of the view and some property of the model, but without a need for special model or listener interfaces. At the same time View and Model stay unaware of each other as all communications are indirect. This preserves the fundamental MVC concerns separation.
Each component is wired with a "binding set" - a number of predefined named bindings, some of which are mandatory, some - optional. The actual implementation of a binding set is provided by JStaple and depends on the specific view (as internally a binding has to use whatever API Swing provides, including anonymous inner classes and specialized models), however the API used inside controller to work with the bindings is the same regardless of the underlying communication mechanism. Also we can do better than just connecting model property to a view property. As demonstrated below, there is a few types of bindings, e.g. binding STComponent actions to certain view events, etc. Bindings are made really dynamic by adding a scripting language to the mix. Current JStaple implementation uses OGNL for scripting.
Bindings layer is declarative and separate from STComponent. JStaple has a fully working bindings prototype that is configured inside STComponent. However it should be easy to extract all bindings in a separate XML. This way bindings can be changed and reloaded dynamically without recompiling, but more importantly they will not pollute the code.
Here is an example of binding to a JComboBox residing in a custom component. It demonstrates a number of things that can be done with bindings. Note that there is no need to subclass JComboBox, as bindings work with all standard Swing widgets (as well as custom components).
BindingBuilder builder = new BindingBuilder(controllerObject);
builder.switchToSubview("departmentsCombo");
builder.bindToPullValuesFromModel(STComboBoxBindings.LIST_BINDING, "departments");
builder.bindToPullValuesFromModel(STComboBoxBindings.LABEL_BINDING, "departmentLabel(#item)");
builder.bindForTwoWaySync(STComboBoxBindings.SELECTED_VALUE_BINDING, "selectedDepartment");
builder.bindAction(STComboBoxBindings.ACTION_BINDING, "showEmployeesAction()");
builder.bindToPullValuesFromModel("visible", "connected");
Now going through the individual parts...
BindingBuilder builder = new BindingBuilder(controllerObject);
builder.switchToSubview("departmentsCombo");
Above we create a BindingBuilder which is simply a helper class to hide binding internals. The second line tells the builder that we are about to start configuration of a "departmentCombo" property of the View. It is expected to be a JComboBox.
builder.bindToPullValuesFromModel(STComboBoxBindings.LIST_BINDING, "departments");
Here we set one of the "standard" JComboBox bindings. The names of standard bindings for different Swing components are defined in the corresponding interfaces in JStaple. For example this and other JComboBox bindings are defined in STComboBoxBindings interface. Second parameter to a method is the model property name, which happens to be "departments". Properties are resolved using STComponent as a "root" object. As the binding is created using "bindToPullValuesFromModel" method, it works one-way, automatically updating the view whenever the model changes.
builder.bindToPullValuesFromModel(STComboBoxBindings.LABEL_BINDING, "departmentLabel(#item)");
The line above shows how easy it is to build a display label for a given object in the list in a way defined by the controller for this specific view. A string displayed in a JComboBox doesn't have to be a result of "department.toString()", or even a property of the Department class (though it can be in some cases). We can do better than that. With a little piece of OGNL script used as a second argument we can delegate a decision about display string to the controller object:
The actual implementation of "departmentLabel" method by the controller may for instance prefix a given department name with a dash:
public String departmentLabel(Department department) {
return " - " + department.getName();
}
Next line...
builder.bindForTwoWaySync(STComboBoxBindings.SELECTED_VALUE_BINDING, "selectedDepartment");
This line shows how to synchronize the selected object between JComboBox and the model. The object that is passed around is a Department (as this is the type of objects in the bound list), not the String label displayed in the combo box. There is no need for developer to manually convert between Department and string label - JStaple takes care of such conversion.
The previous binding sets a selected Department property of the controller, but we can also bind an action method that should be invoked whenever selected department changes. Here is how this can be done:
builder.bindAction(STComboBoxBindings.ACTION_BINDING, "showEmployeesAction()");
STComboBoxBindings interface declares just a few bindings specific to JComboBox. At the same time a typical JComponent may have a dozen or more properties; some may need to be controlled by the application. This brings us to another cool feature - binding to an arbitrary property. The example below shows how to setup a binding that changes JComboBox visibility based on the "connected" property of the model:
builder.bindToPullValuesFromModel("visible", "connected");
JComboBox example above doesn't feel like Swing anymore. It is clean and does not contain anonymous inner classes or specialized models. So lets reiterate over what can be accomplished using bindings:
- Bindings are a part of the controller used for indirect communication (e.g. passing data or invoking an action) between (a) view and model and (b) view and STComponent.
- Bindings provide an infrastructure for the clean hierarchical MVC design using Swing, AWT and custom components.
- Each view element (i.e. each Swing or custom widget) may have a predefined set of bindings. User components can declare bindings too. For instance a dialog can have two bindings: "okAction" and "cancelAction". Depending on which dialog button was clicked, one of them will be activated when the dialog is closed, resulting in an invocation of a bound action method of the caller object. All this is possible without defining special listener interfaces or knowing anything about the caller.
- Bindings for a given component are independent from each other, so there is no need for a specialized model for each component type. Instead different pieces of the domain model can be bound via different bindings, resulting in the ultimate flexibility.
- If a binding is not recognized as a "standard one", the name of the binding is treated as a view property.
- From the view standpoint, bindings can be "pull", "push" or "two-way".
- STComponent actions can be bound just as easy as property values.
- Just like the event-based approach, bindings decouple view from model, though bindings go much further in this respect.
- Unlike event-based MVC bindings do not require special model interfaces or anonymous inner classes.
There are other things bindings can help with that are not yet fully explored in JStaple. For instance bindings can provide generic hooks for input validation. It should be possible to install a validator object that is called every time a binding pushes a value from the view to the model. Validation failures can be handled in a consistent fashion, e.g. by changing the color of the view element and setting a tooltip to display a validation message.
What About the Events?
Every component in Swing provides a way to register listeners interested in notifications for the various things that happen within component. This is a nice feature, as it gives a lot of flexibility. However application as a whole rarely needs to know when some checkbox is checked or text typed into a text field. Such events generated by widgets can be called "short range" - only a corresponding controller cares about them.
On the other hand there are application-level events usually associated with model changes. They are quiet different from regular Swing events and are normally application-specific. These events may still have their origin in a button click or some other view action, however this fact is likely not reflected in the event object and is not important to listeners. Those are the "long range" events, and they may be of interest to more than one component.
What is suggested here is to replace any explicit use of "short range" events at the component level with bindings for simplicity and flexibility. But a more complex application should still be designed around "long range" events that provide loose communication between the parts of the program. Of course "short range" events can still be used together with bindings if needed, as JStaple bindings are simply adapters to a standard widget set provided by Swing.
Hiding low-level widget events is only one of the things bindings can do. They can also help with "long range" event firing. A binding can be configured to trigger a "long range" event whenever a view pushes value to the model (this feature is not implemented in JStaple yet). There are two good things about this approach - a component can be coded without knowing which events it will generate (as this is defined by component users dynamically in runtime), and domain model doesn't have to be decorated to fire events on property change.
Similar Frameworks
- Scope (http://scope.sourceforge.net/). As of version 1.0.* known to the author, Scope contains many of the concepts discussed here under the common name of Hierarchical Model View Controller (HMVC). However Scope has a number of differences. Namely the bindings design is less dynamic, doesn't support advanced scripting and requires subclassing standard Swing widgets. Actions and properties are treated as separate things (called "controls" and "selectors"). It doesn't seem to support extracting bindings into a separate layer.
- Mac OS X Cocoa. Implemented in Objective C. Java wrapper exists, but can be used only on Mac OS X.
Conclusion
The article shows main shortcomings of Swing that make it so hard to use: (1) lack of separation between the code dealing with Swing internals and application logic and (2) no clean way to connect domain model with components. Suggested solution is to add a separate MVC layer on top of Swing, treating Swing components as a "view". Scriptable dynamic bindings are used inside this layer for declarative component assembly and communication between various related application parts.
JStaple code with all the examples shown in the article can be downloaded from http://objectstyle.org/jstaple/.
About the Author
Andrei (aka Andrus) Adamchik is one of the founders and the main developer of Cayenne Object Relational Mapping Framework. In his work as software programmer and architect he created enterprise applications for a number of companies ranging from logistics to finance, to media and entertainment. Currently Andrus is a CEO of ObjectStyle LLC, a New York and Atlanta based software consulting company. Andrus started JStaple project as a prototype to streamline painful Swing development of Cayenne modeling tools.