Drawing a Rectangle That Won't Disappear in Next Paint

Drawing a rectangle that won't disappear in next paint

  1. Don't override paint WITHOUT VERY, VERY good reason...use paintComponent instead
  2. Always call super.paintXxx, these methods do a lot in the background, failing to call super is only going to come back and haunt you.
  3. If you're using multiple panes as rectangles, make the MyPanel transparent.

Paint's are stateless. There is no connection between the last paint and the next. On each paint request you are expected to update the entire state.

Andrew's suggest of double buffering (or back buffering) is and excellent one, and I highly encourage you to have a look an implementing it.

In the mean time, I put this little example together...

Sample Image

Basically, you press and hold the mouse button and it will randomly add another rectangle to the panel every 40 milli-seconds (roughly 25 frames a second).

I got this up to a 1000 rects without any issue, was able to resize the window without and issue or obviously slow down...

public class MyPanel extends JPanel {

private List<MyRectangle> lstShapes;
private Timer populate;

public MyPanel() {

lstShapes = new ArrayList<MyRectangle>(25);

populate = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {

int x = (int) (Math.random() * getWidth());
int y = (int) (Math.random() * getHeight());
int width = (int) (Math.random() * (getWidth() / 4));
int height = (int) (Math.random() * (getHeight() / 4));

if (x + width > getWidth()) {
x = getWidth() - width;
}
if (y + height > getHeight()) {
y = getHeight() - height;
}

Color color = new Color(
(int) (Math.random() * 255),
(int) (Math.random() * 255),
(int) (Math.random() * 255));

lstShapes.add(new MyRectangle(x, y, width, height, color));
repaint();
}
});
populate.setInitialDelay(0);
populate.setRepeats(true);
populate.setCoalesce(true);

addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
populate.restart();
}

@Override
public void mouseReleased(MouseEvent e) {
populate.stop();
}
});

}

@Override
protected void paintComponent(Graphics g) {

super.paintComponent(g);

Graphics2D g2d = (Graphics2D) g;
for (MyRectangle rect : lstShapes) {
rect.paint(g2d);
}

FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(lstShapes.size());

g2d.setColor(getForeground());
g2d.drawString(text, getWidth() - fm.stringWidth(text), getHeight() - fm.getHeight() + fm.getAscent());

}

public class MyRectangle extends Rectangle {

private Color color;

public MyRectangle(int x, int y, int width, int height, Color color) {
super(x, y, width, height);
this.color = color;
}

public Color getColor() {
return color;
}

public void paint(Graphics2D g2d) {

g2d.setColor(getColor());
g2d.fill(this);

}
}
}

have a go, it's fun ;)

ps- I got to up to over 5000 rectangles before I noticed a slow down (I modified the code down to a 10 milli second delay and was adding 10 new rectangles per tick)

Paint rectangle each time a method is called

There are two common approaches

  1. You need to keep a list of Rectangles to paint and iterate through the list every time.
  2. Paint your Rectangle to a BufferedImage and paint the BufferedImage.

Check out Custom Painting Approaches for working examples of both of these approaches.

Drawing a shape on a JPanel which already uses the paint method

First two things that you should remember: Never override paint but paintComponent and call super.paintComponent in there so that borders and everything works as expected. Regarding why this is the case, reference this question: Difference between paint() and paintcomponent()?


Now to answer your question. Assuming you have an existing logic to determine in which square you want to draw your Ellipse (let's assume you have two Integers elX and elY that are the column and row of your square) you can simply go and draw it after you have finished drawing the board itself.

Imagine sample code like this:

@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);

// Draw the board
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
g.drawRect(j * squareSize, i * squareSize, squareSize, squareSize);
}
}

// Draw the ellipse at the correct location using half the size of a normal square.
g.drawOval(elX * squareSize + squareSize / 4, elY * squareSize + squareSize / 4, squareSize / 2 , squareSize / 2);
}

Now the final part of how to go about actually determining where to draw your ellipse.
A simple solution would be to add a MouseListener to your panel. And then in the mouseClicked method you calculate where you actually did click.

Could look like this:

this.addMouseListener(new MouseListener()
{

@Override
public void mouseClicked(MouseEvent e)
{
int column = e.getX() / squareSize;
int row = e.getY() / squareSize;

board[column][row] = 1;
}

[...] // add the other methods to override

}

Then you slightly adapt your paintComponent method with something like this:

for (int column = 0; column < width; ++column)
{
for (int row = 0; row < height; ++row)
{
if (board[column][row] == 1)
{
g.drawOval(column * squareSize + squareSize / 4, row * squareSize + squareSize / 4, squareSize / 2, squareSize / 2);
}
}
}

and now you draw an ellipse everywhere you click. You could also check if the clicked square already has 1 set as a value and reset it to 0 to have some toggle mechanism or increment it and draw different things based on the integer value... it's all up to you :)

Why doesn't repaint() in the draw() method work (doesn't call paint())?

Slowpoke answer.. it worked after changing Window to transparent JFrame and drawing Rectangle to adding Jpanel with black background like this:

public class BlackRectangle extends JPanel {

public BlackRectangle() {
new JPanel();
setBackground(Color.BLACK);
setVisible(true);
}

public void setNew(int x, int y, int width, int height) {
setBounds(x, y, width, height);
}

public void clearPanel() {
setBounds(0, 0, 0, 0);
}

}

public class FrameTransparent extends JFrame{

public FrameTransparent() {
new JFrame();
setAlwaysOnTop(true);
setUndecorated(true);
setBackground(new Color(0, 0, 0, 0));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(null);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
setSize(screenSize);
setLocationRelativeTo(null);
setVisible(true);
}
}

public class Main {

public static void main (String [] args){
new Main();
}
public Main(){
BlackRectangle blackRectangle = new BlackRectangle();
FrameTransparent frameTransparent = new FrameTransparent();
frameTransparent.add(blackRectangle);
blackRectangle.setNew(10, 70, 200, 200);
frameTransparent.revalidate();
frameTransparent.repaint();
}

}

Movement stuck when clicking buttons

Short answer for the first. Add:

 button1.setFocusable(false);
button2.setFocusable(false);
button3.setFocusable(false);
button4.setFocusable(false);
button5.setFocusable(false);
button6.setFocusable(false);
button7.setFocusable(false);
button8.setFocusable(false);
button9.setFocusable(false);
button10.setFocusable(false);

Java drawing rectangle upside down with varying heigth

Use

g.fillRect(x, y - h, 100, h);

instead.



Related Topics



Leave a reply



Submit