The Source for Java Technology Collaboration
User: Password:



Alexander Potochkin

Alexander Potochkin's Blog

Crazy JButton painters

Posted by alexfromsun on October 05, 2006 at 10:51 AM | Comments (6)

Inspired by some latest blogs I decided to find the most hacky and crazy method to paint on a component
I chose JButton as the most well-known component to play with

The goal is to implement some custom painting to a button without subclassing it and with no custom UI delegate

Custom component

As you probably know all Swing components are Containers, that means that you can add any children components to them. However not all Swing components support it, e.g. it usually doesn't make sense to add children to a components like JSlider or JProgressBar

JButton is not supposed to be used as a Container but we certainly can try:

JButton button = new JButton("I am a JButton");         
// JButton's layout is null by default 
button.setLayout(new FlowLayout()); 
         
button.add(new JButton("Surprise!"));

It is the clue to the first trick, you can add a translusent component to the button to make it looks different

button.setLayout(new BorderLayout()); 
button.add(new ComponentPainter()); 
                     
button.revalidate(); 
button.repaint(); 
.                     
.

class ComponentPainter extends JPanel { 
    protected void paintComponent(Graphics g) { 
        Graphics2D g2 = (Graphics2D) g.create(); 
        // It enables painting outside the component's border 
        g2.setClip(null); 
        // Make it translucent 
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); 
        // Take button's insets into account 
        Insets insets = button.getInsets(); 
        // Custom painting 
        g2.setColor(Color.ORANGE); 
        g2.fillOval(-insets.left, -insets.top, button.getWidth(), button.getHeight()); 
        g2.setColor(Color.GRAY); 
        g2.drawOval(-insets.left, -insets.top, button.getWidth(), button.getHeight()); 
        g2.dispose(); 
    } 
}

Unusual border

Borders in Swing are used to paint decorations around a Component,
but we are going to break the rules and create a Border instance to paint to the whole component's area

class BorderPainter extends AbstractBorder { 
    
    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { 
        Graphics2D g2 = (Graphics2D) g.create(); 
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); 
        g2.setPaint(new GradientPaint(0, 10, Color.BLACK, 10, 10, Color.RED, true)); 
        // We are free to paint wherever we want :-) 
        g2.fillRect(0, 0, button.getWidth(), button.getHeight()); 
    } 
}

The only problem with this approach is that a component can have only one border, and new border will replace the old one.
javax.swing.border.CompoundBorder will help us to compose the existing border with the additional one:

Border oldBorder = button.getBorder(); 
button.setBorder(new CompoundBorder(oldBorder, new BorderPainter()));

Misuse of Icon

The last dirty trick involves javax.swing.Icon

it has a paintIcon() method with suggested coordinates as parameters, but as you can guess we are not going to use them.
There might be the same problem as we already mentioned for Borders: you can set only one icon per a button

But it is not a problem for a real hacker :-)

Icon oldIcon = button.getIcon(); 
button.setIcon(new IconPainter(oldIcon)); 
. 
.
class IconPainter implements Icon { 
    private final Icon delegateeIcon; 
 
    public IconPainter(Icon innerIcon) { 
        this.delegateeIcon = innerIcon; 
    } 
 
    public int getIconWidth() { 
        if (delegateeIcon != null) { 
            return delegateeIcon.getIconWidth(); 
        } 
        return 0; 
    } 
 
    public int getIconHeight() { 
        if (delegateeIcon != null) { 
            return delegateeIcon.getIconHeight(); 
        } 
        return 0; 
    } 
 
    public void paintIcon(Component c, Graphics g, int x, int y) { 
        if (delegateeIcon != null) { 
            delegateeIcon.paintIcon(c, g, x, y); 
        } 
        Graphics2D g2 = (Graphics2D) g.create(); 
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); 
        g2.setPaint(new GradientPaint(10, 0, Color.RED, 10, 10, Color.BLUE, true)); 
        // It is a special icon, it is not interesed in x, y and icon's size  
        g2.fillRect(0, 0, button.getWidth(), button.getHeight()); 
    } 
}

Demo

There is a demo with all this crazy stuff implemented

I have finally found the modern and fresh look for my buttons !

Source code

Conclusion

Despite the fact the provided tricks work
please consider this blog as a joke

I hope no one takes it seriously and starts using something like described "IconPainter"

Have a nice day
alexp
;-)


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • I used the same approach to replace a button's 2d icon by a 3d one a while ago: http://www.jroller.com/page/gfx/?anchor=a_swing_button_with_3d

    I simply used the JButton as a container and .add()ed the 3d stuff :)

    Posted by: gfx on October 05, 2006 at 11:19 AM

  • heh, you may have succeeded in your quest to find the craziest way to implement a painter. :D The only thing nuttier may be to use a crayon and doodle stuff over where the component may be situated. Im starting to think this Painter business may be addicting.

    BH

    Posted by: leouser on October 05, 2006 at 02:40 PM

  • leouser: Check out Tejina by Incors. They have the crayon in the UI :)

    Posted by: gfx on October 05, 2006 at 02:48 PM

  • Romain - page no longer available. I wonder what's going on with Incors...

    Posted by: kirillcool on October 05, 2006 at 03:51 PM

  • There is another way to set a painter for a swing component, it requires a little reflection and use of the ComponentUI.

    For a given component you can sublass the UI delegate and override the paint method. Normally this would require re-implmenting lots of methods, but the trick is not to call the setUI method as it uninstalls all the listeners and so on. Instead the 'ui' field of JComponent is set. In the paint method one then calls the original UI delegate to do the painting and then the custom painting.
    For example to draw a cross over a button


    public class XButtonUI extends ButtonUI
    {
    ButtonUI ui;

    public void setup( JButton c )
    {
    ui = (ButtonUI)XComponentUiHelper.setUI( (JComponent)c, this );
    }

    // Paints an X over the button.
    public void paint(Graphics g, JComponent c)
    {
    // Invoke the original UI delegate
    ui.paint( g, c );
    ComponentUI ui2 = UIManager.getUI( c );

    // Now do the custom painting
    Rectangle r = c.getBounds();
    g.setColor( Color.red );
    g.drawLine( 0, 0, r.width, r.height );
    g.drawLine( 0, r.height, r.width, 0 );
    }
    }


    Then, to install the painter delegate once you have created you component just call:
    new XButtonUI().setup( submitBtn );

    The code for the XComponentUiHelper is part of a development version of the XUI framework, and the code for class can be found in the project's source repository.

    Posted by: luano on October 06, 2006 at 09:45 AM

  • Kirill, Tejina was just too much support and too little revenue, so we decided to no longer offer the product. We are concentrating on two quite large projects at the moment, and we should urgently push out Alloy 1.5 and 1.6. So all in all, don't worry about INCORS, we are alive and kicking, but much is happening behind closed doors.

    Posted by: jansan on October 12, 2006 at 04:12 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds