Astuce Java 142: pousser JButtonGroup

Swing a de nombreuses classes utiles qui facilitent le développement d'interface utilisateur graphique (GUI). Certaines de ces classes, cependant, ne sont pas bien implémentées. Un exemple d'une telle classe est ButtonGroup. Cet article explique pourquoi il ButtonGroupest mal conçu et propose une classe de remplacement JButtonGroup, qui hérite ButtonGroupet corrige certains de ses problèmes.

Remarque: vous pouvez télécharger le code source de cet article à partir de Resources.

ButtonGroup trous

Voici un scénario courant dans le développement de l'interface graphique Swing: vous créez un formulaire pour collecter des données sur les éléments que quelqu'un va entrer dans une base de données ou enregistrer dans un fichier. Le formulaire peut contenir des zones de texte, des cases à cocher, des boutons radio et d'autres widgets. Vous utilisez la ButtonGroupclasse pour regrouper tous les boutons radio qui nécessitent une sélection unique. Lorsque la conception du formulaire est prête, vous commencez à implémenter les données du formulaire. Vous rencontrez l'ensemble de boutons radio et vous devez savoir quel bouton du groupe a été sélectionné afin de pouvoir stocker les informations appropriées dans la base de données ou le fichier. Vous êtes maintenant coincé. Pourquoi? La ButtonGroupclasse ne vous donne pas de référence au bouton actuellement sélectionné dans le groupe.

ButtonGroupa une getSelection()méthode qui renvoie le modèle du bouton sélectionné (en tant que ButtonModeltype), pas le bouton lui-même. Maintenant, cela pourrait être bien si vous pouviez obtenir la référence du bouton à partir de son modèle, mais vous ne pouvez pas. L' ButtonModelinterface et ses classes d'implémentation ne vous permettent pas de récupérer une référence de bouton à partir de son modèle. Donc que fais-tu? Vous regardez la ButtonGroupdocumentation et voyez la getActionCommand()méthode. Vous vous rappelez que si vous instanciez a JRadioButtonavec a Stringpour le texte affiché à côté du bouton, puis que vous appelez getActionCommand()sur le bouton, le texte du constructeur retourne. Vous pourriez penser que vous pouvez toujours continuer avec le code parce que même si vous n'avez pas la référence de bouton, vous avez au moins son texte et vous connaissez toujours le bouton sélectionné.

Eh bien, surprise! Votre code est interrompu lors de l'exécution avec un fichier NullPointerException. Pourquoi? Parce getActionCommand()qu'en ButtonModelretour null. Si vous pariez (comme je l'ai fait) qui getActionCommand()produit le même résultat si appelé sur le bouton ou sur le modèle ( ce qui est le cas avec beaucoup d' autres méthodes, telles que isSelected(), isEnabled()ou getMnemonic()), vous avez perdu. Si vous n'appelez pas explicitement setActionCommand()le bouton, vous ne définissez pas la commande d'action dans son modèle et la méthode getter retourne nullpour le modèle. Cependant, la méthode de lecture ne renvoyer le texte du bouton lorsqu'il est appelé sur le bouton. Voici la getActionCommand()méthode in AbstractButton, héritée par toutes les classes de boutons dans Swing:

public String getActionCommand () {String ac = getModel (). getActionCommand (); if (ac == null) {ac = getText (); } return ac; }

Cette incohérence dans la configuration et l'obtention de la commande d'action est inacceptable. Vous pouvez éviter cette situation si setText()dans AbstractButtondéfinit la commande d'action du modèle sur le texte du bouton lorsque la commande d'action est nulle. Après tout, sauf s'il setActionCommand()est appelé explicitement avec un Stringargument (non nul), le texte du bouton est considéré comme la commande d'action par le bouton lui-même. Pourquoi le modèle devrait-il se comporter différemment?

Lorsque votre code a besoin d'une référence au bouton actuellement sélectionné dans le ButtonGroup, vous devez suivre ces étapes, dont aucune n'implique d'appeler getSelection():

  • Appelez getElements()le ButtonGroup, qui renvoie unEnumeration
  • Parcourez le Enumerationpour obtenir une référence à chaque bouton
  • Appelez isSelected()chaque bouton pour déterminer s'il est sélectionné
  • Renvoie une référence au bouton qui a renvoyé true
  • Ou, si vous avez besoin de la commande d'action, appelez getActionCommand()le bouton

