java - ejemplo - Strange JFrame Behavior
java swing actionlistener multiple buttons (3)
Tengo el siguiente programa que tiene un comportamiento muy extraño y no deseado cuando se ejecuta. Se supone que tiene dos botones, "Inicio" y "Detener", pero cuando hago clic en "Iniciar" aparece otro botón justo debajo de "Inicio". Aquí hay una pantalla de impresión de lo que estoy hablando:
¿Qué estoy haciendo mal y cómo soluciono este feo problema?
Aquí está el código:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
public class TwoButtonsTest {
JFrame frame;
Timer timer;
boolean isClicked;
public static void main(String[] args) {
TwoButtonsTest test = new TwoButtonsTest();
test.go();
}
public void go() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
JButton startButton = new JButton("Start");
startButton.addActionListener(new StartListener());
JButton stopButton = new JButton("Stop");
stopButton.addActionListener(new StopListener());
final DrawPanel myDraw = new DrawPanel();
frame.getContentPane().add(BorderLayout.CENTER, myDraw);
frame.getContentPane().add(BorderLayout.NORTH, startButton);
frame.getContentPane().add(BorderLayout.SOUTH, stopButton);
frame.setVisible(true);
timer = new Timer(50, new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
myDraw.repaint();
}
});
}
class StartListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//needs to be implemented
if(!isClicked) {
}
isClicked = true;
timer.start();
}
}
class StopListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//needs to be implemented
timer.stop();
isClicked = false;
}
}
class DrawPanel extends JPanel {
public void paintComponent(Graphics g) {
int red = (int)(Math.random()*256);
int blue = (int)(Math.random()*256);
int green = (int)(Math.random()*256);
g.setColor(new Color(red, blue, green));
Random rand = new Random();
// following 4 lines make sure the rect stays within the frame
int ht = rand.nextInt(getHeight());
int wd = rand.nextInt(getWidth());
int x = rand.nextInt(getWidth()-wd);
int y = rand.nextInt(getHeight()-ht);
g.fillRect(x,y,wd,ht);
}
} // close inner class
}
También estoy tratando de obtener el botón de Inicio para hacer dos cosas. Una de ellas es, por supuesto, iniciar la animación, pero cuando se presiona el botón Detener y presiono Iniciar de nuevo, quiero que limpie la pantalla, por así decirlo, y comience la animación de nuevo. ¿Algún consejo sobre eso?
Ojalá pudiera ofrecer una solución, pero hasta ahora no he encontrado ninguna. Puedo decirle que la raíz del "problema" radica en la forma en que está dibujando la sección Centro de su BorderLayout. Está anulando toda la función paintComponent () para este programa y teniendo todo lo que crea puesto en el centro de su BoarderLayout. En este caso, cada vez que hace clic en un botón, el programa llama al repintado para dibujar la imagen de un botón presionado, pero como también ha agregado CUALQUIERA de los objetos dibujados al panel central, también se dibuja allí. Como este repintado específico no especifica una ubicación, va en la esquina superior izquierda.
Solucioné el problema de tu botón en mi computadora con Windows XP invocando SwingUtilities.
He formateado tu código de Java.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TwoButtonsTest implements Runnable {
JFrame frame;
Timer timer;
boolean isClicked;
public static void main(String[] args) {
SwingUtilities.invokeLater(new TwoButtonsTest());
}
@Override
public void run() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
JButton startButton = new JButton("Start");
startButton.addActionListener(new StartListener());
JButton stopButton = new JButton("Stop");
stopButton.addActionListener(new StopListener());
final DrawPanel myDraw = new DrawPanel();
frame.getContentPane().add(BorderLayout.CENTER, myDraw);
frame.getContentPane().add(BorderLayout.NORTH, startButton);
frame.getContentPane().add(BorderLayout.SOUTH, stopButton);
frame.setVisible(true);
timer = new Timer(50, new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
myDraw.repaint();
}
});
}
class StartListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
// needs to be implemented
if (!isClicked) {
}
isClicked = true;
timer.start();
}
}
class StopListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
// needs to be implemented
timer.stop();
isClicked = false;
}
}
class DrawPanel extends JPanel {
@Override
public void paintComponent(Graphics g) {
int red = (int) (Math.random() * 256);
int blue = (int) (Math.random() * 256);
int green = (int) (Math.random() * 256);
g.setColor(new Color(red, blue, green));
Random rand = new Random();
// following 4 lines make sure the rect stays within the frame
int ht = rand.nextInt(getHeight());
int wd = rand.nextInt(getWidth());
int x = rand.nextInt(getWidth() - wd);
int y = rand.nextInt(getHeight() - ht);
g.fillRect(x, y, wd, ht);
}
} // close inner class
}
Para limpiar la pantalla cuando presiona el botón Inicio, tendrá que agregar algunos métodos a su clase DrawPanel.
Aquí hay una manera de hacerlo.
class DrawPanel extends JPanel {
protected boolean eraseCanvas;
public void setEraseCanvas(boolean eraseCanvas) {
this.eraseCanvas = eraseCanvas;
}
@Override
public void paintComponent(Graphics g) {
if (eraseCanvas) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
} else {
int red = (int) (Math.random() * 256);
int blue = (int) (Math.random() * 256);
int green = (int) (Math.random() * 256);
g.setColor(new Color(red, blue, green));
Random rand = new Random();
// following 4 lines make sure the rect stays within the frame
int ht = rand.nextInt(getHeight());
int wd = rand.nextInt(getWidth());
int x = rand.nextInt(getWidth() - wd);
int y = rand.nextInt(getHeight() - ht);
g.fillRect(x, y, wd, ht);
}
}
} // close inner class
No debe llamar a super.paintComponent(Graphics g)
en el método paintComponent(..)
que debe paintComponent(..)
para respetar la cadena de pintura y, por lo tanto, la pintura de otros componentes.
Esta llamada también debería ser la primera llamada dentro del método:
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//do painting here
}
Podría surgir una prueba de que los dibujos no son persistentes. Debe tener una forma de almacenar dibujos y volver a dibujar cada vez. El más común es un ArrayList
que contendrá los objetos que se dibujarán (por lo tanto, puede agregar a la lista eliminar etc.), lo que haría es iterar sobre la lista y volver a dibujar cada objeto en paintComponent
. Vea mi respuesta aquí para un ejemplo.
También recuerde crear y manipular componentes Swing en el hilo de envío de eventos :
SwingUtilities.invokeLater(new Runnable() { @Override public void run() { //create UI and components here } });
No
setSize(..)
enJFrame
sustituya agetPreferredSize()
deJPanel
y devuelva una altura adecuada que se ajuste a todos los componentes, que llame aJFrame#pack()
antes de configurarJFrame
(pero después de agregar todos los componentes).No hay necesidad de
getContentPane().add(..)
partir de Java 6+add(..)
predeterminado en contentPaneNo vuelva a declarar
Random
es decir,Random r=new Random()
cada vez que sepaintComponent
ya que esto hará que las distribuciones de los valores sean menos aleatorias, en lugar de iniciar una vez cuando se crea la clase y llamar a métodos en la instancia
Aquí está el código fijo (con las correcciones anteriores implementadas):
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;
public class TwoButtonsTest {
JFrame frame;
Timer timer;
boolean isClicked;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TwoButtonsTest test = new TwoButtonsTest();
test.go();
}
});
}
final DrawPanel myDraw = new DrawPanel();
public void go() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton startButton = new JButton("Start");
startButton.addActionListener(new StartListener());
JButton stopButton = new JButton("Stop");
stopButton.addActionListener(new StopListener());
frame.add(myDraw, BorderLayout.CENTER);
frame.add(startButton, BorderLayout.NORTH);
frame.add(stopButton, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
timer = new Timer(50, new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
myDraw.repaint();
}
});
}
class StartListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
//needs to be implemented
if (!isClicked) {
}
myDraw.clearRects();
isClicked = true;
timer.start();
}
}
class StopListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//needs to be implemented
timer.stop();
isClicked = false;
}
}
class DrawPanel extends JPanel {
private ArrayList<MyRectangle> rects = new ArrayList<>();
private Random rand = new Random();
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
addRect();
for (MyRectangle r : rects) {
g.setColor(r.getColor());
g.fillRect(r.x, r.y, r.width, r.height);
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
public void clearRects() {
rects.clear();
}
public void addRect() {
// following 4 lines make sure the rect stays within the frame
int ht = rand.nextInt(getHeight());
int wd = rand.nextInt(getWidth());
int x = rand.nextInt(getWidth() - wd);
int y = rand.nextInt(getHeight() - ht);
int red = (int) (Math.random() * 256);
int blue = (int) (Math.random() * 256);
int green = (int) (Math.random() * 256);
rects.add(new MyRectangle(x, y, wd, ht, new Color(red, blue, green)));
}
} // close inner class
}
class MyRectangle extends Rectangle {
Color color;
public MyRectangle(int x, int y, int w, int h, Color c) {
super(x, y, w, h);
this.color = c;
}
public Color getColor() {
return color;
}
}