Ohjelmointi

Java-vinkki 142: JButtonGroupin työntäminen

Swingissä on monia hyödyllisiä luokkia, jotka tekevät graafisen käyttöliittymän (GUI) kehittämisestä helppoa. Joitakin näistä luokista ei kuitenkaan toteuteta hyvin. Yksi esimerkki tällaisesta luokasta on PainikeRyhmä. Tässä artikkelissa selitetään miksi PainikeRyhmä on huonosti suunniteltu ja tarjoaa korvaavan luokan, JPainikeRyhmä, joka perii PainikeRyhmä ja korjaa joitain sen ongelmia.

merkintä: Voit ladata tämän artikkelin lähdekoodin Resursseista.

Ryhmäreiät

Tässä on yleinen skenaario Swing-käyttöliittymäkehityksessä: Rakennat lomakkeen kerätäksesi tietoja kohteista, jotka joku lisää tietokantaan tai tallentaa tiedostoon. Lomake voi sisältää tekstiruutuja, valintaruutuja, valintanappeja ja muita widgetejä. Käytät PainikeRyhmä luokka ryhmittää kaikki valintanapit, jotka tarvitsevat yhden valinnan. Kun lomakkeen suunnittelu on valmis, aloitat lomaketietojen käyttöönoton. Kohdat valintanappipaketin ja sinun on tiedettävä, mikä ryhmän painike on valittu, jotta voit tallentaa tarvittavat tiedot tietokantaan tai tiedostoon. Olet nyt jumissa. Miksi? PainikeRyhmä luokka ei anna sinulle viitteitä ryhmässä valitulle painikkeelle.

PainikeRyhmä on getSelection () menetelmä, joka palauttaa valitun painikkeen mallin (kuten PainikeMalli tyyppi), ei itse painiketta. Nyt tämä voi olla kunnossa, jos saat painikeviitteen sen mallista, mutta et voi. PainikeMalli käyttöliittymä ja sen toteutusluokat eivät salli noutaa painikeviitettä sen mallista. Joten mitä sinä teet? Katsot PainikeRyhmä asiakirjat ja katso getActionCommand () menetelmä. Muistat, että jos aloitat a JRadioPainike kanssa Merkkijono painikkeen vieressä näkyvälle tekstille, ja sitten soitat getActionCommand () painikkeessa konstruktorin teksti palaa. Saatat ajatella, että voit silti jatkaa koodia, koska vaikka sinulla ei olisi paineviittausta, sinulla on ainakin sen teksti ja tiedät silti valitun painikkeen.

No, yllätys! Koodisi rikkoutuu ajon aikana a: lla NullPointerException. Miksi? Koska getActionCommand () sisään PainikeMalli palaa tyhjä. Jos lyöt vetoa (kuten minä) niin getActionCommand () tuottaa saman tuloksen riippumatta siitä, kutsutaanko painiketta tai mallia (kuten on monet muut menetelmät, kuten isSelected (), on käytössä()tai getMnemonic ()), hävisit. Jos et nimenomaisesti soita setActionCommand () painikkeessa, et aseta toimintokomentoa sen malliin, ja getter-menetelmä palaa tyhjä mallille. Kuitenkin getter-menetelmä tekee palauta painikkeen teksti, kun sitä painetaan. Tässä on getActionCommand () menetelmä TiivistelmäNappi, peri kaikki Swingin painikeluokat:

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

Tätä epäjohdonmukaisuutta toimintokomennon asettamisessa ja saamisessa ei voida hyväksyä. Voit välttää tämän tilanteen, jos setText () sisään TiivistelmäNappi asettaa mallin toimintokomennon painikkeen tekstiksi, kun toimintokomento on tyhjä. Loppujen lopuksi, ellei setActionCommand () kutsutaan nimenomaisesti joidenkin kanssa Merkkijono argumentti (ei nolla), painikkeen teksti On pitää toimintokomentoa itse painikkeella. Miksi mallin pitäisi toimia toisin?

