The MVC Architecture and Argument Maps

The MVC Architecture Explained


Argument maps also have another purpose besides passing arguments between the application and the GUI converter -- to standardize the interface between GUI model and GUI view / controller code. These are the three components of the MVC architecture. The model is the code that maintains and changes the state of the application, in other words the code that determines what to display and how to respond to requests to perform actions. The controller is the code responsible for notifying the model of changes caused by the user manipulating the interface. The view is the code responsible for displaying the state of the application to the user, in other words, the code that determines how to display the model to the user. For example, when the user clicks a button to open a file, the controller notifies the model. The model then puts itself in an "Opening file" state,  and makes a request to the view. The view then displays a Open File dialog. When the user has finished selecting a file, the controller notifies the model. The model then changes its state to "Reading", stores the file name in its internal state, and opens it. After the file has been processed, the model changes its state to "Ready", and requests that the view display some interpretation of the file. The MVC architecture is a sensible way of separating the responsibilities of a GUI application, so that the logic behind what to display is separated from how to display it. In practice, the view and the controller are usually implemented together, because the components that display the interface are also used to gather user responses.

Benefits of the MVC Architecture

All this is very theoretical, so what is it good for? The MVC architecture provides a clean separation between the internal state, and how to display it to users. These are radically different tasks, so it is easier to write code that only implements the model, or code that only implements the view / controller. Thus model programmers do not need to know anything about how the view / controller is implemented, and vice versa. This is great because most data processing programmers hate to write GUI code, and do it badly when they have to. GUI programmers have more of an aesthetic sensibility and specialized knowledge of GUI tricks but may know little about how the model implements features. The MVC architecture splits these tasks so that data processing and GUI programmers can each focus on what they do best.

Another benefit of this is that the view / controller's implementation can change without forcing changes to the model. Thus, an interface can be upgraded or perhaps a "cooler" version can be substituted, without forcing the recompilation of the model. Think "skins".

Is Swing MVC? Up to a point ....

Unfortunately, the separation between the model and the view / controller is extremely weak in most Swing applications.

When people refer to Swing as an MVC architecture, they are referring to the implementation of each Swing component, not necessarily all GUI's constructed with Swing components. For example, the implementation of JTable splits the tasks of the storage of the table data (done with a TableModel), and the display of the table data (done with an instance of TableUI). Thus it has separated the model of the table from the view of the table. As anyone who has ever written an implementation of TableModel can tell you, this is very elegant.

However, it is up to the GUI application programmer to set boundaries between the model, view, and controller of the overall application. Unfortunately, without the text2gui library, most Swing applications are forced to combine the model and the view, which makes the model code dependent on the implementation of the view. For example, when displaying panel of settings, the current settings are displayed. This is typically done manually by bits of code like this:

volume = restoreVolumeSettingFromFile();
slider.setValue(volume);


volume is stored in a the variable of the model. When displaying a panel of settings, the model must manually set the state of the slider. This means it is dependent that the volume property is displayed with a slider! It might be desired later on to display this property with a scroll bar or a spinner. But this cannot be changed without changing the model code! What is needed is an interface to the view that doesn't depend on how the view is implemented.

Similarly, the interface between the controller and the model is typically implemented by using event listeners. But there are a myriad of different types of listeners, depending on what component is being used to respond to input. Thus, typically, the model is also dependent on the implementation of the controller.

A Lousy Attempt at Implementing the MVC Architecture

Now that we realize the benefits of the MVC architecture, we might be tempted to try to use a Java interface that represents the GUI, and have the model call methods of the Java interface, and have the model call methods of the interface instead of modifying components itself. For example, we could define a Java interface reprenting the GUI as follows:

public interface IAudioSettingsView {
    void setVolume(int volume);
    ... // more methods below
}

Some concrete class would need to implement this interface like this:

public class MyAudioSettingsView extends JPanel
  implements IAudioSettingsView {
    public
MyAudioSettingsView() {
        slider = new JSlider();
        add(slider);
    }

    public void setVolume(int volume) {
        slider.setValue(volume);
    }     

    private JSlider slider;
}

Then when the model wants to update the displayed volume, it does this:

volume = restoreVolumeSettingFromFile();
audioSettingsDisplay.setVolume(volume);

