Skip to main content

Architecting Applications 3: the Controller

Posted by zixle on March 6, 2006 at 4:24 PM PST

This is the third blog in a series on architecting applications. In
the first blog I discussed the application I'm going to develop,
how it would be architected, and briefly went over the model. In the
second blog I motivated the need for an Application class that is
suitable for typical Swing based Apps, as well as the functionality it
should provide. In this third installment I'll go over the role of the
controller as used in the MVC architecture. As promised, this blog has
a runnable demo.

I was strongly tempted to redo the model a bit for this installment of
the application architecture series, but I had to hold back lest there
be no demo for this article. So, without further ado, here's the
demo.


width="88"
height="23"
style="border-style:none; margin-right:44px"/>
style="border: none; "/>

Password Store Demo Screenshot

The demo is far from complete. In particular most of the menus are not
wired up and are disabled. The only menu items that work is creating
a new account (control-n). I'll flesh out the implementation of the
remaining menu items in future articles. Complete source code is
available at the end of the article.

To keep the app from being signed, but be able to store information
between sessions I'm using the persistence service provided by
webstart. The persistence service allows an app to save state in a
secure way, similar to cookies. If you run the app from the command
line it'll save the file into your home directory.

Controller

As early explained this app is built using the model-view-controller
pattern. As the name implies the pattern dictates you design three
distinct areas: the model, for modelling your data; the view, for the
graphical view the user will interact with; lastly, the controller
which orchestrates the two, keeping them in sync.

For the purposes of this demo the Controller will perform the
following operations:

  1. Create and assemble the view.
  2. Wire the view to the model or controller as appropriate.
  3. Keep the view and model in sync.

A Controller need not create and assemble the view. Undoubtedly the
view is intimately tied to the controller, and in may apps, such as
this one, making the controller create the UI fits naturally. Ideally
the view would be loaded from a GUI builder and the controller could
extract the components it's interested in, but that's for a separate
blog.

Creating the UI

Creating and assembling the view is pretty much route Swing
programming. As earlier mentioned I'm not going to have any external
dependencies, so I wrote this code by hand using GridBagLayout. I
honestly don't recommend hand coding UI creation or layout, it's
painful, error prone, and a maintenance burden. Use a builder. Of
course I recommend Matisse and once done will will publish a followup
blog that's written in terms of Matisse.

JList Wiring

The model consists of a List of PasswordEntrys. This will be
rendered as a JList. JList itself requires a model which implements
the ListModel interface. To display the List of PasswordEntrys a
custom ListModel implementation will be used that talks to the List of
PasswordEntrys from the model. Here's the code.

  private PasswordModel model;
  private class PasswordListModel extends AbstractListModel {
    public int getSize() {
      return model.getPasswordEntries().size();
    }
    public Object getElementAt(int index) {
      return model.getPasswordEntries().get(index);
    }
  }

Notice the ListModel returns a PasswordEntry for each of the
elements. JList's default cell renderer will use toString on this,
which isn't all that helpful here. As such we'll need a custom
ListCellRenderer. I'm not going to go over the code for the
CellRenderer, it's pretty simple, if you're curious look to the
source.

Synchronization

Keeping the model and view in sync is one of the more tedious aspects
of an application of this sort. To reinforce that, consider what
happens when the user types in a text field. As the user types in a
text field the model must be updated. As I wanted the model to be
updated immediately I'm using a DocumentListener. DocumentListeners
are notified immediately of any changes to a text component. Here's
the code.

  // Install a DocumentListener on the host text field
  hostTF.addDocumentListener(new DocumentHandler());

  private class DocumentHandler implements DocumentListener {
    public void insertUpdate(DocumentEvent e) {
      edited(e);
    }

    public void removeUpdate(DocumentEvent e) {
      edited(e);
    }

    public void changedUpdate(DocumentEvent e) {
      // TextFields can ignore this one.
    }

    private void edited(DocumentEvent e) {
      // The host text field was edited, update the model
      hostChanged();
    }
  }

  // A valid edit has occured, update the model.
  private void hostChanged() {
    getSelectedEntry().setHost(hostTF.getText());
  }

