Java-Tipp 142: JButtonGroup drücken

Swing verfügt über viele nützliche Klassen, die die Entwicklung grafischer Benutzeroberflächen (GUI) vereinfachen. Einige dieser Klassen sind jedoch nicht gut implementiert. Ein Beispiel für eine solche Klasse ist ButtonGroup. Dieser Artikel erklärt, warum er ButtonGroupschlecht gestaltet ist und bietet eine Ersatzklasse JButtonGroup, die ButtonGroupeinige ihrer Probleme erbt und behebt.

Hinweis: Sie können den Quellcode dieses Artikels von Resources herunterladen.

ButtonGroup Löcher

Hier ist ein häufiges Szenario bei der Entwicklung der Swing-GUI: Sie erstellen ein Formular, um Daten zu Elementen zu erfassen, die jemand in eine Datenbank eingibt oder in einer Datei speichert. Das Formular kann Textfelder, Kontrollkästchen, Optionsfelder und andere Widgets enthalten. Mit der ButtonGroupKlasse gruppieren Sie alle Optionsfelder, für die eine einzelne Auswahl erforderlich ist. Wenn das Formulardesign fertig ist, beginnen Sie mit der Implementierung der Formulardaten. Sie stoßen auf die Optionsfelder und müssen wissen, welche Schaltfläche in der Gruppe ausgewählt wurde, damit Sie die entsprechenden Informationen in der Datenbank oder Datei speichern können. Du steckst jetzt fest. Warum? Die ButtonGroupKlasse gibt Ihnen keinen Verweis auf die aktuell in der Gruppe ausgewählte Schaltfläche.

ButtonGrouphat eine getSelection()Methode, die das Modell der ausgewählten Schaltfläche (als ButtonModelTyp) zurückgibt , nicht die Schaltfläche selbst. Dies könnte in Ordnung sein, wenn Sie die Schaltflächenreferenz von ihrem Modell erhalten könnten, dies jedoch nicht. Die ButtonModelSchnittstelle und ihre implementierenden Klassen ermöglichen es Ihnen nicht, eine Schaltflächenreferenz aus ihrem Modell abzurufen. Also, was machst du? Sie sehen sich die ButtonGroupDokumentation an und sehen die getActionCommand()Methode. Sie erinnern sich, dass der Text im Konstruktor zurückgegeben wird , wenn Sie a JRadioButtonmit a Stringfür den neben der Schaltfläche angezeigten Text instanziieren und dann getActionCommand()die Schaltfläche aufrufen . Sie könnten denken, Sie können trotzdem mit dem Code fortfahren, denn selbst wenn Sie nicht über die Schaltflächenreferenz verfügen, haben Sie zumindest den Text und kennen die ausgewählte Schaltfläche.

Nun, Überraschung! Ihr Code bricht zur Laufzeit mit a NullPointerException. Warum? Weil getActionCommand()in ButtonModelRetouren null. Wenn Sie wetten (so wie ich) , dass getActionCommand()das gleiche Ergebnis erzeugt , ob auf der Schaltfläche oder auf dem Modell genannt (die es der Fall mit vielen anderen Methoden, wie zum Beispiel isSelected(), isEnabled()oder getMnemonic()), verloren Sie. Wenn Sie setActionCommand()die Schaltfläche nicht explizit aufrufen , legen Sie den Aktionsbefehl nicht in seinem Modell fest, und die Getter-Methode gibt nullfür das Modell zurück. Die Getter-Methode gibt jedoch den Schaltflächentext zurück, wenn sie auf der Schaltfläche aufgerufen wird. Hier ist die getActionCommand()Methode in AbstractButton, die von allen Schaltflächenklassen in Swing geerbt wurde:

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

