Suggerimento Java 142: premere JButtonGroup

Swing ha molte classi utili che semplificano lo sviluppo dell'interfaccia utente grafica (GUI). Alcune di queste classi, tuttavia, non sono ben implementate. Un esempio di una tale classe è ButtonGroup. Questo articolo spiega perché ButtonGroupè progettato male e offre una classe sostitutiva JButtonGroup, che eredita ButtonGroupe risolve alcuni dei suoi problemi.

Nota: puoi scaricare il codice sorgente di questo articolo da Risorse.

ButtonGroup fori

Ecco uno scenario comune nello sviluppo di Swing GUI: si crea un modulo per raccogliere dati sugli elementi che qualcuno inserirà in un database o salverà in un file. Il modulo potrebbe contenere caselle di testo, caselle di controllo, pulsanti di opzione e altri widget. Puoi utilizzare la ButtonGroupclasse per raggruppare tutti i pulsanti di opzione che richiedono una selezione singola. Quando la progettazione del modulo è pronta, inizi a implementare i dati del modulo. Ti imbatti nel set di pulsanti di opzione e devi sapere quale pulsante nel gruppo è stato selezionato in modo da poter memorizzare le informazioni appropriate nel database o nel file. Adesso sei bloccato. Perché? La ButtonGroupclasse non fornisce un riferimento al pulsante attualmente selezionato nel gruppo.

ButtonGroupha un getSelection()metodo che restituisce il modello del pulsante selezionato (come ButtonModeltipo), non il pulsante stesso. Ora, questo potrebbe andare bene se si potesse ottenere il riferimento al pulsante dal suo modello, ma non è possibile. L' ButtonModelinterfaccia e le sue classi di implementazione non consentono di recuperare un riferimento a un pulsante dal suo modello. Allora cosa fai? Guardi la ButtonGroupdocumentazione e vedi il getActionCommand()metodo. Ricordate che se istanziate a JRadioButtoncon a Stringper il testo visualizzato accanto al pulsante, e poi chiamate getActionCommand()il pulsante, il testo nel costruttore ritorna. Potresti pensare di poter ancora procedere con il codice perché anche se non hai il riferimento al pulsante almeno hai il suo testo e conosci ancora il pulsante selezionato.

Ebbene, sorpresa! Il codice si interrompe in fase di esecuzione con un file NullPointerException. Perché? Perché getActionCommand()in ButtonModelcambio null. Se si scommette (come ho fatto io), che getActionCommand()produce lo stesso risultato sia chiamato sul pulsante o sul modello (che è il caso di molti altri metodi, ad esempio isSelected(), isEnabled()o getMnemonic()), hai perso. Se non chiami esplicitamente setActionCommand()il pulsante, non imposti il ​​comando di azione nel suo modello e il metodo getter restituisce nullper il modello. Tuttavia, il metodo getter fa tornare il testo del pulsante quando viene chiamato sul pulsante. Ecco il getActionCommand()metodo in AbstractButton, ereditato da tutte le classi di pulsanti in Swing:

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

Questa incoerenza nell'impostazione e nell'ottenere il comando di azione è inaccettabile. È possibile evitare questa situazione se setText()in AbstractButtonimposta il comando di azione del modello sul testo del pulsante quando il comando di azione è nullo. Dopotutto, a meno che non setActionCommand()venga chiamato esplicitamente con qualche Stringargomento (non nullo), il testo del pulsante è considerato il comando di azione dal pulsante stesso. Perché il modello dovrebbe comportarsi in modo diverso?

Quando il codice necessita di un riferimento al pulsante attualmente selezionato in ButtonGroup, è necessario seguire questi passaggi, nessuno dei quali comporta la chiamata getSelection():

  • Chiamata getElements()su ButtonGroup, che restituisce unEnumeration
  • Scorri Enumerationper ottenere un riferimento a ciascun pulsante
  • Chiama isSelected()ogni pulsante per determinare se è selezionato
  • Restituisce un riferimento al pulsante che ha restituito true
  • Oppure, se hai bisogno del comando di azione, chiama getActionCommand()il pulsante

Se ti sembrano molti passaggi solo per ottenere un riferimento a un pulsante, continua a leggere. Credo che l ButtonGroup'implementazione sia fondamentalmente sbagliata. ButtonGroupmantiene un riferimento al modello del pulsante selezionato quando dovrebbe effettivamente mantenere un riferimento al pulsante stesso. Inoltre, poiché getSelection()recupera il metodo del pulsante selezionato, potresti pensare che il metodo setter corrispondente sia setSelection(), ma non lo è: è setSelected(). Ora, setSelected()ha un grosso problema. I suoi argomenti sono un ButtonModele un booleano. Se si chiama setSelected()a ButtonGroupe si passa il modello di un pulsante che non fa parte del gruppo e truecome argomenti, quel pulsante viene selezionato e tutti i pulsanti nel gruppo diventano deselezionati. In altre parole,ButtonGroupha il potere di selezionare o deselezionare qualsiasi pulsante passato al suo metodo, anche se il pulsante non ha nulla a che fare con il gruppo. Questo comportamento si verifica perché setSelected()in ButtonGroupnon verifica se il ButtonModelriferimento ricevuto come argomento rappresenta un pulsante nel gruppo. E poiché il metodo impone una selezione singola, in realtà deseleziona i propri pulsanti per selezionarne uno non correlato al gruppo.