The real source code is slightly different in so far as I've coalesced
all the listeners into a single class, which leads to a switch like
statement in the edited method. None-the-less the core idea is the
same. As the document is edited the model is immediately updated.

The Controller must also track changes made to the model by other
parts of the app. For example, an Action might directly update the model. To
track changes to the model the Controller installs a
PropertyChangeListener on each PasswordEntry. Here's the code.

  // Install a listener on each entry
  entry.addPropertyChangeListener(new PropertyChangeHandler());

  private class PropertyChangeHandler implements PropertyChangeListener {
    public void propertyChange(PropertyChangeEvent e) {
      entryChanged((PasswordEntry)e.getSource(), e.getPropertyName());
    }
  }

  private void entryChanged(PasswordEntry passwordEntry,
          String propertyChanged) {
    // Notify the list of the change
    listModel.fireContentsChanged(model.getPasswordEntries().
              indexOf(passwordEntry));

    // If the selected entry was changed, update the appropriate text field
    if (getSelectedEntry() == passwordEntry) {
      if (propertyChanged == "host") {
        hostTF.setText(passwordEntry.getHost());
      } else ...
    }
  }

A PropertyChangeListener is attached to
every entry in the model. When an entry is changed, ListModel notification
is dispatched (listModel.fireContentsChanged), which triggers the JList to
update itself. Similarly if the contents of the selected entry have
changed the appropriate text field is updated.

The following graphically depicts changes originating from the text field.

The following depicts changes originating from the model.

What's wrong with the previous code? Turns out there is a subtle
bug. When the text field changes, we update the model, which sends
notification, which triggers a reset of the text field. JTextField
(actually javax.swing.text.Document) does not allow a listener to
modify the contents, so that this code triggers Swing to thrown an
exception. Here's a graphical depiction of this.

Notice the textfield on both sides.

This is happening because the Controller doesn't realize that it initiated
the change to the model and doesn't need to update the display. There
are a couple of schemes for dealing with this. You can disable and
reenable listeners, check the contents of the text fields, or keep a
property around. I've settled for keeping a property around that
tracks if the Controller initiated a change to the model.

Here's the revised code for the edited method that is invoked when the
text is edited.

  private void hostChanged() {
    if (!changingHost) {
      changingHost = true;
      getSelectedEntry().setHost(hostTF.getText());
      changingHost = false;
    }
  }

Now the hostChanged method knows who originated the code. If
changingHost is true it indicates the change was initiated by the
Controller and the Controller need not do anything.

The entryChanged method is similarly updated.

  private void entryChanged(PasswordEntry passwordEntry,
          String propertyChanged) {
    // Notify the list of the change
    listModel.entryChanged(model.getPasswordEntries().
              indexOf(passwordEntry));

    if (getSelectedEntry() == passwordEntry) {
      if (propertyChanged == "host" && changingHost) {
        changingHost = true;
        hostTF.setText(passwordEntry.getHost());
        changingHost = false;
      } else ...
    }
  }

This is most definitely one of the more tedious aspects of GUI
programming. One forgotten !changingXXX or changingXXX=true, and
you'll either get an exception, or you'll stop updating the
model or UI. Ugh!

The last thing worth mentioning is that as List has no notification
the Controller can not listen to it. Instead all mutations to the list
itself are done in Controller which will update the ListModel
appropriately. I will revist this later on.

Source

I've broken the source into
two chunks: fabric.zip contains the generally useful stuff, and
passwordStore.zip contains the source specific to PasswordStore.

Summary

So, where are we? I've gone through the model, I've touched on the
Application class, and went through the bulk of the
controller. At this point the app is usable, but by no means a complete
app. Next time around I'll either go over undo, or
cut/copy/paste. Which would you prefer?

    -Scott

Related Topics >>