Search code examples
javaswingrepaintgraphics2dpaintcomponent

paintComponent draws other components on top of my drawing


I'm trying to build a simple paint tool. The mouseDrag events creates a new ellipse and causes my JPanel to repaint().

This works fine so far. However, if I press any button (or any other UI component) before firing the mouseDrag event for the first time, the button is painted in the upper left corner of my panel.

I have isolated the code into this test application:

import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Test extends JFrame
{
    public Test()
    {
        final JPanel paintPanel = new JPanel(){
            @Override
            protected void paintComponent(Graphics g)
            {
                Graphics2D g2d = (Graphics2D)g;
                g2d.setPaintMode();

                g2d.setStroke(new BasicStroke(1));
                g2d.fillRect(100, 100, 10, 10);
            }
        };

        paintPanel.setPreferredSize(new Dimension(300,300));
        paintPanel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e)
            {
                paintPanel.repaint();
            }
        });

        this.setLayout(new FlowLayout());

        this.add(paintPanel);
        this.add(new JButton("Dummy"));

        this.pack();
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }

    public static void main(String... args)
    {
        new Test();
    }
}

TestApp before clicking on the Panel TestApp after clicking on the Panel

A Screenshot for "seeing" the problem in my Main application


Solution

  • +1 to @MadProgrammer's answers.

    • You should have super.paintComponent(..) as the first call in your overriden paintComponent()
    • Do not extend JFrame unnecessarily
    • Create and minipulate Swing components via EDT
    • Dont call setPrefferedSize() rather override getPrefferedSize()

    Here is an example which incorporates my advice's and @MadProgrammer's:

    enter image description here

    import java.awt.BasicStroke;
    import java.awt.Dimension;
    import java.awt.FlowLayout;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Rectangle;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.util.ArrayList;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class Test {
    
        JFrame frame;
    
        public Test() {
            frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            final PaintPanel paintPanel = new PaintPanel();
    
            paintPanel.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    paintPanel.addRect(e.getX(), e.getY());
                }
            });
    
            frame.setLayout(new FlowLayout());
    
            frame.add(paintPanel);
            frame.add(new JButton("Dummy"));
    
            frame.pack();
            frame.setVisible(true);
        }
    
        public static void main(String... args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new Test();
                }
            });
        }
    }
    
    class PaintPanel extends JPanel {
    
        public PaintPanel() {
            addRect(100, 100);
        }
        ArrayList<Rectangle> rects = new ArrayList<>();
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            g2d.setPaintMode();
    
            for (Rectangle r : rects) {
                g2d.setStroke(new BasicStroke(1));
                g2d.fillRect(r.x, r.y, r.width, r.height);
            }
        }
    
        public void addRect(int x, int y) {
            rects.add(new Rectangle(x, y, 10, 10));
            repaint();
        }
    
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(300, 300);
        }
    }