Domanda

Dichiarazione di non responsabilità completa: sono uno studente CS e questa domanda è correlata a un programma Java assegnato di recente per la programmazione orientata agli oggetti. Sebbene abbiamo fatto alcune cose sulla console, questa è la prima volta che lavoriamo con una GUI e Swing o Awt. Ci è stato dato del codice che creava una finestra con del testo e un pulsante che ruotava attraverso colori diversi per il testo. Ci è stato poi chiesto di modificare il programma per creare i pulsanti di opzione per i colori, anche questo ci ha permesso di esercitarci nella ricerca di un'API. Ho già consegnato il mio incarico e ho ricevuto l'autorizzazione dal mio istruttore per pubblicare il mio codice qui.

Qual è il modo migliore per implementare le azioni dei pulsanti in Java? Dopo aver giocherellato un po ', ho creato i pulsanti in questo modo:

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);
    ...

E aggiunti listener di azioni ...

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

È stato già creato uno stub per il metodo actionPerformed per darci un'idea su come usarlo, ma poiché nel modello era presente un solo pulsante, non era chiaro come implementare più pulsanti. Ho provato ad accendere una stringa, ma ho capito subito che, poiché una stringa non è un tipo primitivo, non potevo usarla per un'istruzione switch. Avrei potuto improvvisare con una catena if-else, ma questo è quello che mi è venuto in mente invece. Sembra tutt'altro che elegante e deve esserci un modo migliore. Se c'è, che cos'è? C'è un modo per accendere una stringa? O scegliere un'azione in modo più scalabile?

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;
    }
}
È stato utile?

Soluzione

Invece di scrivere questo:

resetButton.addActionListener(this);

Puoi anche scrivere questo:

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

E invece di scrivere una grande azionePerformed () per tutte le azioni, puoi (e quindi devi) scrivere questo:

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

Non so se questa sia la soluzione più elegante, ma almeno non hai più quel grande se costrutto.

Altri suggerimenti

Due approcci alternativi:

  1. Crea una nuova classe che implementa l'interfaccia Action e abbia un campo Color e un metodo actionPerformed che imposta il colore
  2. Gestisci una HashMap dai nomi dei comandi alle istanze di colore e cerca il nome del comando nella mappa

Un approccio abbastanza decente è dichiarare un enum i cui elementi corrispondono alle tue stringhe e attiva valueOf (str) (l'esempio collegato mostra come farlo con una buona dose di sicurezza).

Il motivo per evitare le classi interne anonime è probabilmente perché la classe non ha ancora avuto quel costrutto, anche se potrebbe essere la soluzione migliore.

Come già suggerito, è possibile utilizzare classi interne anonime per implementare l'interfaccia ActionListener. In alternativa, non è necessario utilizzare classi interne anonime, ma è invece possibile utilizzare una semplice classe nidificata:

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

e poi ...

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();
    }
}

Per quale motivo questo approccio - o qualsiasi approccio che coinvolge classi interne - è meglio dell'implementazione di ActionListener nella classe esterna, vedi " Design Patterns " ;:

" Favorisce 'composizione oggetto' piuttosto che 'ereditarietà di classe'. " (Gang of Four 1995: 20)

Scegliere tra classi interne anonime e queste classi interne nominate è in gran parte una questione di stile, ma penso che questa versione sia più facile da capire e più chiara quando ci sono molte azioni.

ergh. Non implementare masse di interfacce non correlate in una mega classe. Invece, usa le classi interne anaudiche. Sono un po 'prolissi, ma sono quello che vuoi. Usane uno per ogni evento, quindi non avrai bisogno di una grande catena if-else. Suggerisco di mantenere abbastanza codice all'interno della classe interna per decodificare l'evento e chiamare metodi che abbiano senso per gli oggetti target. Inoltre, puoi parametrizzare le tue classi interne. Probabilmente scoprirai che non è necessario mantenere i riferimenti ai widget reali in giro.

Nel tuo esempio sembra che tu stia usando un JComponent come JPanel. Non c'è molta differenza, ma usa JPanel per raccogliere un blocco di widget. Inoltre, è improbabile che sia necessario sottoclassarlo, quindi non farlo.

Ad esempio:

   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);
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top