Si cela ressemble à beaucoup d'étapes juste pour obtenir une référence de bouton, lisez la suite. Je pense que ButtonGroupla mise en œuvre de ce programme est fondamentalement erronée. ButtonGroupconserve une référence au modèle du bouton sélectionné alors qu'il doit en fait conserver une référence au bouton lui-même. De plus, comme getSelection()récupère la méthode du bouton sélectionné, vous pourriez penser que la méthode setter correspondante l'est setSelection(), mais ce n'est pas le cas: c'est setSelected(). Maintenant, setSelected()a un gros problème. Ses arguments sont a ButtonModelet un booléen. Si vous appelez setSelected()a ButtonGroupet passez le modèle d'un bouton qui ne fait pas partie du groupe et en truetant qu'arguments, ce bouton devient alors sélectionné et tous les boutons du groupe deviennent désélectionnés. En d'autres termes,ButtonGroupa le pouvoir de sélectionner ou désélectionner n'importe quel bouton passé à sa méthode, même si le bouton n'a rien à voir avec le groupe. Ce problème se produit car setSelected()dans ButtonGroupne vérifie pas si la ButtonModelréférence reçue en tant qu'argument représente un bouton dans le groupe. Et parce que la méthode impose une sélection unique, elle désélectionne en fait ses propres boutons pour en sélectionner un sans rapport avec le groupe.

Cette stipulation dans la ButtonGroupdocumentation est encore plus intéressante:

Il n'existe aucun moyen de désactiver un bouton par programme pour effacer le groupe de boutons. Pour donner l'apparence de «aucun sélectionné», ajoutez un bouton radio invisible au groupe, puis sélectionnez ce bouton par programme pour désactiver tous les boutons radio affichés. Par exemple, un bouton normal avec l'étiquette «aucun» pourrait être câblé pour sélectionner le bouton radio invisible.

Eh bien pas vraiment. Vous pouvez utiliser n'importe quel bouton, assis n'importe où dans votre application, visible ou non, et même désactivé. Oui, vous pouvez même utiliser le groupe de boutons pour sélectionner un bouton désactivé en dehors du groupe, et il désélectionnera toujours tous ses boutons. Pour obtenir des références à tous les boutons du groupe, vous devez appeler le ridicule getElements(). Ce que les «éléments» ont à voir, ButtonGroupc'est la supposition de quiconque. Le nom a probablement été inspiré par les Enumerationméthodes de la classe ( hasMoreElements()et nextElement()), mais getElements()aurait clairement dû être nommé getButtons(). Un groupe de boutons regroupe des boutons, pas des éléments.

Solution: JButtonGroup

Pour toutes ces raisons, je voulais implémenter une nouvelle classe qui corrigerait les erreurs ButtonGroupet fournirait des fonctionnalités et une commodité à l'utilisateur. J'ai dû décider si la classe devait être une nouvelle classe ou hériter de ButtonGroup. Tous les arguments précédents suggèrent de créer une nouvelle classe plutôt qu'une ButtonGroupsous - classe. Cependant, l' ButtonModelinterface nécessite une méthode setGroup()qui prend un ButtonGroupargument. À moins que je ne sois prêt à réimplémenter également les modèles de boutons, ma seule option était de sous ButtonGroup-classer et de remplacer la plupart de ses méthodes. En parlant d' ButtonModelinterface, notez l'absence d'une méthode appelée getGroup().

Un autre problème que je n'ai pas mentionné est que ButtonGroupconserve en interne les références à ses boutons dans un fichier Vector. Ainsi, il obtient inutilement la Vectorsurcharge de la synchronisation , quand il doit utiliser an ArrayList, puisque la classe elle-même n'est pas thread-safe et Swing est de toute façon à thread unique. Cependant, la variable protégée buttonsest déclarée un Vectortype, et non Listcomme vous pourriez vous attendre d'un bon style de programmation. Ainsi, je ne pouvais pas réimplémenter la variable en tant que ArrayList; et parce que je voulais appeler super.add()et super.remove(), je ne pouvais pas cacher la variable superclasse. J'ai donc abandonné le problème.

Je propose la classe JButtonGroup, dans le ton de la plupart des noms de classe Swing. La classe remplace la plupart des méthodes dans ButtonGroupet fournit des méthodes pratiques supplémentaires. Il garde une référence au bouton actuellement sélectionné, que vous pouvez récupérer avec un simple appel getSelected(). Grâce à ButtonGroupla mauvaise implémentation de, je pourrais nommer ma méthode getSelected(), puisque getSelection()c'est la méthode qui renvoie le modèle de bouton.

Voici JButtonGrouples méthodes de.

Tout d'abord, j'ai apporté deux modifications à la add()méthode: Si le bouton à ajouter est déjà dans le groupe, la méthode retourne. Ainsi, vous ne pouvez pas ajouter plusieurs fois un bouton à un groupe. Avec ButtonGroup, vous pouvez créer un JRadioButtonet l'ajouter 10 fois au groupe. L'appel getButtonCount()retournera alors 10. Cela ne devrait pas arriver, donc je n'autorise pas les références en double. Ensuite, si le bouton ajouté a été précédemment sélectionné, il devient le bouton sélectionné (c'est le comportement par défaut dans ButtonGroup, ce qui est raisonnable, donc je ne l'ai pas remplacé). La selectedButtonvariable est une référence au bouton actuellement sélectionné dans le groupe:

