MVC meets Swing

Explore the underpinnings of the JFC's Swing components

A good software user interface often finds its genesis in the user interfaces present in the physical world. Consider for a moment a simple button like one of the keys on the keyboard in front of you. With such a button there is a clean separation between the parts that compose the button's mechanism and the parts that compose its façade. The building block called a keyboard key is actually composed of two pieces. Once piece gives it its button-like behavior. The other piece is responsible for its appearance.

This construction turns out to be a powerful design feature. It encourages reuse rather than redesign. Because your keyboard's keys were designed that way, it's possible to reuse the button mechanism design, and replace the key tops to create a new key rather than designing each key from scratch. This creates a substantial savings in design effort and time.

Not surprisingly, similar benefits occur when this technique is applied to software development. One commonly used implementation of this technique in software is the design pattern called Model/View/Controller (MVC).

That's all well and good, but you're probably wondering how this relates to the Swing user interface components in the Java Foundation Classes (JFC). Well, I'll tell you.

While the MVC design pattern is typically used for constructing entire user interfaces, the designers of the JFC used it as the basis for each individual Swing user interface component. Each user interface component (whether a table, button, or scrollbar) has a model, a view, and a controller. Furthermore, the model, view, and controller pieces can change, even while the component is in use. The result is a user interface toolkit of almost unmatched flexibility.

Let me show you how it works.

The MVC design pattern

If you are not familiar with the MVC design pattern, I recommend you review "Observer and Observable", one of my earlier articles that discusses this topic in much greater detail and provides the groundwork for this month's column.

As I mentioned a moment ago, the MVC design pattern separates a software component into three distinct pieces: a model, a view, and a controller.

Figure 1. MVC design pattern

The model is the piece that represents the state and low-level behavior of the component. It manages the state and conducts all transformations on that state. The model has no specific knowledge of either its controllers or its views. The system itself maintains links between model and views and notifies the views when the model changes state.

The view is the piece that manages the visual display of the state represented by the model. A model can have more than one view, but that is typically not the case in the Swing set.

The controller is the piece that manages user interaction with the model. It provides the mechanism by which changes are made to the state of the model.

Using the keyboard key example, the model corresponds to the key's mechanism, and the view and controller correspond to the key's façade.

The following figure illustrates how to break a JFC user interface component into a model, view, and controller. Note that the view and controller are combined into one piece, a common adaptation of the basic MVC pattern. They form the user interface for the component.

Figure 2. JFC user interface component

A button in detail

To better understand how the MVC pattern relates to Swing user interface components, let's delve deeper in the Swing set. Just as I did last month, I'll use the ubiquitous button component as a reference.

We'll begin with the model.

The model

The behavior of the model in the button illustration above is captured by the ButtonModel interface. A button model instance encapsulates the internal state of a single button and defines how the button behaves. Its methods can be grouped into four categories -- those that:

  • Query internal state
  • Manipulate internal state
  • Add and remove event listeners
  • Fire events

Other user interface components have their own, component-specific models. They all, however, provide the same groups of methods.

The view and controller

The behavior of the view and controller in the button illustration above are captured by the ButtonUI interface. Classes that implement this interface are responsible for both creating a button's visual representation and handling user input provided via the keyboard and mouse. Its methods can be grouped into three categories -- those that:

  • Paint
  • Return geometric information
  • Handle AWT events

Other user interface components have their own, component-specific view/controllers. They all, however, provide the same groups of methods.

The scaffolding

Programmers do not typically work with model and view/controller classes directly. In fact, to the casual observer, their presence is veiled. They hide behind an ordinary component class -- a subclass of java.awt.Component. The component class acts as the glue, or scaffolding, that holds the MVC triad together. Many of the methods present on the component class (paint(), for example) are nothing more than wrappers that pass along the method invocation to either the model or the view/controller.

Because the component classes are subclasses of class Component, a programmer can freely mix Swing components with regular AWT components. However, because the Swing set contains components that functionally mimic the regular AWT components, mixing the two is usually not necessary.

A concrete example

