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

advertisement

AddThis Social Bookmark Button

Creating Custom Desktop Components
Pages: 1, 2

Implementing paintComponent()

Old AWT components had to implement the paint() method for their painting. The Swing's JComponent overrides this method and defines three new methods: paintComponent(), paintBorder(), and paintChildren(). Custom Swing components may override any of these new methods, which are called by paint(), but in most situations you only need to implement paintComponent(). For more details on this subject, you should read "Painting in AWT and Swing" on The Swing Connection.

The paintComponent() method of the PaintView component:

  • Obtains the annotated image with the getBackImage() method of the data model.
  • Draws a black rectangle if getBackImage() returns null.
  • Passes the zoom factor to the scale() method of the graphics context.
  • Draws the annotated image (if present).
  • Sets the background color of the graphics context.
  • Iterates over the tool objects, calling their paint() method in order to draw all image annotations.
protected void paintComponent(Graphics g) {
     super.paintComponent(g);
     Image backImage = model.getBackImage();
     if (backImage == null) {
         g.setColor(Color.black);
         Dimension size = getSize();
         g.drawRect(0, 0, size.width-1, size.height-1);
     }
     Graphics2D g2 = (Graphics2D) g;
     float zoomFactor = model.getZoomFactor();
     g2.scale(zoomFactor, zoomFactor);
     if (backImage != null)
         g2.drawImage(backImage, 0, 0, this);
     g2.setBackground(getBackground());
     Iterator iterator = model.getToolIterator();
     while (iterator.hasNext()) {
         AbstractTool tool
             = (AbstractTool) iterator.next();
         tool.paint(g2, backImage);
     }
 }

When the user finishes drawing an annotation and releases the mouse button, the mouseReleased() method described earlier calls repaint(), requesting AWT to execute the paint() method, which invokes paintComponent(). The custom component is also repainted when its content needs to be refreshed. This happens, for example, when the application's window is resized or maximized.

User Interface Operations

Typical custom Swing components need to do two things: implement the painting operation that visualizes the data model, and handle the user actions (mouse and/or keyboard events) in order to update the data model. These operations are specific to each component, but as you've seen in the previous section, AWT and Swing provide generic mechanisms such as the paint() and paintComponent() methods that must be overridden by the custom components, and the registration mechanism for your event listeners. Simple GUI components can implement both operations in the same class. Complex components, however, should split the functionality into specialized classes, making the code more maintainable. For example, Swing's JTable uses cell renderers and cell editors for the two user interface operations. The PaintView component delegates its UI operations to a set of "tool" classes that paint lines, rectangles, ellipses, text notes, and so on. This makes the application extensible, allowing you to add support for new annotations without having to change the application's architecture. Note that these classes are specific to JImaging, but you'll probably find them useful when developing other drawing components or applications. Figure 2 shows JImaging's tools.


Figure 2. The tool classes are selected from the toolbar

Painting Image Annotations

Each image annotation has a set of properties that define its shape and specify the color and other rendering attributes. The AbstractTool class groups these properties using java.awt.Shape, java.awt.Color, and java.awt.Stroke:

package com.devsphere.articles.desktop.paint.tools;
 
 import java.awt.Color;
 import java.awt.Graphics2D;
 import java.awt.Image;
 import java.awt.Stroke;
 import java.awt.Shape;
 
 public abstract class AbstractTool {
     public static final int DRAW_STYLE = 1;
     public static final int FILL_STYLE = 2;
 
     private Shape shape;
     private Color color;
     private Stroke stroke;
     private int paintStyle;
     ...
 }

The AbstractTool class has get and set methods for each of its properties: shape, color, stroke, and paintStyle.


public Shape getShape() {
     return shape;
 }

 public void setShape(Shape shape) {
     this.shape = shape;
 }

 public Color getColor() {
     return color;
 }

 public void setColor(Color color) {
     this.color = color;
 }

 public Stroke getStroke() {
     return stroke;
 }

 public void setStroke(Stroke stroke) {
     this.stroke = stroke;
 }

 public int getPaintStyle() {
     return paintStyle;
 }

 public void setPaintStyle(int paintStyle) {
     this.paintStyle = paintStyle;
 }

The abstract action() method must be implemented by the subclasses of AbstractTool in order to handle the user actions, such as the mouse events generated while the user draws an annotation on screen.

public abstract void action(int eventID,
    float x, float y, Graphics2D g);

The paint() method has two parameters: the graphics context and the annotated image. These parameters are passed to paintImpl() along with the tool properties: shape, color, stroke, and paintStyle. The paintImpl() method sets the rendering attributes of the graphics context and calls draw() or fill(), depending on the value of the paintStyle property:

public void paint(Graphics2D g, Image backImage) {
     paintImpl(g, backImage, shape, color,
         stroke, paintStyle);
 }

 protected void paintImpl(Graphics2D g,
         Image backImage, Shape shape, Color color,
         Stroke stroke, int paintStyle) {
     g.setColor(color);
     g.setStroke(stroke);
     switch (paintStyle) {
         case DRAW_STYLE:
             g.draw(shape);
             break;
         case FILL_STYLE:
             g.fill(shape);
             break;
     }
 }

Handling User Actions

The PencilTool class handles the mouse events that occur when the user draws something using the application's pencil. The PencilTool() constructor creates a java.awt.geom.GeneralPath instance and sets the shape and paintStyle properties:

package com.devsphere.articles.desktop.paint.tools;
 
 import java.awt.Graphics2D;
 import java.awt.event.MouseEvent;
 import java.awt.geom.GeneralPath;
 import java.awt.geom.Line2D;
 import java.awt.geom.Point2D;
 
 public class PencilTool extends AbstractTool {
     private GeneralPath path;
 
     public PencilTool() {
         path = new GeneralPath();
         setShape(path);
         setPaintStyle(DRAW_STYLE);
     }
     ...
 }

The action() method takes four parameters: the ID of a mouse event, the x and y coordinates of the mouse cursor, and the graphics context. Note that x and y are float numbers because they may have a fractional part if the zoom factor isn't 1. These coordinates are calculated in the toolAction() method of the PaintView class.

When the user presses the mouse button, the action() method calls the moveTo() method of the GeneralPath object, passing the x and y coordinates. When the user drags the mouse, action() calls the lineTo() method of the GeneralPath object and creates a java.awt.geom.Line2D that is painted with the draw() method of the graphics context. Therefore, the action() method does two things:

  • Draws small lines between the points where the mouse events are generated.
  • Stores all of these points into a GeneralPath object so that the shape can be redrawn at a later time.

public void action(int eventID, float x, float y,
         Graphics2D g) {
     if (eventID == MouseEvent.MOUSE_PRESSED)
         path.moveTo(x, y);
     else if (eventID == MouseEvent.MOUSE_DRAGGED) {
         Point2D startPoint = path.getCurrentPoint();
         Point2D endPoint = new Point2D.Float(x, y);
         Line2D l = new Line2D.Float(startPoint, endPoint);
         g.setColor(getColor());
         g.setStroke(getStroke());
         g.draw(l);
         path.lineTo(x, y);
     }
 }

Summary

When developing desktop GUIs, most of the time you use existing components as they are. But sometimes you have to customize them, and there are situations where you have to build your own components from scratch, which requires a thorough understanding of the internal mechanisms of AWT and Swing. The example component of this article implements the basic operations of the typical UI components: painting and event handling. Not so common is the painting of the graphic objects outside of the paint() method, but this is a very useful technique when developing drawing applications. It can make the difference between a slow app and one that works very fast, no matter what API you use: Swing, AWT, SWT, Win32, .NET, or something else.

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