站内搜索: 请输入搜索关键词
当前页面: 在线文档首页 > Java Tutorial 5.0 英文版

Implementing a Custom Component - Java Tutorial 5.0 英文版

The JavaTM Tutorial
Previous Page Lesson Contents Next Page Start of Tutorial > Start of Trail > Start of Lesson Search
Feedback Form

Trail: Creating a GUI with JFC/Swing
Lesson: Performing Custom Painting

Implementing a Custom Component

Before you implement a component that performs custom painting, first make sure that you really need to do so. You might be able to use the text and image capabilities of labels (in the Creating a GUI with JFC/Swing trail), buttons (in the Creating a GUI with JFC/Swing trail), or text components (in the Creating a GUI with JFC/Swing trail) instead. And remember, you can sometimes use borders (in the Creating a GUI with JFC/Swing trail) to customize the outside edges of a component and icons (in the Creating a GUI with JFC/Swing trail) to paint an area that perhaps varies by component state. If you need to make changes to many standard components, you should consider doing it by customizing a look and feel (in the Creating a GUI with JFC/Swing trail) such as the GTK+ look and feel.

If you really need to perform custom painting, then you need to decide which superclass to use. Your component can extend JComponent, JPanel, or a more specialized Swing component class.

For example, if you're creating a custom button class, you should probably implement it by extending a button class such as JButton or JToggleButton. That way you'll inherit the state management provided by those classes. If you're creating a component that paints on top of an image, you might want to create a JLabel subclass. A component that's a specialized container should probably extend JPanel. On the other hand, if you're implementing a component that generates and displays a graph, for example — with or without providing user interaction — then you might want to use a JComponent subclass.

When implementing custom painting code for a component, keep these rules in mind:

  • Your custom painting code should be in a method with the signature protected void paintComponent(Graphics).
  • You can — and probably should — use a border to paint the outside edges of your component.
  • Except when painting the background of the component, you should avoid painting over the border area of the component. You can determine this area using the getInsets method.
  • Your component must honor the opaque property. If your component is opaque, it must paint its complete area using an opaque color or colors. If its opaque property is false, then you have the option of not painting over the entire component.
  • Make sure that when the paintComponent method exits, the Graphics object that was passed into it has the same state that it had at the start of the method.
  • To gain access to the power of the 2D graphics API (in the Creating a GUI with JFC/Swing trail), you can cast the Graphics parameter into a Graphics2D object.

Besides those painting-related considerations, here are a few more rules to keep in mind:

  • Your component should return reasonable size information. Specifically, you should either override the getMinimumSize, getPreferredSize, and getMaximumSize methods or make sure that your component's superclass supplies values that are appropriate.
  • Your component should be as accessible as possible. For details, see Making Custom Components Accessible (in the Creating a GUI with JFC/Swing trail).
  • You should separate out strings and resources such as images so that your component can be easily localized. More information is in Internationalization (in the Creating a GUI with JFC/Swing trail).

An Example of Custom Painting

The following application gives an example of custom painting. It features a custom component called IconDisplayer that paints an icon multiple times, with all but the rightmost icon transparent.

4 faded rocket ships follow Duke's rocket ship

You can run the IconDisplayer application using JavaTM Web Start (in the Creating a GUI with JFC/Swing trail). Or, to compile and run the example yourself, consult the example index.

The source code is in IconDisplayer.java (in a .java source file). Here are the main painting-related parts:

public class IconDisplayer extends JComponent {
    ...
    protected void paintComponent(Graphics g) {
        if (isOpaque()) { //paint background
            g.setColor(getBackground());
            g.fillRect(0, 0, getWidth(), getHeight());
        }

        if (icon != null) {
            ...
            Graphics2D g2d = (Graphics2D)g.create();

            while (/* we're not done */) {
                /* Paint an icon. */
            }
            g2d.dispose(); //clean up
        }
    }
    ...
}

The first thing the paintComponent method does is check whether the IconDisplayer needs to be opaque and, if so, paint its background. If IconDisplayer were a subclass of something other than JComponent, we might omit this code and just call super.paintComponent.

The second part of the paintComponent method paints the main part of the component. It creates a copy of the Graphics object it was handed and casts it into a Graphics2D object. The typecast lets paintComponent use 2D features such as alpha compositing; the copy lets it avoid making changes to the passed-in Graphics object, which would have to be undone before returning.

Here's a complete listing of the code that paints the icon repeatedly:

if (icon != null) {
    Insets insets = getInsets();
    int iconWidth = icon.getIconWidth();
    int iconX = getWidth() - insets.right
                           - iconWidth;
    int iconY = insets.top;
    boolean faded = false;
    
    Graphics2D g2d = (Graphics2D)g.create();

    g.getClipBounds(clipRect);
    while (iconX >= insets.left) {
	iconRect.setBounds(iconX, iconY, iconWidth,
			   icon.getIconHeight());
	if (iconRect.intersects(clipRect)) {
            icon.paintIcon(this, g2d, iconX, iconY);
	}
        iconX -= (iconWidth + pad);
        if (!faded) { 
            g2d.setComposite(AlphaComposite.getInstance(
                AlphaComposite.SRC_OVER, 0.1f));
            faded = true;
        }
    }
    g2d.dispose(); //clean up
}

The first thing the code does is prepare for painting icons. It gets its border size, using the getInsets method. It then uses the insets, along with the component and icon sizes, when calculating where the first icon — the rightmost one — should be painted. The icon is an ImageIcon, so it's easy to get its size using methods such as getIconWidth, and it can paint itself using the Icon-defined method paintIcon. The code next gets a Graphics2D object so that it can use alpha compositing when painting some of the icons.

Next comes a bit of performance tuning. The code gets the clipping area, using the Graphics method getClipBounds. The clipping area is the part of the component that needs to be repainted. For example, if a window covering the right half of the component goes away, then only the right half of the component needs to be repainted. After getting the clipping area, the code checks whether this clipping area intersects with the icon's current painting area. If so, it paints the icon. If not, it saves a little time by not painting the icon.

After dealing with the first (rightmost) occurrence of the icon, the code invokes setComposite on the Graphics2D object, specifying parameters that make the subsequently painted icons appear to be only 10% opaque. You can find information on using alpha compositing in Compositing Graphics (in the Creating a GUI with JFC/Swing trail), a section in the 2D Graphics (in the Creating a GUI with JFC/Swing trail) trail.

After the paintComponent method paints all the icons, there's nothing for it to do but clean up. In this case, the cleanup is as simple as disposing of the Graphics2D object the method created.


Previous Page Lesson Contents Next Page Start of Tutorial > Start of Trail > Start of Lesson Search
Feedback Form

Copyright 1995-2005 Sun Microsystems, Inc. All rights reserved.