Question

Disclaimer: je suis un étudiant en CS et cette question est liée à un programme Java récemment attribué pour la programmation orientée objet. Bien que nous ayons fait quelques choses sur la console, c'est la première fois que nous travaillons avec une interface graphique et Swing ou Awt. On nous a donné du code qui a créé une fenêtre avec du texte et un bouton qui faisait pivoter différentes couleurs pour le texte. Nous avons ensuite été invités à modifier le programme pour créer des boutons radio pour les couleurs à la place, ce qui était également destiné à nous permettre de nous familiariser avec la recherche d’une API. J'ai déjà rendu mon travail et reçu de mon instructeur la permission de poster mon code ici.

Quel est le meilleur moyen d'implémenter les actions de bouton en Java? Après quelques manipulations, j'ai créé les boutons suivants:

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

Et des auditeurs d'action supplémentaires ...

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

Un module de remplacement avait déjà été créé pour la méthode actionPerformed afin de nous donner une idée de son utilisation. Toutefois, comme il n'y avait qu'un seul bouton dans le modèle, la manière d'implémenter plusieurs boutons n'était pas claire. J'ai essayé d'activer une chaîne, mais je me suis vite rendu compte que, comme une chaîne n'était pas un type primitif, je ne pouvais pas l'utiliser pour une instruction switch. J'aurais pu improviser avec une chaîne if-else, mais c'est ce que j'ai proposé à la place. Cela semble loin d’être élégant et il doit y avoir une meilleure solution. Si c'est le cas, c'est quoi? Est-il possible d'allumer une chaîne? Ou choisissez une action de manière plus évolutive?

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;
    }
}
Était-ce utile?

La solution

Au lieu d'écrire ceci:

resetButton.addActionListener(this);

Vous pouvez également écrire ceci:

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

Et au lieu d'écrire une seule grosse actionPerformed () pour toutes les actions, vous pouvez (et devez ensuite) écrire ceci:

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

Je ne sais pas s'il s'agit de la solution la plus élégante, mais au moins, vous n'avez plus ce gros si construit.

Autres conseils

Deux approches alternatives:

  1. Créer une nouvelle classe qui implémente l'interface Action et possède un champ Color et une méthode actionPerformed qui définit la couleur
  2. Maintenez un tableau de hachage des noms de commandes en instances de couleur et recherchez le nom de la commande dans la carte

Une approche assez convenable consiste à déclarer un une énumération dont les éléments correspondent à vos chaînes et activez valueOf (str) (l'exemple lié montre comment procéder avec une sécurité suffisante).

La raison pour éviter les classes internes anonymes est probablement parce que la classe n'a pas (encore) cette construction, même si cela pourrait être la meilleure solution.

Comme déjà suggéré, vous pouvez utiliser des classes internes anonymes pour implémenter l'interface ActionListener. Alternativement, vous n'avez pas besoin d'utiliser des classes internes anonymes, mais vous pouvez utiliser une simple classe imbriquée:

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

et ensuite ...

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

Pour quelle raison cette approche (ou toute approche impliquant des classes internes) est préférable à la mise en oeuvre d'ActionListener dans la classe externe, voir "Modèles de conception":

"Favoriser la" composition d'objet "par rapport à" l'héritage de classe "." (Gang of Four 1995: 20)

Le choix entre les classes internes anonymes et les classes internes nommées est en grande partie une question de style, mais je pense que cette version est plus facile à comprendre et plus claire lorsqu'il y a beaucoup d'actions.

Ergh. N'implémentez pas des masses d'interfaces non liées dans une méga classe. Au lieu de cela, utilisez des classes internes anonymes. Ils sont un peu verbeux, mais sont ce que vous voulez. Utilisez-en un pour chaque événement, vous n'aurez alors pas besoin d'une grande chaîne if-else. Je suggère de conserver suffisamment de code dans la classe interne pour décoder l'événement et d'appeler des méthodes qui ont un sens pour les objets cibles. De plus, vous pouvez paramétrer vos classes internes. Vous constaterez probablement que vous n’avez pas besoin de conserver des références aux widgets réels.

Dans votre exemple, vous semblez utiliser un composant JComponent en tant que JPanel. Il n'y a pas beaucoup de différence, mais utilisez JPanel pour collecter un bloc de widgets. De plus, il est peu probable que vous ayez besoin de le classer, alors ne le faites pas.

Ainsi, par exemple:

   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);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top