The Source for Java Technology Collaboration
User: Password:
Register | Login help    

Search

Online Books:
java.net on MarkMail:


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.

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 >> Open JDK      
Comments
Comments are listed in date ascending order (oldest first)