OK, now the model code doesn't depend on the implementation of the view. This is all works, but defining such an interface and writing the implementation is very tedious and time-consuming. It requires considerable design time to figure out what the interface should do. In addition, any change to the implementation requires recompilation. Even worse, changing the Java interface requires that both the model and the view be recompiled.

How would the model receive notification that a setting is changed? If it can wait until the view is dismissed, it can call a method like getVolume(). But what if the model needs to increase the volume of a sample sound as the volume setting is being changed? The model cannot add its own listeners directly to the slider, since it would then depend on the implementation of the GUI.  The model would need to add listeners to the view, which are notified when the slider's knob is moved. The view needs to maintain these listeners for the model, and furthermore add its own listeners to the slider. What a time-consuming pain!

The text2gui Solution : Argument Maps

What is needed is an interface between the model and view/controller that never needs to be recompiled, and can provide feedback to the model. Such an interface needs to accommodate any kind of property value of the model. A map is a natural choice for this interface, since its keys can be named arbitrarily, and its values can be any type of object. Instead of calling setVolume(), the model can associate the volume level with the volume key like this:

volume = restoreVolumeSettingFromFile();
argMap.putNoReturn("volume", new Integer(volume));

(putNoReturn() does the same thing as put(), but for efficiency, has no return value.) How does the view get this value? There are two ways. The first is way occurs when a component is created using a GUI converter -- its property values can be read from a map. For example, here is a line in a property resource bundle that will create a JSlider with its value set to the volume:

slider.value=$volume

Pretty easy, huh? What about updating the value after creation time? All the model needs to do is to re-map the "volume" key:

argMap.putNoReturn("volume", new Integer(100000)); // OUCH my ears!!!

When this happens, the map puts the new volume property and notifies a listener which updates the slider. This requires that the map be observable; listeners are notified of changes to the values mapped to keys. Fortunately, only one type of listener is needed for this task: PropertyChangeListener. These listeners are automatically added by the text2gui library when the slider is created (this can be disabled -- see below).

What if the model wants to receive notification that the currently selected volume is changing? First, the slider's value needs to be declared as writing the volume property when it changes:

slider.value=$volume:rw

The "r" means that the initial value should be read from the map during creation time. The "w" informs the text2gui library that a map consistency listener should be added to the slider. The map consistency listener updates the value mapped to volume in the map as the slider's knob is moved. The lack of a "u" informs the text2gui library that the slider's value will not be updated by the model.

So now the model needs only to listen for changes to the "volume" property. It does this by adding a PropertyChangeListener to the map:

argMap.addPropertyChangeListener("volume", new PropertyChangeListener() {
  public void propertyChanged(PropertyChangeEvent event) {
    Integer v = (Integer) event.getNewValue();
    int volume = v.intValue();

     // set the volume of the device
     ...
  }
});

As you can see, the interface between the model and the view/controller is now standardized using an observable map. The view is changed by calling putNoReturn() to associate property names with property values. The model receives feedback from the controller by adding instances of PropertyChangeListener to the map.

To prove our concept, let's consider changing the interface so that uses a spinner instead of a slider to gather the user's volume setting preference. This requires no recompilation, but the property resource bundle needs to contain this change:

spinner.value=$volume:rw

assuming that the spinner is part of a bigger panel. Of course, references to slider in the resource bundle also need to be changed to spinner.

A Concrete Example

We have discussed the role of argument maps above, but not how to create them. Also we have not discussed how write a complete application that uses argument maps or discussed the connection between resource bundles and maps. We'll explain these items here with a simple example. To understand this example, you need to have a basic understanding of how text2gui converts strings and resource bundles keys to objects -- for a review see Basics. We will create a frame to contain a volume control that prints out the volume as it is being changed. First we write the model:
import java.beans.*;
import java.util.ResourceBundle;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

// Used to create the resource bundle
import com.taco.util.ChainedResourceBundleFactory;

// For type of the argument map.
import com.taco.data.INoReturnObservableMap;

// Used to create the argument map.
import com.taco.swinger.SwingInvokeProxyFactory;

// Used to create the frame from the resource bundle and argument map.
import com.taco.swinger.text2gui.DispatchingComponentConverter;

