ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Data Models for Desktop Apps
Pages: 1, 2

The Tool Classes Used by PaintModel

The application model keeps the image annotations in a java.util.LinkedList, whose elements are instances of the classes from the com.devsphere.articles.desktop.paint.tools package. These tool classes will be presented in a future article of this series. You may study their source code if you want, but at this moment, the only thing you need to know about them is that each tool object keeps information about an image annotation that was painted by the user with the mouse.



The getLastTool() method returns the last element of toolList:

public AbstractTool getLastTool() {
   if (toolList.isEmpty())
      return null;
   else
      return (AbstractTool) toolList.getLast();
}

The setLastTool() method adds an element to toolList:

public void setLastTool(AbstractTool newLastTool) {
   AbstractTool oldLastTool = getLastTool();
   toolList.add(newLastTool);
   firePropertyChange(LAST_TOOL_PROPERTY, oldLastTool, newLastTool);
}

You may use getToolIterator() to obtain all elements of toolList, one by one:

public Iterator getToolIterator() {
   return toolList.iterator();
}

The createTool() method creates an instance of the current toolClass, initializing its properties with the current color and stroke:

public AbstractTool createTool(int paintStyle) {
   AbstractTool tool = null;
   try {
      tool = (AbstractTool) toolClass.newInstance();
   } catch (IllegalAccessException e) {
      throw new InternalError(e.getMessage());
   } catch (InstantiationException e) {
      throw new InternalError(e.getMessage());
   }
   tool.setColor(toolColor);
   tool.setStroke(toolStroke);
   tool.setPaintStyle(paintStyle);
   return tool;
}

Finally, removeTool() can be used to remove an element from toolList:

public void removeTool(AbstractTool tool) {
   toolList.remove(tool);
}

The toolList could have been an indexed property, allowing direct access to its elements. However, the java.beans.PropertyChangeSupport class doesn't provide features for indexed properties. In addition, the GUI components need to be notified only when a tool object is added to the list, and this can be done with the lastTool property.

Using Data Models

As explained in the previous section, PaintModel keeps the annotations that are shown on screen by the MainPanel and PaintView components. Some of the properties of PaintModel are used and modified by the application's toolbar, which is created by the ToolBarBuilder class. Therefore, the state of the data model (PaintModel) is reflected on screen by three components: a desktop panel (MainPanel), a custom component (PaintView), and a toolbar (see Figure 1). These GUI components get the model's data using the get methods and change the model's state using the set methods. Some of these components register themselves as event listeners in order to be notified when the model's state is changed. The PaintModel class doesn't have to know anything about the GUI components.

Figure 1
Figure 1. The GUI components of the JImaging prototype

Creating a PaintModel Instance

An instance of the PaintModel class is created by the PaintView GUI component, which has a getModel() method that returns the model:

package com.devsphere.articles.desktop.paint;
...
public class PaintView extends JComponent {
   private PaintModel model;
   ...
 
   public PaintView() {
      model = new PaintModel(this);
      ...
   }
 
   public PaintModel getModel() {
      return model;
   }
   ...
}

The details of the PaintView class will be presented in the next article of this series.

Registering Listeners to PaintModel

MainPanel is one of the classes that register listeners to the data model with addPropertyChangeListener(). When the value of a model property is changed, the propertyChange() method of the event listener is called by a firePropertyChange() method inherited by PaintModel from PropertyChangeSupport. Depending on the name of the changed property, the event listener calls other methods of MainPanel:

package com.devsphere.articles.desktop.frames;
...
public class MainPanel extends JDesktopPane {
   public static final int INIT_WIDTH = 600;
   public static final int INIT_HEIGHT = 400;
   public static final Integer PAINT_LAYER = new Integer(10);
   public static final Integer NOTE_LAYER = new Integer(20);
 
   private PaintView paintView;
 
   public MainPanel() {
      paintView = new PaintView();
      setBackground(paintView.getModel().getBackColor());
      setDragMode(OUTLINE_DRAG_MODE);
      add(paintView, PAINT_LAYER);
      registerListeners();
   }
 
   public PaintView getPaintView() {
      return paintView;
   }
 
