Pregunta

Descargo de responsabilidad completo: soy un estudiante de CS, y esta pregunta está relacionada con un programa Java recientemente asignado para la programación orientada a objetos. Aunque hemos hecho algunas cosas de consola, esta es la primera vez que trabajamos con una GUI y Swing o Awt. Nos dieron un código que creó una ventana con algo de texto y un botón que rotó a través de diferentes colores para el texto. Luego nos pidieron que modificáramos el programa para crear botones de radio para los colores en su lugar & # 8212; esto también tenía la intención de darnos práctica para investigar una API. Ya entregué mi tarea y recibí permiso de mi instructor para publicar mi código aquí.

¿Cuál es la mejor manera de implementar acciones de botón en Java? Después de jugar un poco, creé los botones de esta manera:

class HelloComponent3 extends JComponent
    implements MouseMotionListener, ActionListener
{
    int messageX = 75, messageY= 175;

    String theMessage;
    String redString = "red", blueString = "blue", greenString = "green";
    String magentaString = "magenta", blackString = "black", resetString = "reset";

    JButton resetButton;
    JRadioButton redButton, blueButton, greenButton, magentaButton, blackButton;
    ButtonGroup colorButtons;

    public HelloComponent3(String message) {

    theMessage = message;

    //intialize the reset button
    resetButton = new JButton("Reset");
    resetButton.setActionCommand(resetString);
    resetButton.addActionListener(this);

    //intialize our radio buttons with actions and labels
    redButton = new JRadioButton("Red");
    redButton.setActionCommand(redString);
    ...

Y oyentes de acción añadidos ...

redButton.addActionListener(this);
blueButton.addActionListener(this);
...

Ya se creó un código auxiliar para el método actionPerformed para darnos una idea de cómo usarlo, pero como solo había un botón en la plantilla, no estaba claro cómo implementar varios botones. Intenté encender un String, pero rápidamente me di cuenta de que, dado que un String no es un tipo primitivo, no podía usarlo para una declaración de cambio. Podría haber improvisado con una cadena if-else, pero esto es lo que se me ocurrió en su lugar. Parece lejos de ser elegante, y debe haber una mejor manera. Si hay, ¿qué es? ¿Hay alguna manera de encender una cadena? ¿O elegir una acción de una manera más escalable?

public void actionPerformed(ActionEvent e){

    if (e.getActionCommand().equals(resetString)) {
        messageX = 75; messageY = 175;
        setForeground(Color.black);
        blackButton.setSelected(true);
        repaint();
        return;
    }

    if ( e.getActionCommand().equals(redString) ) {
        setForeground(Color.red);
        repaint();
        return;
    }

    if ( e.getActionCommand().equals(blueString) ) {
        setForeground(Color.blue);
        repaint();
        return;
    }

    if ( e.getActionCommand().equals(greenString) ) {
        setForeground(Color.green);
        repaint();
        return;
    }

    if ( e.getActionCommand().equals(magentaString) ) {
        setForeground(Color.magenta);
        repaint();
        return;
    }

    if ( e.getActionCommand().equals(blackString) ) {
        setForeground(Color.black);
        repaint();
        return;
    }
}
¿Fue útil?

Solución

En lugar de escribir esto:

resetButton.addActionListener(this);

También puedes escribir esto:

resetButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent evt) {
        resetButtonActionPerformed(evt);
    }
});

Y en lugar de escribir una gran actionPerformed () para todas las acciones, puede (y luego debe) escribir esto:

public void resetButtonActionPerformed(ActionEvent evt) {
    messageX = 75; messageY = 175;
    setForeground(Color.black);
    blackButton.setSelected(true);
    repaint();
}

No sé si esta es la solución más elegante, pero al menos ya no tienes esa gran construcción if.

Otros consejos

Dos enfoques alternativos:

  1. Cree una nueva clase que implemente la interfaz Action y tenga un campo Color y un método actionPerformed que establezca el color
  2. Mantenga un HashMap desde los nombres de los comandos hasta las instancias de Color y busque el nombre del comando en el mapa

Un enfoque lo suficientemente decente es declarar una enumeración cuyos elementos coinciden con sus cadenas y active valueOf (str) (el ejemplo vinculado muestra cómo hacer esto con bastante seguridad).

La razón para evitar clases internas anónimas es probablemente porque la clase no ha tenido esa construcción (todavía), aunque esa podría ser la mejor solución.

Como ya se sugirió, puede usar clases internas anónimas para implementar la interfaz ActionListener. Como alternativa, no tiene que usar clases internas anónimas, pero puede usar una clase anidada simple en su lugar:

resetButton = new JButton(new ResetAction());
redButton = new JButton(new ColorAction("Red", Color.red));

y luego ...

private class ResetAction extends AbstractAction {
    public ResetAction() {
        super("Reset");
    }

    public void actionPerformed(ActionEvent e) {
        messageX = 75; messageY = 175;
        setForeground(Color.black);
        blackButton.setSelected(true);
        repaint();
    }
}

private class ResetAction extends AbstractAction {
    private Color color;

    public ColorAction(String title, Color color) {
        super(title);
        this.color = color;
    }

    public void actionPerformed(ActionEvent e) {
        setForeground(color);
        repaint();
    }
}

Para saber por qué este enfoque, o cualquier enfoque que involucre clases internas, es mejor que implementar ActionListener en la clase externa, vea " Patrones de diseño " ;:

" Favorece 'composición de objeto' sobre 'herencia de clase'. " (Banda de cuatro 1995: 20)

Elegir entre clases internas anónimas y estas clases internas nombradas es en gran medida una cuestión de estilo, pero creo que esta versión es más fácil de entender y más clara cuando hay muchas acciones.

Ergh. No implemente masas de interfaces no relacionadas en una megaclase. En cambio, use clases internas anónimas. Son un poco detallados, pero son lo que quieres. Use uno para cada evento, entonces no necesitará una gran cadena if-else. Sugiero mantener suficiente código dentro de la clase interna para decodificar el evento y llamar a métodos que tengan sentido para los objetos de destino. Además, puedes parametrizar tus clases internas. Probablemente encontrará que no necesita mantener referencias a los widgets reales.

En su ejemplo, parece estar utilizando un JComponent como JPanel. No hay mucha diferencia, pero usa JPanel para recolectar un bloque de widgets. Además, es poco probable que sea necesario subclasificarlo, así que no lo haga.

Entonces, por ejemplo:

   addColorButton("Green" , Color.GREEN );
   addColorButton("Red"   , Color.RED   );
   addColorButton("Yellow", Color.YELLOW);
   addColorButton("Blue"  , Color.BLUE  );
   ...

private void addColorButton(String label, Color color) {
    JRadioButton button = new JRadioButton(label);
    button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent event) {
            target.setForeground(color);
            target.repaint();
        } 
    });
    colorGroup.add(button);
    panel.add(button);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top