Kun koodisi tarvitsee viitteen tällä hetkellä valitulle painikkeelle PainikeRyhmä, sinun on noudatettava näitä vaiheita, joista mikään ei sisällä soittamista getSelection ():

  • Puhelu getElements () päällä PainikeRyhmä, joka palauttaa arvon Luettelointi
  • Toista läpi Luettelointi saadaksesi viittauksen jokaiseen painikkeeseen
  • Puhelu isSelected () kunkin painikkeen kohdalla sen määrittämiseksi, onko se valittu
  • Palauta viite painikkeeseen, joka palasi tosi
  • Tai jos tarvitset toimintokomennon, soita getActionCommand () painiketta

Jos tämä näyttää paljon vaiheilta vain napin viitteen saamiseksi, lue se. minä uskon PainikeRyhmäToteutus on pohjimmiltaan väärä. PainikeRyhmä säilyttää viittauksen valitun painikkeen malliin, kun sen pitäisi itse asiassa pitää viittaus itse painikkeeseen. Lisäksi, koska getSelection () noutaa valitun painikkeen menetelmän, saatat ajatella vastaavan setterimenetelmän olevan setSelection (), mutta se ei ole: se on setSelected (). Nyt, setSelected () on iso ongelma. Sen perustelut ovat a PainikeMalli ja totuusarvo. Jos soitat setSelected () a PainikeRyhmä ja välitä painikkeen malli, joka ei kuulu ryhmään ja totta argumentteina kyseinen painike valitaan ja kaikkia ryhmän painikkeita ei valita. Toisin sanoen, PainikeRyhmä on valta valita tai poistaa minkä tahansa menetelmänsä välittämä painike, vaikka painikkeella ei ole mitään tekemistä ryhmän kanssa. Tämä käyttäytyminen tapahtuu, koska setSelected () sisään PainikeRyhmä ei tarkista onko PainikeMalli argumenttina vastaanotettu viite edustaa ryhmän painiketta. Ja koska menetelmä pakottaa yhden valinnan, se itse poistaa omien painikkeidensa valinnan valitakseen ryhmän, joka ei liity ryhmään.

Tämä määräys PainikeRyhmä dokumentaatio on vieläkin mielenkiintoisempi:

Painiketta ei voida muuttaa ohjelmallisesti "pois" painikeryhmän tyhjentämiseksi. Jos haluat näyttää "kukaan ei ole valittu", lisää ryhmään näkymätön valintanappi ja poista sitten kaikki näytetyt valintanapit käytöstä valitsemalla se painikkeella ohjelmallisesti. Esimerkiksi näkymättömän valintanapin valitsemiseksi voidaan kytkeä tavallinen painike, jossa on merkintä ”ei mitään”.

No eipä oikeastaan. Voit käyttää mitä tahansa painiketta, istuen missä tahansa sovelluksessasi, näkyvissä tai ei, ja jopa pois käytöstä. Kyllä, voit jopa painikeryhmän avulla valita käytöstä poistetun painikkeen ryhmän ulkopuolelta, ja se silti poistaa kaikkien painikkeiden valinnan. Saadaksesi viitteitä ryhmän kaikkiin painikkeisiin, sinun on soitettava naurettavaksi getElements (). Mitä "elementteillä" on tekemistä PainikeRyhmä on kenenkään arvaus. Nimi on todennäköisesti saanut inspiraationsa Luettelointi luokan menetelmät (hasMoreElements () ja seuraavaElement ()), mutta getElements () olisi selvästi pitänyt nimetä getButtons (). Painike ryhmittelee painikkeet, ei elementtejä.

Ratkaisu: JButtonGroup