Diese Inkonsistenz beim Setzen und Abrufen des Aktionsbefehls ist nicht akzeptabel. Sie können diese Situation vermeiden , wenn setText()in AbstractButtonSätzen die Aktion Befehl des Modells auf die Schaltfläche Text , wenn die Aktion Befehl null ist. Immerhin, es sei denn setActionCommand()ausdrücklich mit einigem genannt wird StringArgument (nicht null), wird die Schaltfläche Text wird die Aktion Befehl von der Schaltfläche selbst betrachtet. Warum sollte sich das Modell anders verhalten?

Wenn Ihr Code einen Verweis auf die aktuell ausgewählte Schaltfläche in der benötigt ButtonGroup, müssen Sie die folgenden Schritte ausführen , von denen keiner den Aufruf umfasst getSelection():

  • Rufen Sie getElements()an ButtonGroup, was ein zurückgibtEnumeration
  • Durchlaufen Sie die Enumeration, um einen Verweis auf jede Schaltfläche zu erhalten
  • Rufen Sie isSelected()jede Taste auf, um festzustellen, ob sie ausgewählt ist
  • Geben Sie einen Verweis auf die Schaltfläche zurück, die true zurückgegeben hat
  • Wenn Sie den Aktionsbefehl benötigen, rufen Sie getActionCommand()die Schaltfläche auf

Wenn dies nach vielen Schritten aussieht, nur um eine Schaltflächenreferenz zu erhalten, lesen Sie mit. Ich glaube ButtonGroup, die Implementierung ist grundsätzlich falsch. ButtonGroupbehält einen Verweis auf das Modell der ausgewählten Schaltfläche bei, wenn tatsächlich ein Verweis auf die Schaltfläche selbst beibehalten werden soll. Da getSelection()die Methode der ausgewählten Schaltfläche abgerufen wird, könnte man denken, dass die entsprechende Setter-Methode ist setSelection(), aber es ist nicht: es ist setSelected(). Jetzt setSelected()hat ein großes Problem. Seine Argumente sind a ButtonModelund a boolean. Wenn Sie setSelected()ein ButtonGroupModell einer Schaltfläche aufrufen und übergeben, das nicht Teil der Gruppe ist, und trueals Argumente, wird diese Schaltfläche ausgewählt und alle Schaltflächen in der Gruppe werden deaktiviert. Mit anderen Worten,ButtonGrouphat die Möglichkeit, eine an ihre Methode übergebene Schaltfläche auszuwählen oder deren Auswahl aufzuheben, obwohl die Schaltfläche nichts mit der Gruppe zu tun hat. Dieses Verhalten tritt auf, weil setSelected()in ButtonGroupnicht überprüft wird, ob die ButtonModelals Argument empfangene Referenz eine Schaltfläche in der Gruppe darstellt. Und da die Methode die Einzelauswahl erzwingt, deaktiviert sie tatsächlich ihre eigenen Schaltflächen, um eine auszuwählen, die nicht mit der Gruppe zusammenhängt.

Diese Bestimmung in der ButtonGroupDokumentation ist noch interessanter:

Es gibt keine Möglichkeit, eine Taste programmgesteuert auf "Aus" zu stellen, um die Tastengruppe zu löschen. Fügen Sie der Gruppe ein unsichtbares Optionsfeld hinzu, und wählen Sie dieses Programm programmgesteuert aus, um alle angezeigten Optionsfelder zu deaktivieren. Beispielsweise könnte eine normale Schaltfläche mit der Bezeichnung "Keine" verkabelt werden, um das unsichtbare Optionsfeld auszuwählen.

Nicht wirklich. Sie können jede beliebige Schaltfläche verwenden, die sich an einer beliebigen Stelle in Ihrer Anwendung befindet, sichtbar oder nicht sichtbar und sogar deaktiviert ist. Ja, Sie können sogar die Schaltflächengruppe verwenden, um eine deaktivierte Schaltfläche außerhalb der Gruppe auszuwählen, und die Auswahl aller Schaltflächen wird weiterhin aufgehoben. Um Verweise auf alle Schaltflächen in der Gruppe zu erhalten, müssen Sie das Lächerliche aufrufen getElements(). Was "Elemente" damit zu tun haben, ButtonGroupist jedermanns Vermutung. Der Name wurde wahrscheinlich von den EnumerationMethoden ( hasMoreElements()und nextElement()) der Klasse inspiriert , sollte aber getElements()eindeutig benannt worden sein getButtons(). Eine Schaltflächengruppe gruppiert Schaltflächen, keine Elemente.

