8

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

Elian Kamal
  • 452
  • 6
  • 17
Reini
  • 1,221
  • 3
  • 19
  • 33

2 Answers2

8

+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);
    }
}
David Kroukamp
  • 34,930
  • 13
  • 72
  • 130
  • 1
    thanks for your advices, the snipped above was just a quick'n'dirty extract of my app's code. but always good to know the best practice! (my prof seems not to know -.-') (PS: Selected your answer as accepted one because of the excellent code example) – Reini Nov 13 '12 at 10:48
  • 1
    @Reini its a pleasure. Please see updated code for working snippet – David Kroukamp Nov 13 '12 at 10:50
7

You're not calling super.paintComponent.

The graphics context used for a paint cycle is shared between all the components begin painted, this means if you don't take care to clear it before painting onto, you will end up with what ever was painted before you.

One of the jobs of paintComponent is to prepare the graphics for painting

MadProgrammer
  • 323,026
  • 21
  • 204
  • 329
  • Yes. If I call paintComponent, the additional control is not painted. but also all ellipses i painted are lost, only the new ellipse is painted. – Reini Nov 13 '12 at 10:23
  • 1
    Paint is expected to repaint/regenerate all damaged areas on each paint cycle. This means you need some way to regenerate your display on each paint cycle. Have a look at http://stackoverflow.com/questions/12683533/drawing-a-rectangle-that-wont-disappear-in-next-paint/12683601#12683601 – MadProgrammer Nov 13 '12 at 10:25
  • 1
    PS: Marked Davids answer as accepted one because of the excellent example, but I got the clue with your link! ;-) – Reini Nov 13 '12 at 10:53
  • 1
    Yeah, that's what you get for answering on the iPad ;) – MadProgrammer Nov 13 '12 at 19:50