Kaikista näistä syistä halusin ottaa käyttöön uuden luokan, joka korjaa virheet PainikeRyhmä ja tarjota käyttäjälle jonkin verran toiminnallisuutta ja mukavuutta. Minun piti päättää, pitäisikö luokan olla uusi vai periäkö se PainikeRyhmä. Kaikki edelliset argumentit viittaavat uuden luokan luomiseen a: n sijaan PainikeRyhmä alaluokka. Kuitenkin PainikeMalli käyttöliittymä vaatii menetelmän setGroup () joka vie a PainikeRyhmä Perustelu. Ellei olin valmis uudistamaan myös painikemalleja, ainoa vaihtoehto oli alaluokka PainikeRyhmä ja ohittaa suurimman osan sen menetelmistä. Puhuminen PainikeMalli käyttöliittymässä, huomaa kutsutun menetelmän puuttuminen getGroup ().

Yksi asia, jota en ole maininnut, on se PainikeRyhmä pitää sisäisesti viitteitä painikkeisiinsa a Vektori. Siten synkronoidaan tarpeettomasti Vektori, kun sen pitäisi käyttää ArrayList, koska luokka itsessään ei ole lanka turvallinen ja Swing on joka tapauksessa yksisäikeinen. Suojattu muuttuja painikkeita ilmoitetaan a Vektori tyyppi, eikä Lista kuten voit odottaa hyvältä ohjelmointityyliltä. Siksi en voinut lisätä muuttujaa uudelleen ArrayList; ja koska halusin soittaa super.add () ja super.remove (), En voinut piilottaa yliluokan muuttujaa. Joten hylkäsin asian.

Ehdotan luokkaa JPainikeRyhmä, samaan aikaan useimpien Swing-luokkien nimien kanssa. Luokka ohittaa useimmat menetelmät PainikeRyhmä ja tarjoaa muita mukavuusmenetelmiä. Siinä on viittaus valittuun painikkeeseen, jonka voit hakea yksinkertaisella kutsulla getSelected (). Kiitokset PainikeRyhmähuono toteutus, voisin nimetä menetelmän getSelected (), siitä asti kun getSelection () on menetelmä, joka palauttaa painikemallin.

Seuraavat ovat JPainikeRyhmämenetelmät.

Ensinnäkin tein kaksi muunnosta lisätä() method: Jos lisättävä painike on jo ryhmässä, menetelmä palaa. Siksi et voi lisätä nappia ryhmään useammin kuin kerran. Kanssa PainikeRyhmä, voit luoda JRadioPainike ja lisää se 10 kertaa ryhmään. Kutsumus getButtonCount () palaa sitten 10. Tämän ei pitäisi tapahtua, joten en salli päällekkäisiä viitteitä. Sitten, jos lisätty painike on aiemmin valittu, siitä tulee valittu painike (tämä on oletusarvoinen toiminto PainikeRyhmä, mikä on kohtuullista, joten en ohittanut sitä). valittu painike muuttuja on viittaus ryhmän tällä hetkellä valittuun painikkeeseen:

public void add (AbstractButton button) -painikkeet. sisältää (painike)) return; super.add (painike); jos (getSelection () == button.getModel ()) selectedButton = painike; 

Ylikuormitettu lisätä() method lisää koko joukon painikkeita ryhmään. Se on hyödyllinen, kun tallennat painikkeita viitteitä taulukkoon lohkojen käsittelyä varten (eli rajojen asettaminen, toimintakuuntelijoiden lisääminen jne.):