public class VolumeModel {
public JFrame initGUI() {
// Create the bundle which must be backed by a file
// "VolumeModelResourceBundle.properties" in the classpath.
ResourceBundle bundle = ChainedResourceBundleFactory.DEFAULT_INSTANCE.getBundle(
"VolumeModelResourceBundle", getClass().getClassLoader());

// Create the argument map
argMap = SwingInvokeProxyFactory.makeSwingInvokeProxyMap();

// Set boundaries on the volume.
argMap.putNoReturn("volumeMin", new Integer(0));
argMap.putNoReturn("volumeMax", new Integer(200));

// Put the initial volume in the map
argMap.putNoReturn("volume", volume);

// Create the frame. Notice the argument map is passed to toObject().
// This allows the creation of the frame and its subcomponents to read and
// write values in the map.
frame = (JFrame) DispatchingComponentConverter.DEFAULT_INSTANCE.toObject(bundle,
"volumeFrame", argMap, null);

// Add a listener to the map that notifies us to changes to the "volume" property.
// It updates our internal volume field, and prints the new volume.
argMap.addPropertyChangeListener("volume", new PropertyChangeListener() {
public void (PropertyChangeEvent event) {
Integer v = (Integer) event.getNewValue();
volume = v.intValue();
System.out.println("Volume now set to " + volume);
}
});

return frame;
}

public static void main(String[] args) {
// Create on the event-dispatch thread so all GUI code runs there.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
VolumeModel model = new VolumeModel();
JFrame frame = model.initGUI();
frame.pack();
frame.setVisible(true);
}
});
}

private JFrame frame;
private INoReturnObservableMap argMap;
private int volume = 50;
}
Not too bad, I hope. Although it's a bit of trouble to create and setup the bundle and the argument map, at least we don't have to create any of the components inside the JFrame. The key point here is that the argument map is created by calling SwingInvokeProxyFactory.makeSwingInvokeProxyMap(). Also, we have to assume that the volumeFrame key of the bundle describes a JFrame. This is our only dependency on a Swing component. The contents of the frame can change arbitrarily.

 Now here's the property resource bundle that is used to describe the view and the controller, "VolumeModelResourceBundle.properties".

# this is our entry point: volumeFrame
volumeFrame.title=Set the volume
volumeFrame.contentPane=%volumePanel

volumePanel.layout=box axis=x
volumePanel.contents=[%volumeLabel, {strut length=15},  %volumeSlider]
volumePanel.prefSize={width=300, height=100}

volumeLabel.text=Volume:

# Note the :r suffix. The lack of a "u" means the model won't update the minimum value,
# so there is no need for text2gui to
add a listener to the map to update this value.
volumeSlider.min=$volumeMin:r
volumeSlider.max=$volumeMax:r

# Again the lack of a "u" means the model won't update the value.
# The "w" means that changes to the slider value should
update the map, which will in turn
# update the model since there's a property change listener attached to the map for the
# "volume" property.
volumeSlider.value=$volume:rw

Compiling and running this example will get you something crude, but you can change the GUI just by editing the property file, without recompiling "VolumeModel.java".

Avoiding MVC

text2gui does not force you to implement your GUI using the MVC architecture; it merely provides a way to do so if you wish. One technique for constructing a GUI using text2gui would be to put only initial values in the argument map. All of the components you wish to have control over in your application can be put into the global namespace during conversion. Then your application can retrieve all of these components and manipulate them as desired. See The Use of Globals for Non-MVC Implementations for an example. Also, custom components can be put into the argument map, and integrated into a component hierarchy using argument map references. Using these techniques is frequently the easiest way to convert existing GUI's to use the text2gui library. However, it's usually worth the effort to redesign your code so that it only performs the function of the model in an MVC implementation. With the aid of the text2gui library, model code can be considerably simplified which makes it easier to maintain.

Summary

We have shown how argument maps can be used to separate the model from the view/controller. This separation has the advantage that the code for the model is simpler because it only maintains state, processes input events from the controller, and makes requests to the view. The model does not depend on how the view is implemented, which speeds the development process considerably and also allows the interface to be upgraded.

The MVC architecture is not an all-or-nothing proposition. In reality, it can be difficult to completely separate the model from the view/controller. However, the more separation the better. More separation implies less dependencies which implies less need for recompilation and more opportunities for changes in the future. More separation also implies a reduction in code complexity and an increase in maintainability.

With the use of argument maps, you can focus on the implementation of the model, leaving most of the view/controller code up to text2gui. Of course, the structure of the view/controller needs to be defined in a resource bundle, and some additional control code may need to be written in the form of embedded scripts. But on the whole, the use of argument maps makes creating GUI's with the MVC architecture a breeze.