Lösung: JButtonGroup

Aus all diesen Gründen wollte ich eine neue Klasse implementieren, die die Fehler behebt ButtonGroupund dem Benutzer einige Funktionen und Bequemlichkeiten bietet. Ich musste mich entscheiden, ob die Klasse eine neue Klasse sein oder von ihr erben sollte ButtonGroup. Alle vorherigen Argumente schlagen vor, eine neue Klasse anstelle einer ButtonGroupUnterklasse zu erstellen . Die ButtonModelSchnittstelle erfordert jedoch eine Methode setGroup(), die ein ButtonGroupArgument akzeptiert. Sofern ich nicht bereit war, auch Schaltflächenmodelle neu zu implementieren, bestand meine einzige Option darin, ButtonGroupdie meisten Methoden zu unterordnen und zu überschreiben. Wenn Sie von der ButtonModelSchnittstelle sprechen , beachten Sie das Fehlen einer aufgerufenen Methode getGroup().

Ein weiteres Problem, das ich nicht erwähnt habe, ist, dass ButtonGroupintern Verweise auf seine Schaltflächen in a beibehalten werden Vector. Somit wird unnötigerweise der VectorOverhead der Synchronisation erhalten , wenn eine verwendet werden sollte ArrayList, da die Klasse selbst nicht threadsicher ist und Swing ohnehin Single-Threaded ist. Die geschützte Variable buttonswird jedoch als VectorTyp deklariert und nicht so, Listwie Sie es von einem guten Programmierstil erwarten. Daher konnte ich die Variable nicht als ArrayList; und da wollte ich anrufen super.add()und super.remove(), konnte ich nicht die übergeordnete Klasse Variable verbergen. Also habe ich das Thema aufgegeben.

Ich schlage die Klasse JButtonGroupim Ton mit den meisten Swing-Klassennamen vor. Die Klasse überschreibt die meisten Methoden in ButtonGroupund bietet zusätzliche Komfortmethoden. Es enthält einen Verweis auf die aktuell ausgewählte Schaltfläche, die Sie mit einem einfachen Aufruf abrufen können getSelected(). Dank der ButtonGroupschlechten Implementierung konnte ich meine Methode benennen getSelected(), da dies getSelection()die Methode ist, die das Schaltflächenmodell zurückgibt.

Es JButtonGroupfolgen die Methoden von.

Zuerst habe ich zwei Änderungen an der add()Methode vorgenommen: Wenn sich die hinzuzufügende Schaltfläche bereits in der Gruppe befindet, wird die Methode zurückgegeben. Daher können Sie einer Gruppe nicht mehr als einmal eine Schaltfläche hinzufügen. Mit ButtonGroupkönnen Sie ein erstellen JRadioButtonund es 10 Mal zur Gruppe hinzufügen. Der Anruf getButtonCount()wird dann 10 zurückgeben. Dies sollte nicht passieren, daher erlaube ich keine doppelten Verweise. Wenn die hinzugefügte Schaltfläche zuvor ausgewählt wurde, wird sie zur ausgewählten Schaltfläche (dies ist das Standardverhalten in ButtonGroup, das angemessen ist, sodass ich es nicht überschrieben habe). Die selectedButtonVariable ist eine Referenz auf die aktuell ausgewählte Schaltfläche in der Gruppe:

public void add (AbstractButton-Schaltfläche) button.contains (Schaltfläche)) return; super.add (Schaltfläche); if (getSelection () == button.getModel ()) selectedButton = button;  

The overloaded add() method adds a whole array of buttons to the group. It is useful when you store button references in an array for block processing (i.e., setting borders, adding action listeners, etc.):

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

The following two methods remove a button or an array of buttons from the group:

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