I have started reading Performing custom paint with swing. I wanted to extend what I read on the tutorial and draw rectangle "live". Not just the rectangle to pop up and show on the panel, but I wanted visually see the Rectangle shape resize as I drag the mouse, and finally when I release the mouse I wanted it to appear on the JPanel. I have included below what I did so far with a research and reading. The problem is since I am trying to catch each Rectangualr2D(this is the class I want to use) shape with mouse drag it end up looking like having a black background shape. How can I accomplish my target? Thanks for suggestions as always.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
class RectTool {
public static void main(String args[]) {
JFrame frame = new JFrame();
frame.add(new DPanel());
frame.setSize(500,500);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
class DPanel extends JPanel {
ArrayList<Rectangle2D.Double> linesBasket = new ArrayList<Rectangle2D.Double>();
Rectangle2D.Double rect;
final static BasicStroke stroke = new BasicStroke(5.0f);
double p1d ;
double p2d;
public DPanel() {
setBorder(BorderFactory.createLineBorder(Color.black));
setBackground(Color.white);
addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e){
final double startPointX = e.getX(); ;
final double startPointY = e.getY();
//current
rect.setRect(startPointX,startPointY,p1d,p2d);
}
public void mouseReleased(MouseEvent e){
linesBasket.add(rect);
}
});
addMouseMotionListener(new MouseAdapter(){
public void mouseDragged(MouseEvent e){
p1d = e.getX();
p2d = e.getY();
rect.setRect(10,10,p1d,p2d);
rect.setRect(rect);
repaint();
}
});
}
public void repaintShapeBasket(Graphics g){
rect = new Rectangle2D.Double();
rect.setRect(rect);
Graphics2D g2 = (Graphics2D) g;
linesBasket.add(rect);
for (Rectangle2D.Double rect : linesBasket) {
g2.draw(rect);
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
repaintShapeBasket(g);
}
}
Related
I have used several java.awt.Rectangle, java.awt.Polygon, and java.awt.geom.Ellipse2D shapes together and I want to rotate them with eachother and I also want to have them keep their location on the JFrame. When I use g2D.rotate(Math.toRadians(rotation)), the shapes move on the JFrame and they are no longer close together. How can I make it so that all of the shapes keep their position relative to eachother and their position on the JFrame? Thank you in advance.
If you want to be able to rotate the shapes together and keep their position on the JFrame I would recommend that you use the form of g2D.rotate that uses x and y coordinates to rotate with. You should make a standard x and y coordinate to draw each shapes position from so that when you use these standard x and y coordinates to rotate from all of the shapes will rotate together. It would probably look something like this:
// imports
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.geom.Ellipse2D;
import java.awt.Polygon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JComponent;
public class GraphicsRotate extends JComponent implements ActionListener {
// JFrame and Container
JFrame frame = new JFrame("Graphics Rotate");
Container container = frame.getContentPane();
public int standardX = 100;
public int standardY = 100;
public int rotation = 0;
public static void main(String[] args) {
GraphicsRotate graphicsRotate = new GraphicsRotate();
graphicsRotate.setup();
}
public void setup() {
container.setBackground(Color.BLACK);
container.add(this);
KeyListener kl = new KeyListener() {
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if (code == KeyEvent.VK_LEFT) {
rotation -= 10;
repaint();
}
if (code == KeyEvent.VK_RIGHT) {
rotation += 10;
repaint();
}
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
};
frame.setSize(500, 500);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addKeyListener(kl);
frame.setFocusable(true);
frame.requestFocus();
frame.setVisible(true);
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g.setColor(Color.GREEN);
g2.rotate(Math.toRadians(rotation), standardX, standardY);
Rectangle rect = new Rectangle(standardX + 10, standardY + 10, 5,
5);
Ellipse2D ellipse = new Ellipse2D.Double(standardX + 13, standardY
+ 5, 10, 10);
g2.fill(ellipse);
g2.fill(rect);
}
#Override
public void actionPerformed(ActionEvent e) {
}
}
This could be implemented on a much larger scale, and if you wanted the shapes to rotate around the center of the larger shape that you have made with them, then you could do the necessary calculations to figure out the center and do something like g2.rotate(Math.toRadians(rotation), standardX + middleX, standardY + middleY).
I hope that this answered your question.
It's really important to remember, transformations are compounding. So you can't simply apply a new rotation to the Graphics context for each object, instead, you need to reset the state between transformations
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public final class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private List<Shape> shapes = new ArrayList<>(4);
private double angle = 0;
public TestPane() {
shapes.add(new Rectangle2D.Double(50, 50, 100, 100));
shapes.add(new Rectangle2D.Double(250, 50, 100, 100));
shapes.add(new Rectangle2D.Double(50, 250, 100, 100));
shapes.add(new Rectangle2D.Double(250, 250, 100, 100));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
angle += 1;
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Shape shape : shapes) {
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = shape.getBounds();
int midx = bounds.x + (bounds.width / 2);
int midy = bounds.y + (bounds.height / 2);
g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(angle), midx, midy));
g2d.draw(shape);
g2d.dispose();
}
}
}
}
You could also transform the shape directly, for example...
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Shape shape : shapes) {
Rectangle bounds = shape.getBounds();
int midx = bounds.x + (bounds.width / 2);
int midy = bounds.y + (bounds.height / 2);
Path2D.Double rotatedShape = new Path2D.Double(shape, AffineTransform.getRotateInstance(Math.toRadians(angle), midx, midy));
g2d.draw(rotatedShape);
}
g2d.dispose();
}
I am attempting to have multiple JPanels that can "overlap", also allowing me to perform custom painting.
For this I am using a MainPanel, which extends JLayeredPane, and from what I can see, I have set bounds and index correctly.
The expected result, would be two rectangles painting at the same time to the screen.
The result I get, is flickering on one of the two OverlappingPanels, which I assume is from the RepaintManager fighting on which panel to draw (Found this here).
My question is, How can I properly overlap panels and retain painting capabilties, using Swing?
EDIT:
Code in question:
import javax.swing.*;
import java.awt.*;
public class Example extends JFrame {
public static class MainPanel extends JLayeredPane implements Runnable {
public OverlappingPanel1 overlappingPanel1;
public OverlappingPanel2 overlappingPanel2;
Thread mainThread;
public void startMainThread() {
mainThread = new Thread(this);
mainThread.start();
}
public MainPanel() {
this.setPreferredSize(new Dimension(1920,720));
this.setBackground(Color.BLACK);
this.setDoubleBuffered(true);
overlappingPanel1 = new OverlappingPanel1();
overlappingPanel2 = new OverlappingPanel2();
overlappingPanel1.setBounds(0,0,1920,720);
overlappingPanel2.setBounds(0,720/2,1920,720);
add(overlappingPanel1,1);
add(overlappingPanel2,2);
}
#Override
public void run() {
while(mainThread != null) {
overlappingPanel1.repaint();
overlappingPanel2.repaint();
}
}
}
public static class OverlappingPanel1 extends JPanel {
public OverlappingPanel1() {
setDoubleBuffered(true);
setPreferredSize(new Dimension(1920,720));
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D graphics2D = (Graphics2D) g;
graphics2D.fillRect(0,0,200,200);
}
}
public static class OverlappingPanel2 extends JPanel {
public OverlappingPanel2() {
setDoubleBuffered(true);
setPreferredSize(new Dimension(1920,720));
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D graphics2D = (Graphics2D) g;
graphics2D.fillRect(0,80,200,200);
}
}
public static void main(String[] args) {
JFrame window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
MainPanel mainPanel = new MainPanel();
window.add(mainPanel);
window.setBackground(Color.BLACK);
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
mainPanel.startMainThread();
}
}
So yes, a JLayeredPane would allow easy overlap of Swing components such as JPanels, and there are also layouts others have created that allow this, one called "overlay layout", but that's not what you want to for your currently stated problem.
Yours is an XY Problem type question where you ask "how do I solve X problem" when the best solution is not to solve it in this way, but rather to do Y, something completely different. Here, to paint multiple different images, your best solution is not to create and overlap heavier-weight Swing components such as JPanels, but rather to draw in one single JPanel and overlap sprite images. Otherwise you're just making things unnecessarily harder for yourself and your code than is needed.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class Example2 extends JPanel {
private static final int MY_WIDTH = 1600;
private static final int MY_HEIGHT = 720;
List<Rectangle> rectangles = new ArrayList<>();
public Example2() {
setPreferredSize(new Dimension(MY_WIDTH, MY_HEIGHT));
setBackground(Color.WHITE);
rectangles.add(new Rectangle(0, 0, 200, 200));
rectangles.add(new Rectangle(0, 80 + MY_HEIGHT / 2, 200, 200));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for (Rectangle rectangle : rectangles) {
g2.fill(rectangle);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Example2 example = new Example2();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(example);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
And yes, as suggested in comments, override paintComponent, not paint. This reduces the risk of unwanted side effects that might come from painting child components or borders, and also allows for automatic double-buffering for when you do animation.
Also, a while (true) loop is not a healthy construct within an event-driven GUI program, not as you've written it. If you need repeated actions in a Swing program (which you don't in your example, not yet), use a Swing Timer instead.
So doing this this way gives you good flexibility. For instance, if you wanted to modify the above program to allow addition of shapes on mouse click, it would be easy to do so:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class Example3 extends JPanel {
private static final int MY_WIDTH = 1600;
private static final int MY_HEIGHT = 720;
List<ColorShape> colorShapes = new ArrayList<>();
public Example3() {
setPreferredSize(new Dimension(MY_WIDTH, MY_HEIGHT));
setBackground(Color.WHITE);
addMouseListener(new MyMouse());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (ColorShape colorShape : colorShapes) {
colorShape.draw(g2);
}
}
private class MyMouse extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
// create a random color
float hue = (float) Math.random();
float saturation = 1f;
float brightness = (float) (0.5 * Math.random() + 0.5);
Color color = Color.getHSBColor(hue, saturation, brightness);
// create a new ColorShape, add to list, and repaint:
colorShapes.add(new ColorShape(e.getPoint(), color));
repaint();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Example3 example = new Example3();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(example);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
class ColorShape {
private int width = 80;
private Point location;
private Color color;
private Shape shape;
public ColorShape(Point location, Color color) {
this.location = location;
this.color = color;
int x = location.x - width / 2;
int y = location.y - width / 2;
shape = new Ellipse2D.Double(x, y, width, width);
}
public void draw(Graphics2D g2) {
g2.setColor(color);
g2.fill(shape);
}
public Point getLocation() {
return location;
}
}
The last two parameteres in setBounds(int x,int y, int width, int height) are the width and height of your panel. In your case, these are the dimensions of your rectangle , thus you should set them to 200, as below:
overlappingPanel1.setBounds(0,0,200,200);
overlappingPanel2.setBounds(0,720/2,200,200);
Also, remove setPreferredSize(new Dimension(1920,720)); in the OverlappingPanel1 and OverlappingPanel2 classes, as they are not needed.
It shows the line without jpanel on jframe, but it doesn't when I add it to jpanel. I've tried setting the layout manager of jpanel to null but no result. I want to use JComponents for drawing lines because I want them clickable.
Main.java file:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
//Parent Panel
JPanel panel = new JPanel();
panel.setBackground(Color.YELLOW);
panel.setLayout(null);
//Add Line To Panel
Line line = new Line(new Point2D.Double(20,20), new Point2D.Double(180,180));
panel.add(line);
panel.repaint();
frame.add(panel);
frame.setVisible(true);
}
}
class Line extends JComponent {
private final Point2D start, end;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLUE);
g2.setStroke(new BasicStroke(2.0F));
g2.draw(new Line2D.Double(start,end));
}
public Line( Point2D start, Point2D end){
this.start = start;
this.end = end;
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("mouse clicked");
}
});
}
}
It shows the line without jpanel on jframe, but it doesn't when I add it to jpanel
Swing components are responsible for determining their own preferred size.
When you add a component to a panel, the layout manager will then set the size/location of the component based on the rules of the layout manager.
When you add a component to the frame you really add it to the content pane of the frame which is a Jpanel which uses a BorderLayout by default. So the component is sized to fill the space available in the frame.
panel.setLayout(null);
You then added the component to a panel with a null layout. Now you are responsible for setting the size/location of the component. If you don't the size is (0, 0) so there is nothing to paint.
You should override the getPreferredSize() method of your class to return the preferred size of the component. Then layout managers can do their job.
If you really need a null layout, then the size of the component should be set in the application code, not it the Line class itself.
But now my line has a big container that listens for any clicks,
If you want hit detection then you override the contains(...) method.
Here is a basic example implementing the above suggestions:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.Rectangle;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Line extends JComponent
{
private Line2D.Double line;
public Line( Point2D start, Point2D end)
{
line = new Line2D.Double(start, end);
addMouseListener(new MouseAdapter()
{
#Override
public void mouseClicked(MouseEvent e)
{
System.out.println("mouse clicked");
}
});
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor( Color.BLUE );
g2.setStroke( new BasicStroke(2.0F) );
g2.draw( line );
}
#Override
public Dimension getPreferredSize()
{
Rectangle bounds = line.getBounds();
int width = bounds.x + bounds.width;
int height = bounds.y + bounds.height;
return new Dimension(width, height);
}
#Override
public boolean contains(int x, int y)
{
double distance = line.ptSegDist( new Point2D.Double(x, y) );
return distance < 2;
}
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
//Parent Panel
JPanel panel = new JPanel();
panel.setBackground(Color.YELLOW);
//Add Line To Panel
Line line = new Line(new Point2D.Double(20,20), new Point2D.Double(180,180));
panel.add(line);
panel.repaint();
frame.add(panel);
frame.setVisible(true);
}
}
Add custom size in Line constructor.
public Line( Point2D start, Point2D end){ ...
this.setSize(200, 200); }
Updated to fit also with painted Graph
Advice to change from JComponent to JPanel in order to see background
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
//Parent Panel
JPanel panel = new JPanel();
panel.setSize(300,300);
frame.add(panel);
panel.setBackground(Color.YELLOW);
panel.setLayout(null);
//Add Line To Panel
Line line = new Line(new Point2D.Double(20,20), new Point2D.Double(180,180));
panel.add(line);
frame.setVisible(true);
}
}
class Line extends JPanel {
private final Point2D start, end;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setBackground(Color.RED);
g2.setColor(Color.BLUE);
g2.setStroke(new BasicStroke(2.0F));
g2.draw(new Line2D.Double(start,end));
Rectangle r = g2.getClipBounds();
System.out.println(r.x+":"+r.y);
}
public Line( Point2D start, Point2D end){
this.start = start;
this.end = end;
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("mouse clicked at "+e.getX()+":"+e.getY());
}
});
int max_x = (int) Math.max(start.getX(), end.getX());
int max_y = (int) Math.max(start.getY(), end.getY());
System.out.println("max x="+max_y+",y="+max_y);
setSize(max_x,max_y);
setVisible(true);
setBackground(Color.GREEN);
}
}
Note: Only inside_green clicks allowed !
I'm trying to make a simple game in java using the swing engine. However currently I am having issues with lag. This simple code draws a circle and moves it from the top left corner of the frame to the bottom right corner, but it lags a lot. While my pc is somewhat old, i3 4gb ram, I think it should manage to perform this without lag?
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.Timer;
public class Game extends JPanel implements ActionListener {
public Timer timer;
int x = 0;
int y = 0;
public Game() {
timer = new Timer(20, this);
timer.start();
}
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.fillOval(x, y, 15, 15);
}
public void actionPerformed(ActionEvent e) {
x++;
y++;
repaint();
}
public static void main(String[] args) throws InterruptedException {
JFrame frame = new JFrame("test");
Game game = new Game();
frame.add(game);
frame.setSize(600, 600);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
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();
}
}
A Screenshot for "seeing" the problem in my Main application
+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:
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);
}
}
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