Creating Custom Desktop Components

Swing provides a complete set of standard GUI components, ranging from simple
buttons and text fields to feature-rich tables, trees, and text editors. These
components are fully customizable, but you might find that Swing's built-in components
don't offer everything you need. For example, financial and monitoring applications
use charts to present their data graphically. Of course, before starting to
build your own chart components, you should evaluate some of the existing chart
frameworks, in case someone has already created the component you need. Sometimes,
this isn't the case, or perhaps the licensing terms are not acceptable, which
means that you have to develop the custom component required by your
application yourself. This article presents a drawing component used by an image-annotation
application named JImaging.
Some of the JImaging code has already been described in two other articles, titled
"Prototyping
Desktop Applications" and "Data
Models for Desktop Apps."

Developing the Component Class

JImaging's PaintView class extends javax.swing.JComponent,
like any regular Swing component. The JComponent class provides
many features shared by all Swing components, such as the support for double
buffering, which eliminates the flashing effect that occurs when the graphic
objects are painted directly onto the screen. With double buffering, the UI components
are painted into a buffer, and when the painting is done, the buffer is copied
to your screen very quickly.

The painting mechanisms of AWT and Swing are based on a paint()
method that can draw a whole component. This works very well for the standard
GUI components, such as text boxes, trees, and tables, that don't need to be repainted
at a high rate. But when you draw something on screen, using a virtual pencil,
hundreds or thousands of mouse events are generated while you move the mouse.
It is not efficient to repaint the whole component for each event. It is much
better to draw small lines directly on the screen, outside of the AWT/Swing
paint() method, as you'll see later in this article.

Figure 1 shows the main frame of the application, which has a toolbar and
a main panel (based on Swing's JDesktopPane) that contains the
PaintView component. Text notes (based on Swing's JInternalFrame)
are painted on top of our custom component.

Figure 1. The GUI components of the JImaging prototype

The Component's Data Model

Swing components obtain the data they visualize from the model objects that
are updated when the user types characters, clicks on something like a list
item, or does some other action with the mouse or the keyboard. In the previous
article in this series, "Data
Models for Desktop Apps,") I presented the PaintModel class,
which manages the information about all annotations that are painted by the PaintView
component. The separation between the data model and the GUI that visualizes
the data has many benefits: the components of the application can be changed
or enhanced independently, the application's data is kept in one place, and it
becomes easier to reuse the application's code. An instance of the PaintModel
class is created in the PaintView() constructor. Other classes
may obtain a reference to the model object with the getModel()
method. This method is not specified by any Swing interface or superclass, but
all standard Swing components have such a method. For example, JTable's
getModel() returns a TableModel instance, JTree's
getModel() returns a TreeModel instance, and so on.
Following this pattern, the getModel() method of PaintView
returns a PaintModel object.

Registering Event Listeners

In order to be able to respond the user's actions, most GUI components register
mouse listeners and/or keyboard listeners with the addMouseListener(),
addMouseMotionListener(), and addKeyListener() methods
inherited from java.awt.Component. Note that there are components,
such as JLabel, that don't handle mouse nor keyboard events.

The PaintView component has a method named registerListeners()
that registers a mouse listener and a mouse motion listener. The methods implemented
by these listeners are called when the user presses or releases a mouse button
or when he drags the mouse with a button pressed, respectively. Only the events generated
with the left mouse button are processed with the toolAction()
method, which is presented below. The mousePressed() method creates
a tool object that is passed to the data model in the mouseReleased()
method. In addition to the two mouse listeners, registerListeners()
creates a PropertyChangeListener that is registered to the data
model. The propertyChange() method is called when the value of
a model property is changed, which means that the PaintView component
needs to be repainted:

Note that toolAction() is called from the mouse event handlers,
which are executed in an AWT thread that also invokes the paint()
method inherited by the UI component from JComponent. The image
annotations are shown on screen, but neither AWT nor Swing keeps them in memory
after they are painted. The application must be prepared to repaint the annotations
at any time. If the application wants to refresh the content of the PaintView
component, it must call repaint(), which generates an AWT PaintEvent.
All UI events, including the mouse and paint events, are queued. These events
are processed by the AWT thread that invokes paint(), mousePressed(),
mouseReleased(), mouseDragged(), and so on.

Non-AWT events such as PropertyChangeEvent are not queued. As
explained in the previous article,
propertyChange() is called by the firePropertyChange()
methods of the data model. This has serious implications in multithreaded applications,
which is not the case with JImaging. A discussion about the AWT event model or
about thread safety is beyond the scope of this article. If you aren't familiar
with these notions, you should read The
Java Tutorial.