public void add (AbstractButton [] -painikkeet) {if (painikkeet == null) return; varten (int i = 0; i

Seuraavat kaksi tapaa poistaa painikkeen tai painikeryhmän ryhmästä:

public void remove (AbstractButton button) {if (button! = null) {if (selectedButton == button) selectedButton = null; super.remove (painike); }} public void poista (AbstractButton [] -painikkeet) {if (painikkeet == null) return; varten (int i = 0; i

Tämän jälkeen ensimmäinen setSelected () menetelmä antaa sinun asettaa painikkeen valintatilan välittämällä painikkeen viitteen sen mallin sijaan. Toinen menetelmä ohittaa vastaavan setSelected () sisään PainikeRyhmä varmistaa, että ryhmä voi valita tai poistaa vain ryhmään kuuluvan painikkeen:

public void setSelected (AbstractButton button, boolean selected) {if (button! = null && button.contains (button)) {setSelected (button.getModel (), selected); jos (getSelection () == button.getModel ()) selectedButton = painike; }} public void setSelected (ButtonModel-malli, totuusarvo valittu) {AbstractButton-painike = getButton (malli); jos (painikkeet sisältää (painike)) super.setSelected (malli, valittu); } 

getButton () method hakee viittauksen painikkeeseen, jonka malli on annettu. setSelected () käyttää tätä tapaa hakea valitun painikkeen mallinsa perusteella. Jos menetelmälle välitetty malli kuuluu ryhmän ulkopuolella olevaan painikkeeseen, tyhjä palautetaan. Tämän menetelmän pitäisi olla olemassa PainikeMalli toteutuksia, mutta valitettavasti se ei:

public AbstractButton getButton (ButtonModel-malli) {Iterator it = painikkeet.iterator (); while (it.hasNext ()) {AbstractButton ab = (AbstractButton) it.next (); if (ab.getModel () == malli) palauttaa ab; } return null; } 

getSelected () ja isSelected () ovat yksinkertaisia ​​ja todennäköisesti hyödyllisimpiä menetelmiä JPainikeRyhmä luokassa. getSelected () palauttaa viitteen valittuun painikkeeseen ja isSelected () ylikuormittaa saman nimisen menetelmän sisään PainikeRyhmä napin viite:

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

Tämä menetelmä tarkistaa, kuuluuko painike ryhmään:

julkinen totuusarvo sisältää (AbstractButton-painike) {paluupainikkeet. sisältää (painike); } 

Voit odottaa nimettyä menetelmää getButtons () jonkin sisällä PainikeRyhmä luokassa. Se palauttaa muuttumattoman luettelon, joka sisältää viitteet ryhmän painikkeisiin. Muuttamaton luettelo estää painikkeiden lisäämisen tai poistamisen käymättä läpi painikeryhmän menetelmiä. getElements () sisään PainikeRyhmä ei vain ole täysin inspiroimatonta nimeä, mutta se palauttaa myös Luettelointi, joka on vanhentunut luokka, jota sinun ei pitäisi käyttää. Kokoelmakehys tarjoaa kaiken mitä tarvitset luetteloiden välttämiseksi. Näin getButtons () palauttaa muuttumattoman luettelon:

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

Paranna ButtonGroupia

JPainikeRyhmä luokka tarjoaa paremman ja mukavamman vaihtoehdon Swingille PainikeRyhmä luokassa samalla kun säilytetään kaikki superluokan toiminnot.

Daniel Tofan on tutkijatohtorina kemian osastolla New Yorkin osavaltion yliopistossa Stony Brookissa. Hänen työhönsä kuuluu kurssinhallintajärjestelmän keskeisen osan kehittäminen kemian sovelluksella. Hän on Sun 2 -sertifioitu ohjelmoija Java 2 -alustalle ja hänellä on kemian tohtori.

Lisätietoja tästä aiheesta

  • Lataa tämän artikkelin mukana oleva lähdekoodi

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

  • Sun Microsystemsin Java Foundation Classes -sivusto

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

  • Java 2 Platform, Standard Edition (J2SE) 1.4.2 -sovellusliittymän dokumentaatio

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

  • ButtonGroup-luokka

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

  • Näytä kaikki edelliset Java-vinkkejä ja lähetä oma

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

  • Selaa AWT / Swing osa JavaWorld 's Ajankohtainen hakemisto

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

  • Selaa Säätiön luokat osa JavaWorld 's Ajankohtainen hakemisto

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

  • Selaa Käyttöliittymän suunnittelu osa JavaWorld 's Ajankohtainen hakemisto

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

  • Käy JavaWorld-foorumilla

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

  • Ilmottautua JavaWorld 'Ilmaiset viikoittaiset sähköpostiuutiskirjeet

    //www.javaworld.com/subscribe

Tämän tarinan, "Java Tip 142: Pushing JButtonGroup", julkaisi alun perin JavaWorld.