Java savjet 142: Guranje JButtonGroup

Swing ima mnogo korisnih klasa koje olakšavaju razvoj grafičkog korisničkog sučelja (GUI). Međutim, neke od ovih klasa nisu dobro primijenjene. Jedan od primjera takve klase je ButtonGroup. Ovaj članak objašnjava zašto ButtonGroupje loše dizajniran i nudi zamjensku klasu JButtonGroupkoja nasljeđuje ButtonGroupneke probleme i rješava ih.

Napomena: Izvorni kôd ovog članka možete preuzeti iz izvora.

ButtonGroup rupe

Evo uobičajenog scenarija u razvoju Swing GUI-ja: Gradite obrazac za prikupljanje podataka o stavkama koje će netko unijeti u bazu podataka ili spremiti u datoteku. Obrazac može sadržavati tekstne okvire, potvrdne okvire, radio gumbe i druge widgete. ButtonGroupKlasu koristite za grupiranje svih radio gumba kojima je potreban jedan odabir. Kada je dizajn obrasca spreman, započinjete s implementacijom podataka obrasca. Nailazite na skup radio gumba i morate znati koji je gumb u grupi odabran kako biste odgovarajuće podatke mogli pohraniti u bazu podataka ili datoteku. Sad si zapeo. Zašto? ButtonGroupKlasa ne daje referencu na gumb trenutno odabranog u grupi.

ButtonGroupima getSelection()metodu koja vraća odabrani model gumba (kao ButtonModelvrstu), a ne sam gumb. Ovo bi moglo biti u redu ako biste mogli dobiti referencu gumba iz njegovog modela, ali ne možete. ButtonModelSučelje i njegovi provedbeni klase ne omogućuju vam da preuzimanje gumb referencu iz svog modela. Što radiš? Gledate ButtonGroupdokumentaciju i vidite getActionCommand()metodu. Sjećate se da ako instancirate a JRadioButtons Stringtekstom prikazanim pored gumba, a zatim pozovete getActionCommand()gumb, tekst u konstruktoru se vraća. Možda mislite da i dalje možete nastaviti s kodom, jer čak i ako nemate referencu na gumb, barem imate njegov tekst i još uvijek znate odabrani gumb.

Pa iznenađenje! Vaš se kôd lomi tijekom izvođenja s a NullPointerException. Zašto? Jer getActionCommand()u ButtonModeluzvratima null. Ako se kladiti (kao i ja), koji getActionCommand()proizvodi isti rezultat bilo nazivom na gumb ili na modelu (što je slučaj s mnogim drugim metodama, kao što su isSelected(), isEnabled()ili getMnemonic()), izgubio. Ako eksplicitno ne pozovete setActionCommand()gumb, ne postavite naredbu action u njegovom modelu, a metoda getter vraća se nullza model. Međutim, metoda inicijator i ne vrati tekst gumba kada je pozvao na gumb. Evo getActionCommand()metode u AbstractButton, naslijeđene od svih klasa gumba u Swingu:

javni String getActionCommand () {Niz ac = getModel (). getActionCommand (); if (ac == null) {ac = getText (); } povratak ac; }

Ova nedosljednost u postavljanju i dobivanju naredbe akcije je neprihvatljiva. Možete izbjeći ovu situaciju ako setText()in AbstractButtonpostavlja naredbu akcije za model na tekst gumba kada je naredba akcije null. Uostalom, osim ako setActionCommand()je izričito rečeno s nekim Stringargumentom (NOT NULL), tekst gumb se smatra akcija naredbu sama gumb. Zašto bi se model trebao ponašati drugačije?

Kada vaš kôd treba referencu na trenutno odabrani gumb u ButtonGroup, trebate slijediti ove korake, a niti jedan ne uključuje pozivanje getSelection():

  • Poziv getElements()na ButtonGroupkoji vraćaEnumeration
  • Prelistajte Enumerationkako biste dobili referencu na svaki gumb
  • Pozovite isSelected()svaki gumb da biste utvrdili je li odabran
  • Vrati referencu na gumb koji je vratio true
  • Ili, ako vam je potrebna naredba akcije, nazovite getActionCommand()gumb

Ako ovo izgleda kao puno koraka samo za dobivanje reference gumba, pročitajte dalje. Vjerujem ButtonGroupda je provedba u osnovi pogrešna. ButtonGroupzadržava referencu na model odabranog gumba kada bi zapravo trebao zadržati referencu na sam gumb. Nadalje, budući da getSelection()preuzima metodu odabranog gumba, možda mislite da je odgovarajuća metoda postavljača setSelection(), ali nije: jest setSelected(). Sad setSelected()ima veliki problem. Njegovi su argumenti a ButtonModeli logička vrijednost. Ako poziv setSelected()na ButtonGroupi proći na gumb model koji nije dio grupe i truekao argumente, onda taj gumb postaje odabrana, a svi gumbi u grupi postaju poništen je. Drugim riječima,ButtonGroupima moć odabrati ili poništiti odabir bilo kojeg gumba proslijeđenog njegovoj metodi, iako gumb nema nikakve veze s grupom. Do ovog ponašanja dolazi jer setSelected()in ButtonGroupne provjerava predstavlja li ButtonModelreferenca primljena kao argument gumb u grupi. A budući da metoda provodi pojedinačni odabir, zapravo poništava odabir vlastitih gumba za odabir jednog koji nije povezan sa grupom.