Questa disposizione nella ButtonGroupdocumentazione è ancora più interessante:

Non esiste alcun modo per disattivare un pulsante a livello di codice per cancellare il gruppo di pulsanti. Per dare l'aspetto di "nessuno selezionato", aggiungi un pulsante di opzione invisibile al gruppo e quindi seleziona quel pulsante a livello di programmazione per disattivare tutti i pulsanti di opzione visualizzati. Ad esempio, un normale pulsante con l'etichetta "nessuno" potrebbe essere cablato per selezionare il pulsante di opzione invisibile.

Beh, non proprio. Puoi usare qualsiasi pulsante, seduto ovunque nella tua applicazione, visibile o meno, e persino disabilitato. Sì, puoi anche utilizzare il gruppo di pulsanti per selezionare un pulsante disabilitato al di fuori del gruppo e deselezionerà comunque tutti i suoi pulsanti. Per ottenere riferimenti a tutti i pulsanti del gruppo, devi chiamare il ridicolo getElements(). ButtonGroupQualcuno ha a che fare con gli "elementi" . Il nome è stato probabilmente ispirato dai Enumerationmetodi della classe ( hasMoreElements()e nextElement()), ma getElements()chiaramente avrebbe dovuto essere nominato getButtons(). Un gruppo di pulsanti raggruppa pulsanti, non elementi.

Soluzione: JButtonGroup

Per tutti questi motivi ho voluto implementare una nuova classe che correggesse gli errori ButtonGroupe fornisse alcune funzionalità e comodità all'utente. Ho dovuto decidere se la classe dovesse essere una nuova classe o ereditare da ButtonGroup. Tutti gli argomenti precedenti suggeriscono di creare una nuova classe piuttosto che una ButtonGroupsottoclasse. Tuttavia, l' ButtonModelinterfaccia richiede un metodo setGroup()che accetta un ButtonGroupargomento. A meno che non fossi pronto a reimplementare anche i modelli di pulsanti, la mia unica opzione era quella di sottoclassare ButtonGroupe sovrascrivere la maggior parte dei suoi metodi. Parlando ButtonModeldell'interfaccia, nota l'assenza di un metodo chiamato getGroup().

Un altro problema che non ho menzionato è che ButtonGroupmantiene internamente i riferimenti ai suoi pulsanti in un file Vector. Pertanto, ottiene inutilmente l' Vectoroverhead del sincronizzato , quando dovrebbe usare un ArrayList, poiché la classe stessa non è thread-safe e Swing è comunque a thread singolo. Tuttavia, la variabile protetta buttonsè dichiarata un Vectortipo e non Listcome ci si potrebbe aspettare da un buon stile di programmazione. Pertanto, non ho potuto reimplementare la variabile come ArrayList; e poiché volevo chiamare super.add()e super.remove(), non potevo nascondere la variabile della superclasse. Quindi ho abbandonato la questione.

Propongo la classe JButtonGroup, in tono con la maggior parte dei nomi delle classi Swing. La classe sovrascrive la maggior parte dei metodi ButtonGroupe fornisce ulteriori metodi di convenienza. Mantiene un riferimento al pulsante attualmente selezionato, che è possibile recuperare con una semplice chiamata a getSelected(). Grazie alla ButtonGroupscarsa implementazione di, potrei nominare il mio metodo getSelected(), poiché getSelection()è il metodo che restituisce il modello a bottone.

Di seguito sono riportati JButtonGroupi metodi di.

Innanzitutto, ho apportato due modifiche al add()metodo: Se il pulsante da aggiungere è già nel gruppo, il metodo ritorna. Pertanto, non puoi aggiungere un pulsante a un gruppo più di una volta. Con ButtonGroup, puoi creare un JRadioButtone aggiungerlo 10 volte al gruppo. La chiamata getButtonCount()restituirà quindi 10. Ciò non dovrebbe accadere, quindi non consento riferimenti duplicati. Quindi, se il pulsante aggiunto è stato precedentemente selezionato, diventa il pulsante selezionato (questo è il comportamento predefinito in ButtonGroup, il che è ragionevole, quindi non l'ho sovrascritto). La selectedButtonvariabile è un riferimento al pulsante attualmente selezionato nel gruppo:

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

Il add()metodo sovraccarico aggiunge un'intera matrice di pulsanti al gruppo. È utile quando si memorizzano i riferimenti ai pulsanti in un array per l'elaborazione dei blocchi (ad esempio, impostare i bordi, aggiungere listener di azioni, ecc.):

public void add (pulsanti AbstractButton []) {if (buttons == null) return; for (int i = 0; i
   
    

I due metodi seguenti rimuovono un pulsante o una matrice di pulsanti dal gruppo:

public void remove (AbstractButton button) {if (button! = null) {if (selectedButton == button) selectedButton = null; super.remove (pulsante); }} public void remove (pulsanti AbstractButton []) {if (buttons == null) return; for (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 .