public void add (bouton AbstractButton) boutons.contains (bouton)) return; super.add (bouton); if (getSelection () == button.getModel ()) selectedButton = bouton;  

La add()méthode surchargée ajoute tout un tableau de boutons au groupe. C'est utile lorsque vous stockez des références de bouton dans un tableau pour le traitement de bloc (c'est-à-dire, définir des bordures, ajouter des écouteurs d'action, etc.):

public void add (AbstractButton [] boutons) {if (boutons == null) return; pour (int i = 0; i
   
    

Les deux méthodes suivantes suppriment un bouton ou un tableau de boutons du groupe:

public void remove (bouton AbstractButton) {if (bouton! = null) {if (bouton sélectionné == bouton) selectedButton = null; super.remove (bouton); }} public void remove (AbstractButton [] boutons) {if (boutons == null) return; pour (int i = 0; i
     
      

Hereafter, the first setSelected() method lets you set a button's selection state by passing the button reference instead of its model. The second method overrides the corresponding setSelected() in ButtonGroup to assure that the group can only select or unselect a button that belongs to the group:

public void setSelected(AbstractButton button, boolean selected) { if (button != null && buttons.contains(button)) { setSelected(button.getModel(), selected); if (getSelection() == button.getModel()) selectedButton = button; } } public void setSelected(ButtonModel model, boolean selected) { AbstractButton button = getButton(model); if (buttons.contains(button)) super.setSelected(model, selected); } 

The getButton() method retrieves a reference to the button whose model is given. setSelected() uses this method to retrieve the button to be selected given its model. If the model passed to the method belongs to a button outside the group, null is returned. This method should exist in the ButtonModel implementations, but unfortunately it does not:

public AbstractButton getButton(ButtonModel model) { Iterator it = buttons.iterator(); while (it.hasNext()) { AbstractButton ab = (AbstractButton)it.next(); if (ab.getModel() == model) return ab; } return null; } 

getSelected() and isSelected() are the simplest and probably most useful methods of the JButtonGroup class. getSelected() returns a reference to the selected button, and isSelected() overloads the method of the same name in ButtonGroup to take a button reference:

public AbstractButton getSelected() { return selectedButton; } public boolean isSelected(AbstractButton button) { return button == selectedButton; } 

This method checks whether a button is part of the group:

public boolean contains(AbstractButton button) { return buttons.contains(button); } 

You would expect a method named getButtons() in a ButtonGroup class. It returns an immutable list containing references to the buttons in the group. The immutable list prevents button addition or removal without going through the button group's methods. getElements() in ButtonGroup not only has a totally uninspired name, but it returns an Enumeration, which is an obsolete class you shouldn't use. The Collections Framework provides everything you need to avoid enumerations. This is how getButtons() returns an immutable list:

public List getButtons() { return Collections.unmodifiableList(buttons); } 

Improve ButtonGroup

The JButtonGroup class offers a better and more convenient alternative to the Swing ButtonGroup class, while preserving all of the superclass's functionality.

Daniel Tofan is as a postdoctoral associate in the Chemistry Department at State University of New York, Stony Brook. His work involves developing the core part of a course management system with application in chemistry. He is a Sun Certified Programmer for the Java 2 Platform and holds a PhD in chemistry.

Learn more about this topic

  • Download the source code that accompanies this article

    //images.techhive.com/downloads/idge/imported/article/jvw/2003/09/jw-javatip142.zip

  • Sun Microsystems' Java Foundation Classes homepage

    //java.sun.com/products/jfc/

  • Java 2 Platform, Standard Edition (J2SE) 1.4.2 API documentation

    //java.sun.com/j2se/1.4.2/docs/api/

  • ButtonGroup class

    //java.sun.com/j2se/1.4.2/docs/api/javax/swing/ButtonGroup.html

  • View all previous Java Tips and submit your own

    //www.javaworld.com/columns/jw-tips-index.shtml

  • Browse the AWT/Swing section of JavaWorld's Topical Index

    //www.javaworld.com/channel_content/jw-awt-index.shtml

  • Browse the Foundation Classes section of JavaWorld's Topical Index

    //www.javaworld.com/channel_content/jw-foundation-index.shtml

  • Browse the User Interface Design section of JavaWorld's Topical Index

    //www.javaworld.com/channel_content/jw-ui-index.shtml

  • Visit the JavaWorld Forum

    //www.javaworld.com/javaforums/ubbthreads.php?Cat=&C=2

  • Sign up for JavaWorld's free weekly email newsletters

    //www.javaworld.com/subscribe

This story, "Java Tip 142: Pushing JButtonGroup" was originally published by JavaWorld .