Search code examples
swingjpanelpaintcomponent

Draw custom stuff on top of opaque components in a JPanel


I have an JPanel populated with several opaque custom components. Now I would like to draw something on top of these components by overriding the paintComponent() method. My problem is that the painted stuff is placed behind the embedded components and, as they are opaque, is covered by them.

Is there any way to let the painting appear on top of the components?

Here's a short example of what I'm trying to do:

public class DrawOnTop {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame f = new JFrame("Draw on top");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.add(new MyPanel());
                f.pack();
                f.setVisible(true);
            }
        });
    }
}

class MyPanel extends JPanel {

    public MyPanel() {
        setLayout(new BorderLayout(3, 3));
        add(new JButton("Button 1"), BorderLayout.NORTH);
        add(new JButton("Button 2"), BorderLayout.CENTER);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.red);
        g.drawLine(0, 0, getVisibleRect().width, getVisibleRect().height);
    }
}

Solution

  • You were thinking along the right lines. Only problem was that you should have Overridden the paintChildren() method like in the code below. This is because the paintComponent() method is called as first and does the background etc painting of the component itself (the MyPanel), then is called paintBorders() and lastly the paintChildren() which paints all that is inside of the component calling it.

    public class DrawOnTop {
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    JFrame f = new JFrame("Draw on top");
                    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    f.add(new MyPanel());
                    f.pack();
                    f.setVisible(true);
                }
            });
        }
    }
    
    class MyPanel extends JPanel {
    
        public MyPanel() {
            setLayout(new BorderLayout(3, 3));
            JButton b1 = new JButton("Button 1");
            MouseListener ml = new MouseAdapter() {
    
                @Override
                public void mouseExited(MouseEvent e) {
                    super.mouseExited(e);
                    MyPanel.this.repaint();
                }
            };
            b1.addMouseListener(ml);
            JButton b2 = new JButton("Button 2");
            b2.addMouseListener(ml);
            add(b1, BorderLayout.NORTH);
            add(b2, BorderLayout.CENTER);
        }
    
        @Override
        protected void paintChildren(Graphics g) {
            super.paintChildren(g);
            g.setColor(Color.red);
            g.drawLine(0, 0, getVisibleRect().width, getVisibleRect().height);
        }
    }
    

    Important to notice, in the code sample, that I added also a MouseListener to repaint the panel when a mouse exits a button, otherwise the buttons would always stay over the line once mouse enters over one of them.

    But if you want to have a custom painting that is always on top of your components then I would suggest to use a glass pane. Examples of a glass pane use are provided here:

    1. Simple one.
    2. A more complex one.