Programmering

Java Tips 142: Pushing JButtonGroup

Swing har mange nyttige klasser som gjør det enkelt å utvikle grafisk brukergrensesnitt (GUI). Noen av disse klassene er imidlertid ikke godt implementert. Et eksempel på en slik klasse er ButtonGroup. Denne artikkelen forklarer hvorfor ButtonGroup er dårlig designet og tilbyr en erstatningsklasse, JButtonGroup, som arver fra ButtonGroup og løser noen av problemene.

Merk: Du kan laste ned kildekoden til denne artikkelen fra Resources.

Knapp Grupper hull

Her er et vanlig scenario i Swing GUI-utvikling: Du bygger et skjema for å samle inn data om elementer som noen vil legge inn i en database eller lagre i en fil. Skjemaet kan inneholde tekstbokser, avmerkingsbokser, alternativknapper og andre småprogram. Du bruker ButtonGroup klasse for å gruppere alle alternativknapper som trenger enkeltvalg. Når skjemautformingen er klar, begynner du å implementere skjemadataene. Du møter settet med radioknapper, og du må vite hvilken knapp i gruppen som ble valgt, slik at du kan lagre riktig informasjon i databasen eller filen. Du er nå fast. Hvorfor? De ButtonGroup klasse gir deg ikke en referanse til knappen som for øyeblikket er valgt i gruppen.

ButtonGroup har en getSelection () metode som returnerer den valgte knappens modell (som en ButtonModel type), ikke selve knappen. Dette kan være greit hvis du kan få knappereferansen fra modellen, men du kan ikke. De ButtonModel grensesnittet og dets implementeringsklasser tillater deg ikke å hente en knappereferanse fra modellen. Så hva gjør du? Du ser på ButtonGroup dokumentasjon og se getActionCommand () metode. Du husker at hvis du instantierer a JRadioButton med en String for teksten som vises ved siden av knappen, og så ringer du getActionCommand () på knappen, returnerer teksten i konstruktøren. Du tror kanskje du fortsatt kan fortsette med koden, for selv om du ikke har knappereferanse, har du i det minste teksten og fortsatt kjenner den valgte knappen.

Vel, overraskelse! Koden din går i stykker ved kjøretid med en NullPointerException. Hvorfor? Fordi getActionCommand () i ButtonModel returnerer null. Hvis du satser (som jeg gjorde) det getActionCommand () produserer det samme resultatet enten det kalles på knappen eller på modellen (som er tilfelle med mange andre metoder, for eksempel isSelected (), Er på(), eller getMnemonic ()), du tapte. Hvis du ikke eksplisitt ringer setActionCommand () på knappen setter du ikke handlingskommandoen i sin modell, og getter-metoden returnerer null for modellen. Imidlertid getter-metoden gjør returner knappeteksten når du blir kalt på knappen. Her er getActionCommand () metode i Abstrakt Knapp, arvet av alle knappeklasser i Swing:

 public String getActionCommand () {String ac = getModel (). getActionCommand (); hvis (ac == null) {ac = getText (); } returner ac; } 

Denne inkonsekvensen med å sette og få handlingskommandoen er uakseptabelt. Du kan unngå denne situasjonen hvis setText () i Abstrakt Knapp setter modellens handlingskommando til knappeteksten når handlingskommandoen er null. Tross alt, med mindre setActionCommand () kalles eksplisitt med noen String argument (ikke null), knappeteksten er betraktet handlingskommandoen med selve knappen. Hvorfor skal modellen oppføre seg annerledes?

Når koden din trenger en referanse til den valgte knappen i ButtonGroup, må du følge disse trinnene, og ingen av dem innebærer å ringe getSelection ():

  • Anrop getElements ()ButtonGroup, som returnerer en Oppregning
  • Iterere gjennom Oppregning for å få en referanse til hver knapp
  • Anrop isSelected () på hver knapp for å avgjøre om den er valgt
  • Returner en referanse til knappen som returnerte true
  • Eller ring hvis du trenger handlingskommandoen getActionCommand () på knappen

