First of all I leave an alert:
Always start the screen within the Event-Dispatch-Thread ,
because swing is not Thread-Safe , and the whole GUI needs to start in.
of this unique Thread. Nesta
answer better explains the
reason for this and possible problems that may occur. This Other
answer shows some
ways to start the application within this Thread.
As you are doing, you are delegating the drawing of squares to paintComponent
, but this method is called every time the component needs to be redrawn, even this method is invoked as soon as you instantiate your panel. For this reason already starts with a drawing done.
The solution I propose is as follows:
-
Using BufferedImage
to " store " the drawn figures, thus avoiding having to be stored in lists and then traversing loops.
-
Delegate the drawing of the squares to a separate method on the panel, leaving paintComponent
only to update the component with the BufferedImage
image.
Applying the above two suggestions, extending ActionListener
as you are doing becomes unnecessary.
In addition, I modified the button's action so that it calls the method of your panel that will draw a random rectangle whenever it is clicked.
With the modifications, the code is as follows (contains comments in the relevant parts):
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RandomSquares {
NovoPainel painel;
JFrame frame;
public static void main(String[] args) {
//aplicações swing devem iniciar SEMPRE desta forma
SwingUtilities.invokeLater(() -> {
RandomSquares rs = new RandomSquares();
rs.construirGUI();
});
}
public void construirGUI() {
frame = new JFrame("Squares");
painel = new NovoPainel();
JButton botao = new JButton("CRIAR");
//sempre que clicado, vai chamar o método que
//desenhará quadrados aleatórios no painel
botao.addActionListener(e -> {
painel.criarRetangulos();
});
frame.getContentPane().add(painel, BorderLayout.CENTER);
frame.getContentPane().add(botao, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(450, 450);
frame.setResizable(false);
frame.setVisible(true);
}
public class NovoPainel extends JPanel {
BufferedImage image;
public NovoPainel() {
setLayout(null);
}
public void criarRetangulos() {
//cria uma instancia de BufferedImage, se ela nao existir
if(image == null)
image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
int red = (int) (Math.random() * 256);
int green = (int) (Math.random() * 256);
int blue = (int) (Math.random() * 256);
int posicaoX = (int) (Math.random() * this.getWidth());
int posicaoY = (int) (Math.random() * this.getHeight());
int largura = (int) (Math.random() * this.getWidth() + 1);
int altura = (int) (Math.random() * this.getHeight() + 1);
//cria graficos da imagem para que possamos "desenhar" nela
Graphics2D g2 = image.createGraphics();
g2.setColor(new Color(red, green, blue));
g2.fillRect(posicaoX, posicaoY, largura, altura);
//dispensa os graficos, uma vez que já concluimos o desenho
g2.dispose();
//redesenha a tela com um novo quadrado pintado
repaint();
}
public void paintComponent(Graphics g) {
//esta chamada SEMPRE deve ser invocada
//antes de tudo ao reescrever o método paintComponent
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
}
}
Running:
Note that what causes the screen to update immediately each time you click the button is the call of repaint()
shortly after drawing a random rectangle, as it is this method that notifies the screen of changes, and it needs to be redrawn through paintComponent()
.