   protected void registerListeners() {
      paintView.getModel().addPropertyChangeListener(
       new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent e) {
             String p = e.getPropertyName();
             if (p.equals(PaintModel.BACK_IMAGE_PROPERTY)
              || p.equals(PaintModel.ZOOM_FACTOR_PROPERTY))
                updateSize();
             if (p.equals(PaintModel.LAST_TOOL_PROPERTY)
              && e.getNewValue() instanceof NoteTool)
                addNoteFrame((NoteTool) e.getNewValue());
         }
      });
   }
   ...
}

When the backImage or zoomFactor properties are changed, their set methods use firePropertyChange(), which calls the propertyChange() method of the listener created and registered by MainPanel. The propertyChange() method invokes updateSize(), which changes the size of the MainPanel. Note that this panel is wrapped in a JScrollPane by MainFrame. The updateSize() method also sets the bounds of the PaintView component. This last operation is necessary because the main panel holding the paint component is actually a Swing desktop panel (MainPanel extends JDesktopPane).

public void updateSize() {
   int width = INIT_WIDTH;
   int height = INIT_HEIGHT;
   PaintModel paintModel = paintView.getModel();
   if (paintModel.getBackImage() != null) {
      Image backImage = paintModel.getBackImage();
      width = backImage.getWidth(this);
      height = backImage.getHeight(this);
   }
   float zoomFactor = paintModel.getZoomFactor();
   width = (int) (width * zoomFactor);
   height = (int) (height * zoomFactor);
   Dimension size = new Dimension(width, height);
   setPreferredSize(size);
   setSize(size);
   paintView.setBounds(0, 0, width, height);
}

The zoomFactor property is changed when the user modifies the selection of the combo box from the upper-left corner of the window. Figure 2 shows the annotated screenshot of Figure 1 within the JImaging prototype. The zoom factor is 200 percent:

Figure 2
Figure 2. Zoomed screenshot within JImaging

Saving Data Models

The PaintModel class is a data model with get and set methods -- it lets you register event listeners -- but PaintModel is not a fully conformant JavaBean, because it is not serializable. That means that you cannot save the model using Java Object Serialization, which is a mechanism for transforming objects into byte streams that may be saved into files or sent over a network to another computer. Those byte streams can be used to reconstruct the objects together with their links (references). See the Java Object Serialization specification for more details.

The easiest way to make a class serializable is to declare that your class implements java.io.Serializable, which is an interface with no methods. You would also have to make sure that all of its non-static, non-transient fields are serializable, too. Some of the fields of PaintModel and the tool objects are not serializable because their classes do not implement java.io.Serializable nor java.io.Externalizable. While Java Object Serialization sounds like a great idea, sometimes it doesn't work very well in practice. All Swing components implement java.io.Serializable, but their serialization usually fails because some fields of many Swing classes are not serializable. Also, changing the Java code of your classes may easily break the ability to deserialize the objects saved with the old version of the code.

J2SE 1.4 introduced an XML-based mechanism for archiving JavaBeans, which is exposed through the java.beans.XMLEncoder and java.beans.XMLDecoder APIs. This mechanism is fault-tolerant, meaning that it is not interrupted by an exception when an object cannot be archived (serialized) or reconstructed (deserialized). You can "listen" for such exceptions, but it can be difficult to make sure that your objects are archived properly because only the public JavaBean properties are saved without the internal state of the components, such as private fields that aren't mapped to properties.

The java.beans.XMLEncoder cannot save the state of the PaintModel, which has fields whose types aren't JavaBeans. We would have to build "persistence delegates" that know how to save the entire state of the objects that aren't JavaBeans. In the case of the JImaging application, another way to save its data model would be a custom file format, possibly based on XML, but this is beyond the scope of a prototype. In its current version, JImaging can save only the annotated images, as explained in a previous article.

Summary

How would this prototype look without a data model and without using events and listeners? As an exercise, try to take out the PaintModel class, moving its properties into the PaintView component. Instead of using PropertyChangeSupport and PropertyChangeListener, call methods directly when one component must notify another component about a property change. This creates a lot of unnecessary relationships between your classes. For a small application such as JImaging, it might not be a problem, but when you have hundreds or thousands of classes, the unnecessary dependencies make the code very hard to maintain. Model-view separation and event-listener mechanisms solve this problem. The next article of this series will focus on views, showing how to build custom Swing components.

Resources

Andrei Cioroianu is the founder of Devsphere and an author of many Java articles published by ONJava, JavaWorld, and Java Developer's Journal.


Return to ONJava.com.