Hvis dette ser ut som mange trinn bare for å få en knappereferanse, kan du lese videre. jeg tror ButtonGroupimplementeringen er grunnleggende feil. ButtonGroup holder en referanse til den valgte knappens modell når den faktisk skal beholde en referanse til selve knappen. Videre siden getSelection () henter den valgte knappens metode, du tror kanskje den tilsvarende settermetoden er setSelection (), men det er det ikke: det er setSelected (). Nå, setSelected () har et stort problem. Argumentene er a ButtonModel og en boolsk. Hvis du ringer setSelected () på en ButtonGroup og passere en knapps modell som ikke er en del av gruppen og ekte som argumenter, blir den knappen valgt, og alle knappene i gruppen blir ikke valgt. Med andre ord, ButtonGroup har makten til å velge eller oppheve valget av en hvilken som helst knapp som er sendt til metoden, selv om knappen ikke har noe med gruppen å gjøre. Denne oppførselen oppstår fordi setSelected () i ButtonGroup sjekker ikke om ButtonModel referanse mottatt som argument representerer en knapp i gruppen. Og fordi metoden håndhever enkeltvalg, fjerner den faktisk sine egne knapper for å velge en som ikke er relatert til gruppen.

Denne bestemmelsen i ButtonGroup dokumentasjon er enda mer interessant:

Det er ingen måte å slå en knapp programmatisk til "av" for å tømme knappegruppen. For å se ut som 'ingen valgt', legg til en usynlig radioknapp i gruppen og velg deretter programmatisk den knappen for å slå av alle viste radioknapper. For eksempel kan en vanlig knapp med etiketten 'none' kobles til for å velge den usynlige alternativknappen.

Vel, egentlig ikke. Du kan bruke hvilken som helst knapp, sittende hvor som helst i applikasjonen, synlig eller ikke, og til og med deaktivert. Ja, du kan til og med bruke knappegruppen til å velge en deaktivert knapp utenfor gruppen, og den vil fortsatt fjerne merket for alle knappene. For å få referanser til alle knappene i gruppen, må du ringe det latterlige getElements (). Hva "elementer" har med å gjøre ButtonGroup er det noen som gjetter. Navnet var sannsynligvis inspirert av Oppregning klassens metoder (hasMoreElements () og nextElement ()), men getElements () burde tydeligvis ha fått navnet getButtons (). En knappegruppe grupperer knapper, ikke elementer.

Løsning: JButtonGroup

Av alle disse grunnene ønsket jeg å implementere en ny klasse som ville løse feilene i ButtonGroup og gir brukeren litt funksjonalitet og bekvemmelighet. Jeg måtte bestemme meg for om klassen skulle være en ny klasse eller arve fra ButtonGroup. Alle de tidligere argumentene antyder at du oppretter en ny klasse i stedet for en ButtonGroup underklasse. Imidlertid, den ButtonModel grensesnittet krever en metode setGroup () det tar en ButtonGroup argument. Med mindre jeg også var klar til å implementere knappemodeller, var det eneste alternativet mitt å underklasse ButtonGroup og overstyre de fleste av metodene. Apropos ButtonModel grensesnitt, legg merke til fraværet av en metode som kalles getGroup ().

En annen sak jeg ikke har nevnt er at ButtonGroup internt holder referanser til knappene i a Vector. Dermed blir det unødvendig synkronisert Vectorer overhead, når den skal bruke en ArrayList, siden klassen i seg selv ikke er trådsikker, og Swing uansett er en tråd. Imidlertid den beskyttede variabelen knappene er erklært a Vector type, og ikke Liste som du kanskje forventer av god programmeringsstil. Dermed kunne jeg ikke implementere variabelen på nytt som en ArrayList; og fordi jeg ville ringe super.add () og super.remove ()Jeg kunne ikke skjule superklassvariabelen. Så jeg forlot saken.

Jeg foreslår timen JButtonGroup, i tone med de fleste Swing-klassens navn. Klassen overstyrer de fleste metodene i ButtonGroup og gir ytterligere bekvemmelighetsmetoder. Den holder en referanse til den valgte knappen, som du kan hente med en enkel samtale til getSelected (). Takk til ButtonGroupdårlig implementering, kunne jeg nevne metoden min getSelected (), siden getSelection () er metoden som returnerer knappemodellen.

Følgende er JButtonGroupsine metoder.

Først gjorde jeg to modifikasjoner på legge til() metode: Hvis knappen som skal legges til allerede er i gruppen, returnerer metoden. Dermed kan du ikke legge til en knapp i en gruppe mer enn en gang. Med ButtonGroup, kan du opprette en JRadioButton og legg den til 10 ganger i gruppen. Ringer getButtonCount () vil da returnere 10. Dette skal ikke skje, så jeg tillater ikke dupliserte referanser. Deretter, hvis den lagt til knappen tidligere ble valgt, blir den den valgte knappen (dette er standard oppførsel i ButtonGroup, som er rimelig, så jeg overstyrte det ikke). De valgt Knapp variabel er en referanse til den valgte knappen i gruppen:

offentlig tomrom legge til (AbstractButton knapp) knapper. inneholder (knapp)) retur; super.add (knapp); hvis (getSelection () == button.getModel ()) valgtButton = knapp; 

