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

advertisement

AddThis Social Bookmark Button

Creating Custom Desktop Components Creating Custom Desktop Components

by Andrei Cioroianu
08/11/2004

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

Related Reading

Java Swing
By Marc Loy, Robert Eckstein, Dave Wood, James Elliott, Brian Cole

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.

package com.devsphere.articles.desktop.paint;
 
 import com.devsphere.articles.desktop.paint.tools.AbstractTool;
 
 import javax.swing.JComponent;
 import javax.swing.SwingUtilities;
 
 import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.Image;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseMotionAdapter;
 
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 
 import java.util.Iterator;
 
 public class PaintView extends JComponent {
     private PaintModel model;
     private AbstractTool currentTool;
 
     public PaintView() {
         model = new PaintModel(this);
         registerListeners();
     }
 
     public PaintModel getModel() {
         return model;
     }
     ...
 }

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:


protected void registerListeners() {
     addMouseListener(new MouseAdapter() {
         public void mousePressed(MouseEvent e) {
             if (SwingUtilities.isLeftMouseButton(e)) {
                 requestFocus();
                 currentTool = model.createTool(
                     AbstractTool.DRAW_STYLE);
                 toolAction(e);
             }
         }

         public void mouseReleased(MouseEvent e) {
             if (SwingUtilities.isLeftMouseButton(e)) {
                 toolAction(e);
                 model.setLastTool(currentTool);
                 currentTool = null;
                 repaint();
             }
         }
     });

     addMouseMotionListener(new MouseMotionAdapter() {
         public void mouseDragged(MouseEvent e) {
             if (SwingUtilities.isLeftMouseButton(e))
                 toolAction(e);
         }
     });

     model.addPropertyChangeListener(
             new PropertyChangeListener() {
         public void propertyChange(PropertyChangeEvent e) {
             if (isShowing())
                 repaint();
         }
     });
 }

The toolAction() method consumes mouse events, forwarding them to the current tool object for processing. In addition, this method:

  • Obtains a graphics context with getGraphics(), which is necessary in order to paint directly onto the screen.
  • Obtains the model's zoom factor with getZoomFactor().
  • Calls the scale() method of the graphics context, so that the image annotations can be drawn taking into account the zoom factor.
  • Obtains the screen coordinates of the mouse cursor with getX() and getY().
  • Converts the screen coordinates into the x and y coordinates that do not depend on the zoom factor.
  • Calls the action() method of the current tool object.
  • Disposes the graphics context.
protected void toolAction(MouseEvent e) {
     e.consume();
     Graphics2D g2 = (Graphics2D) getGraphics();
     float zoomFactor = model.getZoomFactor();
     g2.scale(zoomFactor, zoomFactor);
     float x = e.getX() / zoomFactor;
     float y = e.getY() / zoomFactor;
     currentTool.action(e.getID(), x, y, g2);
     g2.dispose();
 }

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.

Pages: 1, 2

Next Pagearrow