Now that we understand which Java classes correspond to which parts of the MVC pattern, we're ready to open the box and peek inside. What follows is a scaled-down tour of a set of model classes designed and built according to the MVC principles outlined above. Because the JFC library is so complex, I've narrowed the scope of my tour to include only one user interface component (if you guessed it to be the

Button

class, you'd be right).

Let's take a look at all the major players.

The Button class

The most obvious place to begin is with the code for the button component itself, because this is the class that most programmers will work with.

As I mentioned earlier, the Button user interface component class acts as the scaffolding for the model and the view/controller. Each button component is associated with one model and one view/controller. The model defines the button's behavior and the view/controller defines its appearance. The application can change either at any time. Let's look at the code for making the change.

public void setModel(ButtonModel buttonmodel) { if (this.buttonmodel != null) { this.buttonmodel.removeChangeListener(buttonchangelistener); this.buttonmodel.removeActionListener(buttonactionlistener);

buttonchangelistener = null; buttonactionlistener = null; }

this.buttonmodel = buttonmodel;

if (this.buttonmodel != null) { buttonchangelistener = new ButtonChangeListener(); buttonactionlistener = new ButtonActionListener();

this.buttonmodel.addChangeListener(buttonchangelistener); this.buttonmodel.addActionListener(buttonactionlistener); }

updateButton(); }

public void setUI(ButtonUI buttonui) { if (this.buttonui != null) { this.buttonui.uninstallUI(this); }

this.buttonui = buttonui;

if (this.buttonui != null) { this.buttonui.installUI(this); }

updateButton(); }

public void updateButton() { invalidate(); }

Take a moment to peruse the rest of the Button class before you move on.

The ButtonModel class

The button model maintains three pieces of state information: pressed/not-pressed, armed/not-armed, and selected/not-selected. These are boolean quantities.

A button is pressed if the user has pressed the mouse button while the mouse cursor is over the button, but has not yet released the mouse button. The button is pressed even if the user drags the mouse cursor off the button.

A button is armed if the button is pressed and the mouse cursor is over the button.

Some buttons may also be selected. This value is typically toggled on and off by repeatedly pressing the button.

The code below shows the default implementation for the pressed state. The code for the armed and selected states are similar. The ButtonModel class should be subclassed in order to redefine the default behavior.

private boolean boolPressed = false;

public boolean isPressed() { return boolPressed; }

public void setPressed(boolean boolPressed) { this.boolPressed = boolPressed;

fireChangeEvent(new ChangeEvent(button)); }

The button model also notifies other objects (called event listeners) about interesting events that occur. From the code above we see that a change event is fired every time the button's state changes. Here's how it happens.

private Vector vectorChangeListeners = new Vector();

public void addChangeListener(ChangeListener changelistener) { vectorChangeListeners.addElement(changelistener); }

public void removeChangeListener(ChangeListener changelistener) { vectorChangeListeners.removeElement(changelistener); }

protected void fireChangeEvent(ChangeEvent changeevent) { Enumeration enumeration = vectorChangeListeners.elements();

while (enumeration.hasMoreElements()) { ChangeListener changelistener = (ChangeListener)enumeration.nextElement();

changelistener.stateChanged(changeevent); } }

Take a moment to review the remainder of the ButtonModel class before you move on.

The ButtonUI class

The button view/controller is responsible for presentation. By default it simply draws a rectangle of the same color as the background. Subclasses redefine these methods to provide alternate look-and-feel's -- such as Motif, Windows 95, and Java provided with the JFC.

public void update(Button button, Graphics graphics) { ; }

public void paint(Button button, Graphics graphics) { Dimension dimension = button.getSize();

Color color = button.getBackground();

graphics.setColor(color);

graphics.fillRect(0, 0, dimension.width, dimension.height); }

The ButtonUI class doesn't handle AWT events itself. Instead, it employs a custom event listener class to translate low-level AWT user interface events into the high-level semantic events the button model expects. Here's the code to install and uninstall the event listener.

private static ButtonUIListener buttonuilistener = null;

public void installUI(Button button) { button.addMouseListener(buttonuilistener); button.addMouseMotionListener(buttonuilistener); button.addChangeListener(buttonuilistener); }

public void uninstallUI(Button button) { button.removeMouseListener(buttonuilistener); button.removeMouseMotionListener(buttonuilistener); button.removeChangeListener(buttonuilistener); }

The view/controller classes are really just bundles of methods. They don't contain any of their own state. Therefore, many instances of Button can share a single instance of ButtonUI. This code distinguishes between buttons by adding a special button identifier argument to each function call.

Take a moment to review the rest of the ButtonUI class before you move on.

The ButtonUIListener class

This ButtonUIListener class assists the Button class in converting mouse and keyboard input into operations on the underlying button model.

The listener class implements the MouseListener, MouseMotionListener, and ChangeListener interfaces and handles for the following events:

public void mouseDragged(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource();

ButtonModel buttonmodel = button.getModel();

if (buttonmodel.isPressed()) { if (button.getUI().contains(button, mouseevent.getPoint())) { buttonmodel.setArmed(true); } else { buttonmodel.setArmed(false); } } }

public void mousePressed(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource();

ButtonModel buttonmodel = button.getModel();

buttonmodel.setPressed(true);

buttonmodel.setArmed(true); }

public void mouseReleased(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource();

ButtonModel buttonmodel = button.getModel();

buttonmodel.setPressed(false);

buttonmodel.setArmed(false); }

public void stateChanged(ChangeEvent changeevent) { Button button = (Button)changeevent.getSource();

button.repaint(); }

Take a moment to peruse the rest of the ButtonUIListener class before you move on.

The code in action

For those of you who found the source code listings above a bit too sterile, and for those of you anxious to see how this all fits together, I present the applet.

You need a Java-enabled browser to see this applet.

This applet requires a JDK 1.1-compliant browser. Internet Explorer 4.0 qualifies, however, Netscape 4.0 does not -- at least right out of the box. If you're using Netscape 4.0, surf on over to http://developer.netscape.com/software/jdk/download.html and upgrade.

1 2 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more