Den overbelastede legge til() metoden legger til en hel rekke knapper i gruppen. Det er nyttig når du lagrer knappereferanser i en matrise for blokkbehandling (dvs. angi grenser, legge til handlingslyttere osv.):

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

Følgende to metoder fjerner en knapp eller en rekke knapper fra gruppen:

public void remove (AbstractButton-knapp) {if (button! = null) {if (selectedButton == button) valgtButton = null; super.remove (knapp); }} public void remove (AbstractButton [] buttons) {if (buttons == null) return; for (int i = 0; i

Heretter den første setSelected () metoden lar deg angi en knapps valgtilstand ved å sende knappereferansen i stedet for modellen. Den andre metoden tilsidesetter den tilsvarende setSelected () i ButtonGroup for å sikre at gruppen bare kan velge eller fjerne markeringen av en knapp som tilhører gruppen:

public void setSelected (AbstractButton-knapp, boolsk valgt) {if (button! = null && buttons.contains (button)) {setSelected (button.getModel (), valgt); hvis (getSelection () == button.getModel ()) valgtButton = knapp; }} public void setSelected (ButtonModel model, boolean selected) {AbstractButton button = getButton (model); hvis (buttons.contains (button)) super.setSelected (modell, valgt); } 

De getButton () metoden henter en referanse til knappen hvis modell er gitt. setSelected () bruker denne metoden for å hente knappen som skal velges gitt sin modell. Hvis modellen som sendes til metoden tilhører en knapp utenfor gruppen, null blir returnert. Denne metoden skal eksistere i ButtonModel implementeringer, men dessverre gjør det ikke:

offentlig AbstractButton getButton (ButtonModel-modell) {Iterator it = buttons.iterator (); mens (it.hasNext ()) {AbstractButton ab = (AbstractButton) it.next (); hvis (ab.getModel () == modell) returnerer ab; } returner null; } 

getSelected () og isSelected () er de enkleste og sannsynligvis mest nyttige metodene for JButtonGroup klasse. getSelected () returnerer en referanse til den valgte knappen, og isSelected () overbelaster metoden med samme navn i ButtonGroup å ta en knappereferanse:

offentlig AbstractButton getSelected () {return valgtButton; } offentlig boolsk isSelected (AbstractButton-knapp) {retur-knapp == valgtButton; } 

Denne metoden sjekker om en knapp er en del av gruppen:

offentlig boolsk inneholder (AbstractButton-knapp) {retur-knapper. inneholder (knapp); } 

Du forventer en metode som heter getButtons () i en ButtonGroup klasse. Den returnerer en uforanderlig liste som inneholder referanser til knappene i gruppen. Den uforanderlige listen forhindrer tilføyelse eller fjerning av knapper uten å gå gjennom knappegruppens metoder. getElements () i ButtonGroup ikke bare har et helt uinspirert navn, men det returnerer et Oppregning, som er en foreldet klasse du ikke bør bruke. Collections Framework gir alt du trenger for å unngå oppføringer. Dette er hvordan getButtons () returnerer en uforanderlig liste:

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

Forbedre ButtonGroup

De JButtonGroup klasse tilbyr et bedre og mer praktisk alternativ til Swing ButtonGroup klasse, mens du bevarer all superklassens funksjonalitet.

Daniel Tofan er som postdoktor ved Chemistry Department ved State University of New York, Stony Brook. Hans arbeid innebærer å utvikle kjernedelen av et kursledelsessystem med anvendelse i kjemi. Han er en Sun-sertifisert programmerer for Java 2-plattformen og har en doktorgrad i kjemi.

Lær mer om dette emnet

  • Last ned kildekoden som følger med denne artikkelen

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

  • Sun Microsystems 'Java Foundation Classes hjemmeside

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

  • Java 2 Platform, Standard Edition (J2SE) 1.4.2 API-dokumentasjon

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

  • ButtonGroup-klasse

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

  • Vis alle forrige Java-tips og send inn dine egne

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

  • Bla gjennom AWT / sving seksjon av JavaWorld 's Aktuell indeks

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

  • Bla gjennom Grunnklasser seksjon av JavaWorld 's Aktuell indeks

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

  • Bla gjennom Brukergrensesnittdesign seksjon av JavaWorld 's Aktuell indeks

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

  • Besøk JavaWorld Forum

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

  • Melde seg på JavaWorld 's gratis ukentlige nyhetsbrev

    //www.javaworld.com/subscribe

Denne historien, "Java Tip 142: Pushing JButtonGroup" ble opprinnelig utgitt av JavaWorld.

$config[zx-auto] not found$config[zx-overlay] not found