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 ButtonGroup
est mal conçu et propose une classe de remplacement JButtonGroup
, qui hérite ButtonGroup
et 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 ButtonGroup
classe 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 ButtonGroup
classe ne vous donne pas de référence au bouton actuellement sélectionné dans le groupe.
ButtonGroup
a une getSelection()
méthode qui renvoie le modèle du bouton sélectionné (en tant que ButtonModel
type), 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' ButtonModel
interface 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 ButtonGroup
documentation et voyez la getActionCommand()
méthode. Vous vous rappelez que si vous instanciez a JRadioButton
avec a String
pour 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 ButtonModel
retour 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 null
pour 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 AbstractButton
dé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 String
argument (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()
leButtonGroup
, qui renvoie unEnumeration
- Parcourez le
Enumeration
pour 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 ButtonGroup
la mise en œuvre de ce programme est fondamentalement erronée. ButtonGroup
conserve 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 ButtonModel
et un booléen. Si vous appelez setSelected()
a ButtonGroup
et passez le modèle d'un bouton qui ne fait pas partie du groupe et en true
tant qu'arguments, ce bouton devient alors sélectionné et tous les boutons du groupe deviennent désélectionnés. En d'autres termes,ButtonGroup
a 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 ButtonGroup
ne vérifie pas si la ButtonModel
ré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 ButtonGroup
documentation est encore plus intéressante:
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, ButtonGroup
c'est la supposition de quiconque. Le nom a probablement été inspiré par les Enumeration
mé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 ButtonGroup
et 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 ButtonGroup
sous - classe. Cependant, l' ButtonModel
interface nécessite une méthode setGroup()
qui prend un ButtonGroup
argument. À 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' ButtonModel
interface, notez l'absence d'une méthode appelée getGroup()
.
Un autre problème que je n'ai pas mentionné est que ButtonGroup
conserve en interne les références à ses boutons dans un fichier Vector
. Ainsi, il obtient inutilement la Vector
surcharge 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 buttons
est déclarée un Vector
type, et non List
comme 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 ButtonGroup
et 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 à ButtonGroup
la 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 JButtonGroup
les 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 JRadioButton
et 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 selectedButton
variable 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; iLes 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; iHereafter, 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 correspondingsetSelected()
inButtonGroup
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 theButtonModel
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()
andisSelected()
are the simplest and probably most useful methods of theJButtonGroup
class.getSelected()
returns a reference to the selected button, andisSelected()
overloads the method of the same name inButtonGroup
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 aButtonGroup
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()
inButtonGroup
not only has a totally uninspired name, but it returns anEnumeration
, which is an obsolete class you shouldn't use. The Collections Framework provides everything you need to avoid enumerations. This is howgetButtons()
returns an immutable list:public List getButtons() { return Collections.unmodifiableList(buttons); }Improve ButtonGroup
The
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.JButtonGroup
class offers a better and more convenient alternative to the SwingButtonGroup
class, while preserving all of the superclass's functionality.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 .