Thanks to some input from a few users, the AbstractComponentDecorator has been updated to fix a few visual and behavior artifacts.
Get the latest from Sourceforge SVN.
The decorator component allows you to apply arbitrary decorations to a component without affecting its layout or (necessarily) response to events.
This blog has many different examples of usage beyond simple decorations, including drag images and per-component input blocking.
Showing posts with label validation. Show all posts
Showing posts with label validation. Show all posts
Sunday, August 10, 2008
Monday, April 17, 2006
Decorator/Overpainter Update
I've updated the decorator to handle most LAF backgrounds. You can now modify the background on just about anything that relies on a parent to paint its background.
The selection marquee is also no longer clipped to its parent component.

Now you might say "I can just paint on my glass pane" and you'd be right except that:
* Your glass pane eats all user input events once it's visible unless you explicitly redirect them
* You glass pane doesn't know anything about what's under it
* It always paints *above* everything else
The AbstractComponentDecorator is a bit more flexible in that you can target all or part of a specific component or a specific area of your window.
The selection marquee is also no longer clipped to its parent component.
Now you might say "I can just paint on my glass pane" and you'd be right except that:
* Your glass pane eats all user input events once it's visible unless you explicitly redirect them
* You glass pane doesn't know anything about what's under it
* It always paints *above* everything else
The AbstractComponentDecorator is a bit more flexible in that you can target all or part of a specific component or a specific area of your window.
Saturday, April 08, 2006
Decorating/Overpainting Swing Components

Have you ever wanted to change the way an existing component appears, just a little, without having to go and rewrite it or subclass it?
The image at right demonstrates several implementations of an abstract base class which puts an arbitrary decoration over an existing component. This technique allows you to do any of the following:
- Put labels on a scrolled component which stay fixed relative to the scroll pane rather than the scrolled component (useful for labeling horizontal lines on a panned display).
- Put an icon badge anywhere on a component, such as a stop sign over invalid text in a text field
- Put tooltips on a component that you didn't write (like adding annotations to someone else's work).
- Dim the entire painted image to gray, or fade it into the background color to make the component look disabled.
- Change the appearance of component's background (gradients, stripes, or polka-dots).
- Apply animation effects to the component.
- Marching ants/Rubber band selection rectangle (xor is so Seventies).
- Highlight all or part of an existing component to indicate the effective target of a drop operation (changing the selection isn't always the best solution).
Note that the code for each of the following decorations consists of the painting logic and optionally positioning logic, which is all you should be concerned about.
The implementation for drawing a badge:
class Warning extends AbstractComponentDecorator { private final int SIZE = 16; public Warning(JTextField f) { super(f); } /** Position the badge at the right-most edge. */ public Rectangle getDecorationBounds() { Rectangle r = super.getDecorationBounds(); Insets insets = getComponent().getInsets(); r.x += r.width - SIZE - 1; r.y += (r.height - SIZE) / 2; if (insets != null) { r.x -= insets.right; } return r; } public void paint(Graphics graphics) { Rectangle r = getDecorationBounds(); Graphics2D g = (Graphics2D)graphics; GeneralPath triangle = new GeneralPath(); triangle.moveTo(r.x + SIZE/2, r.y); triangle.lineTo(r.x + SIZE-1, r.y + SIZE-1); triangle.lineTo(r.x, r.y + SIZE-1); triangle.closePath(); g.setColor(Color.yellow); g.fill(triangle); g.setColor(Color.black); g.draw(triangle); g.drawLine(r.x + SIZE/2, r.y + 3, r.x + SIZE/2, r.y + SIZE*3/4 - 2); g.drawLine(r.x + SIZE/2, r.y + SIZE*3/4+1, r.x + SIZE/2, r.y + SIZE - 4); } }The implementation for dimming:
class Dimmer extends AbstractComponentDecorator { public Dimmer(JComponent target) { super(target); } /** Paint using a transparent version of the bg color. */ public void paint(Graphics g) { Color bg = getComponent().getBackground(); g.setColor(new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 200)); Rectangle r = getDecorationBounds(); g.fillRect(r.x, r.y, r.width, r.height); } }The implementation for semi-scrolling labels:
private static final int SIZE = 1000; private static final int BLOCK = 20; private static final int LINE = 20; private static class Labeler extends AbstractComponentDecorator { public Labeler(JComponent target) { super(target); } /** Ensure the label stays at the left-most visible * edge of the component. */ public Rectangle getDecorationBounds() { Rectangle r = super.getDecorationBounds(); Rectangle visible = getComponent().getVisibleRect(); if (r.x < visible.x) r.x = visible.x; return r; } public void paint(Graphics g) { Rectangle r = getDecorationBounds(); for (int i=0;i < SIZE;i+= LINE) { g.drawString("label " + (i/LINE + 1), r.x, r.y + i + g.getFontMetrics().getAscent() + 2); } } }
This class started out as a way to highlight different drop targets in a tree or table, and a method for the Costello editor to highlight areas of a component to be captured as an image (without actually setting the target component's background). I've subsequently used it for other decorations as noted above.
The whole idea would be trivial if Swing provided a hook into a component's paint method. There is no such explicit hook, and no implicit ones either without getting into a lot of complexity (perhaps by writing your own repaint manager). Since writing a repaint manager seems totally orthogonal to decorating a single component, we take a different tack.
My first implementation was to simply add a sibling or child component into the hierarchy on top of the first (and indeed, that implementation is still in use by the Costello editor). Unfortunately, the normal component hierarchy doesn't really consider z ordering, so the results are not always consistent. You might get things painted properly 90% of the time, but occasionally have your decoration occluded by a scroll pane, or the fact that there's an extra component in the hierarchy causes the layout manager to occasionally freak out. So it's okay for a proof of concept, but kinda lame in practice.
There is, however, a Swing component that knows about z ordering. Most Swing components will reside somewhere below a JLayeredPane (see the javadoc for javax.swing.RootPaneContainer for details). This little component has the capability of painting things in layers, which is just the capability we're looking for. The JLayeredPane even has predefined layers and was explicitly designed for stacking components in a known Z order (it also has a sub-layer ordering referred to as "position", but this doesn't seem to work).
So the basic idea is that for any decoration, we establish a painting component as a child of the JLayeredPane which is positioned in a layer above the decorated component. The Swing painting mechanism will automatically ensure that our decoration gets painted whenever the decorated component is painted, and that the decoration happens after the target component is painted.
A few details to note:
- The decorator must maintain proper position and size with respect to the target component. If the decoration is in the lower right corner of the component, it should stay there if the component grows.
- The decorator must clip its painting according to how much of the target component is visible. If the component is partially obscured within a scroll pane, the decorator should be clipped in a similar fashion.
- The decorator needs to track the target component's visibility. If the component is on a tabbed pane and a different tab is displayed, the decoration needs to be hidden.
- Painting under the decorated component is a bit more tricky. The current implementation for decorating component backgrounds assumes the current LAF doesn't change how ComponentUI paints the default background (gtk/synth probably won't work well). I haven't really looked into this much.
- It's possible to composite with the existing graphics (so you could, for instance, paint only where there's already a blue pixel), but this capability varies by platform and VM version.
Full source for the demo and AbstractComponentDecorator class.
Subscribe to:
Comments (Atom)