The Source for Java Technology Collaboration
User: Password:



Joshua Marinacci

Joshua Marinacci's Blog

Swing Hack 3: Overlay Graphics

Posted by joshy on September 26, 2003 at 01:49 PM | Comments (5)

It's Friday so I thought I'd do another Swing hack:

When I'm doing really complicated Swing layouts I often have trouble figuring out which component on screen matches the one in my code, especially if there are custom widgets or subclasses that look the same as normal ones (like formatted text fields) or that don't have easy to see borders. To assist in debugging these I created a component which draws a border around each component and prints the class on top of it.

This is really easy to do in Swing because you can create what's known as a glasspane. It's an invisible Swing component which sits on top of the rest of a frame's contents, intercepting events. It's typically used to block input for modal dialogs or progress bars, but you can use it to paint on top of the frame as well. Combined with alpha channel colors we can do nifty tricks.


public class DrawOverTest {

public static void main(String[] args) {
    JFrame frame = new JFrame("glass pane test");
    JPanel panel = new JPanel();
    panel.setLayout(new GridLayout(4,1));
    panel.add(new JButton("my button"));
    panel.add(new JLabel("my button"));
    panel.add(new JButton("my button"));
    frame.getContentPane().add(panel);
    frame.pack();
    frame.setVisible(true);


    LabelGlassPane glass = new LabelGlassPane(frame);
    frame.setGlassPane(glass);
    glass.setVisible(true);
}


}

class LabelGlassPane extends JComponent {
    public LabelGlassPane(JFrame frame) {
      this.frame = frame;
     //this.addMouseListener(new MouseAdapter() {         });
    }
    public JFrame frame;
    public void paint(Graphics g) {
        g.setColor(Color.red);
        Container root = frame.getContentPane();
        g.setColor(new Color(100,100,100,100));
        rPaint(root,g);
    }
    private void rPaint(Container cont, Graphics g) {
        for(int i=0; i<cont.getComponentCount(); i++) {
            Component comp = cont.getComponent(i);
            if(!(comp instanceof JPanel)) {
                int x = comp.getX();
                int y = comp.getY();
                int w = comp.getWidth();
                int h = comp.getHeight();
                g.drawRect(x+4,y+4,w-8,h-8);
                g.drawString(comp.getClass().getName(),x+10,y+20);
            }
            if(comp instanceof Container) {
                rPaint((Container)comp,g);
            }
        }
    }
}

As with my other hacks, the program doesn't know about the hack, meaning there are no subclasses or custom code. All you need is a reference to the frame and the glasspane takes care of the rest.

This glass pane doesn't block the IO even though the javadocs imply it should. It seems that if you don't set any event handlers then the events will pass through. If you add one, even if it's a no-op like the commented line above, then the events are blocked and you have to forward them manually. This seems kind of odd, if convenient. Adding an event handler should add behavior but not affect existing behavior.

Anyway, have a good weekend.


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

  • Nifty
    Joshua,

    this is a pretty nifty approach. It seems the simple ideas are the most impacting ones.
    Especially the fact, that you are not breaking and existing structures makes this hack a very cool one.
    -Norb

    Posted by: norb on September 30, 2003 at 04:30 AM

  • Re: Nifty
    Thanks. I always try to be non-invasive. Are there any other hacks you'd like to see?

    Posted by: joshy on October 03, 2003 at 05:51 PM

  • Suggested improvements
    This hack is quite cool. Thanks for posting it.

    I tried it with a large production application and found the following deficiencies:
    1. The menu bar isn't accounted for, so everything is shifted.
    2. Dense clusters of components (such as a toolbar) are completely unreadable.

    Thanks,
    Curt

    Posted by: coxcu on October 14, 2003 at 09:38 AM

  • Suggested improvements
    >2. Dense clusters of components (such as a toolbar) are completely unreadable.

    I agree so I have made my own LabelGlassPane modification so it only paints the components I want to see:

    In this version you have two new methods: setComponentsPainted and setClassesPainted. In the first one you pass the component you want to see painted, in the seconde one you pass the component classes you want to see painted

    If you don't use any setComponentsPainted nor setClassesPainted this class operates exactly equals than the older one.

    [CODE]
    //Original author Joshua Marinacci
    //Modified by Luis Valero

    import javax.swing.*;
    import java.awt.*;
    import java.util.*;

    public class LabelGlassPane extends JComponent {
    public LabelGlassPane(JFrame frame) {
    this.frame = frame;
    //this.addMouseListener(new MouseAdapter() { });
    }
    private HashSet componentPainted = null;
    private HashSet classesPainted = null;

    public void setComponentsPainted(Collection comp) {
    componentPainted = new HashSet(comp);
    }
    public void setClassesPainted(Collection comp) {
    classesPainted = new HashSet(comp);
    }

    public JFrame frame;
    public void paint(Graphics g) {
    g.setColor(Color.red);
    Container root = frame.getContentPane();
    g.setColor(new Color(100,100,100,100));
    rPaint(root,g);
    }

    private boolean shouldPaint(Component c) {
    boolean bShouldPaint = false;
    if (componentPainted==null && classesPainted == null) {
    bShouldPaint = !(c instanceof JPanel);
    } else {
    bShouldPaint =
    (componentPainted!=null && componentPainted.contains(c))
    || (classesPainted!=null && classesPainted.contains(c.getClass()));

    }
    return bShouldPaint;
    }


    private void rPaint(Container cont, Graphics g) {
    for(int i=0; i<cont.getComponentCount(); i++) {
    Component comp = cont.getComponent(i);
    //if(!(comp instanceof JPanel)) {
    if (shouldPaint(comp)) {
    int x = comp.getX();
    int y = comp.getY();
    int w = comp.getWidth();
    int h = comp.getHeight();
    g.drawRect(x+4,y+4,w-8,h-8);
    g.drawString(comp.getClass().getName(),x+10,y+20);
    }
    if(comp instanceof Container) {
    rPaint((Container)comp,g);
    }
    }
    }
    }
    [/CODE]

    I hope you find this modification usefull.

    Posted by: lvalero on January 23, 2004 at 02:11 AM

  • What about JSplitPanes
    I have tried the hack on the application we are currently developing, but alas with only some success. I do get overlayed frames and titles, but they are all cramped in the upper left corner of the screen. It is as if all the getX/getY-calls returns 0?? The application relies heavily on JSplitPanes. Could this explain the problem?

    Posted by: wmjepet on January 28, 2004 at 10:15 PM





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