ButtonGroupJoš je zanimljivija ova odredba u dokumentaciji:

Ne postoji način da programski gumb "isključite" kako biste obrisali skupinu gumba. Da bi se prikazao 'nitko nije odabran', dodajte nevidljivi radio gumb u skupinu, a zatim programski odaberite taj gumb da biste isključili sve prikazane radio gumbe. Na primjer, uobičajeni gumb s oznakom 'none' mogao bi se ožičiti za odabir nevidljivog radio gumba.

Pa ne baš. Možete koristiti bilo koji gumb koji sjedi bilo gdje u vašoj aplikaciji, vidljiv ili ne, pa čak i onemogućen. Da, možete čak koristiti grupu gumba za odabir onemogućenog gumba izvan grupe i on će i dalje poništiti odabir svih svojih gumba. Da biste dobili reference na sve gumbe u grupi, morate nazvati smiješne getElements(). Kakve veze "elementi" imaju veze ButtonGroupbilo je tko nagađa. Ime je vjerojatno nadahnuto metodama Enumerationrazreda ( hasMoreElements()i nextElement()), ali je getElements()očito trebalo biti imenovano getButtons(). Grupa gumba grupira gumbe, a ne elemente.

Rješenje: JButtonGroup

Iz svih tih razloga želio sam implementirati novu klasu koja će ispraviti pogreške ButtonGroupi pružiti određenu funkcionalnost i praktičnost korisniku. Morao sam odlučiti treba li razred biti novi razred ili ga naslijediti ButtonGroup. Svi prethodni argumenti sugeriraju stvaranje nove klase, a ne ButtonGrouppotklase. Međutim, ButtonModelsučelje zahtijeva metodu setGroup()koja uzima ButtonGroupargument. Ako nisam bio spreman primijeniti i modele gumba, jedina mogućnost bila mi je podvrstati ButtonGroupi nadjačati većinu njegovih metoda. Govoreći o ButtonModelsučelju, primijetite odsustvo metode koja se naziva getGroup().

Još jedno pitanje koje nisam spomenuo jest da ButtonGroupinterno čuva reference na svoje gumbe u a Vector. Dakle, nepotrebno dobiva sinkronizirane Vectorrežijske troškove, kada bi trebao koristiti ArrayList, jer klasa sama po sebi nije sigurna u nitima, a Swing je ionako jednostruki. Međutim, zaštićena varijabla buttonsproglašava se Vectorvrstom, a ne Listonako kako biste mogli očekivati ​​od dobrog stila programiranja. Stoga, nisam mogao ponovno primijeniti varijablu kao ArrayList; i jer sam želio nazvati super.add()i super.remove(), nisam mogao sakriti varijablu superklase. Zato sam napustio problem.

Predlažem predavanje JButtonGroup, u skladu s većinom naziva Swing razreda. Klasa nadjačava većinu metoda u ButtonGroupi pruža dodatne metode pogodnosti. Zadržava referencu na trenutno odabrani gumb, koji možete dobiti jednostavnim pozivom getSelected(). Zahvaljujući ButtonGrouplošoj implementaciji, mogao bih imenovati svoju metodu getSelected(), jer getSelection()je ona metoda koja vraća model gumba.

Slijede JButtonGroupmetode.

Prvo sam načinio dvije izmjene add()metode: Ako se gumb koji se dodaje već nalazi u grupi, metoda se vraća. Dakle, ne možete dodati gumb grupi više puta. Pomoću njega ButtonGroupmožete stvoriti JRadioButtoni dodati ga 10 puta u grupu. Poziv getButtonCount()će se vratiti 10. To se ne bi trebalo dogoditi, pa ne dopuštam duplicirane reference. Zatim, ako je dodani gumb prethodno odabran, on postaje odabrani gumb (ovo je zadano ponašanje u ButtonGroup, što je razumno, pa ga nisam nadjačao). selectedButtonVarijabla referenca na trenutačno odabranog gumb u skupini:

javna void dodavanje (tipka AbstractButton) gumbi.sadrži (gumb)) return; super.add (gumb); ako je (getSelection () == button.getModel ()) selectedButton = button;  

Preopterećena add()metoda dodaje čitav niz gumba u grupu. Korisno je kada reference niza gumba pohranite u niz za obradu blokova (tj. Postavljanje granica, dodavanje preslušača akcija, itd.):

javna praznina dodaj (gumbi AbstractButton []) {if (gumbi == null) return; za (int i = 0; i
   
    

Sljedeće dvije metode uklanjaju gumb ili niz gumba iz grupe:

javna praznina ukloni (gumb AbstractButton) {if (gumb! = null) {if (selectedButton == gumb) selectedButton = null; super.remove (gumb); }} javna praznina ukloni (gumbi AbstractButton []) {if (gumbi == null) return; za (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 .