Programmering

Java Tip 142: Skubber JButtonGroup

Swing har mange nyttige klasser, der gør det let at udvikle grafisk brugergrænseflade (GUI). Nogle af disse klasser er imidlertid ikke godt implementeret. Et eksempel på en sådan klasse er ButtonGroup. Denne artikel forklarer hvorfor ButtonGroup er dårligt designet og tilbyder en erstatningsklasse, JButtonGroup, som arver fra ButtonGroup og løser nogle af dens problemer.

Bemærk: Du kan downloade denne artikels kildekode fra Resources.

Knap Grupper huller

Her er et almindeligt scenarie i Swing GUI-udvikling: Du bygger en formular til at indsamle data om emner, som nogen vil indtaste i en database eller gemme i en fil. Formularen kan indeholde tekstfelter, afkrydsningsfelter, radioknapper og andre widgets. Du bruger ButtonGroup klasse for at gruppere alle radioknapper, der har brug for et enkelt valg. Når formulardesignet er klar, begynder du at implementere formulardata. Du støder på sæt radioknapper, og du skal vide, hvilken knap i gruppen der blev valgt, så du kan gemme de relevante oplysninger i databasen eller filen. Du sidder nu fast. Hvorfor? Det ButtonGroup klasse giver dig ikke en henvisning til den knap, der aktuelt er valgt i gruppen.

ButtonGroup har en getSelection () metode, der returnerer den valgte knaps model (som en ButtonModel type), ikke selve knappen. Nu kan det være okay, hvis du kunne få knapreference fra sin model, men du kan ikke. Det ButtonModel interface og dets implementeringsklasser giver dig ikke mulighed for at hente en knapreference fra sin model. Så hvad laver du? Du ser på ButtonGroup dokumentation og se getActionCommand () metode. Du husker, at hvis du instantierer en JRadioButton med en Snor for teksten, der vises ved siden af ​​knappen, og så ringer du getActionCommand () på knappen vender teksten i konstruktøren tilbage. Du tror måske, at du stadig kan fortsætte med koden, for selvom du ikke i det mindste har knapreferencen, har du dens tekst og stadig kender den valgte knap.

Nå, overraskelse! Din kode bryder ved kørsel med en NullPointerException. Hvorfor? Fordi getActionCommand () i ButtonModel vender tilbage nul. Hvis du satser (som jeg gjorde) det getActionCommand () producerer det samme resultat, hvad enten det kaldes på knappen eller på modellen (hvilket er tilfældet med mange andre metoder, såsom isSelected (), isEnabled (), eller getMnemonic ()), du tabte. Hvis du ikke udtrykkeligt ringer setActionCommand () på knappen indstiller du ikke handlingskommandoen i sin model, og getter-metoden vender tilbage nul til modellen. Dog getter-metoden gør returner knapteksten, når den kaldes på knappen. Her er getActionCommand () metode i AbstraktKnap, arvet af alle knapklasser i Swing:

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

Denne inkonsekvens med at indstille og få handlingskommandoen er uacceptabel. Du kan undgå denne situation, hvis setText () i AbstraktKnap indstiller modelens handlingskommando til knapteksten, når handlingskommandoen er nul. Når alt kommer til alt, medmindre setActionCommand () kaldes eksplicit med nogle Snor argument (ikke null), knapteksten er betragtede handlingskommandoen ved hjælp af selve knappen. Hvorfor skal modellen opføre sig anderledes?

Når din kode har brug for en henvisning til den aktuelt valgte knap i ButtonGroup, skal du følge disse trin, hvoraf ingen involverer at ringe getSelection ():

  • Opkald getElements ()ButtonGroup, som returnerer en Optælling
  • Iterere gennem Optælling for at få en henvisning til hver knap
  • Opkald isSelected () på hver knap for at afgøre, om den er valgt
  • Returner en henvisning til den knap, der returnerede sandt
  • Eller ring, hvis du har brug for handlingskommandoen getActionCommand () på knappen

Hvis dette ligner mange trin bare for at få en knapreference, skal du læse videre. Jeg tror ButtonGroupimplementering er fundamentalt forkert. ButtonGroup holder en henvisning til den valgte knaps model, når den faktisk skal holde en henvisning til selve knappen. Desuden siden getSelection () henter den valgte knaps metode, kan du tro, at den tilsvarende setter-metode er setSelection (), men det er det ikke: det er setSelected (). Nu, setSelected () har et stort problem. Dens argumenter er en ButtonModel og en boolsk. Hvis du ringer setSelected () på en ButtonGroup og videregive en knaps model, der ikke er en del af gruppen og rigtigt som argumenter bliver denne knap valgt, og alle knapper i gruppen bliver ikke markeret. Med andre ord, ButtonGroup har beføjelse til at vælge eller fravælge en knap, der er videregivet til dens metode, selvom knappen ikke har noget at gøre med gruppen. Denne adfærd opstår på grund af setSelected () i ButtonGroup kontrollerer ikke, om ButtonModel reference modtaget som et argument repræsenterer en knap i gruppen. Og fordi metoden håndhæver enkelt valg, fravælger den faktisk sine egne knapper for at vælge en, der ikke er relateret til gruppen.

Denne bestemmelse i ButtonGroup dokumentation er endnu mere interessant:

Der er ingen måde at dreje en knap programmatisk til 'fra' for at rydde knapgruppen. For at give udseendet som 'ingen valgt' skal du tilføje en usynlig radioknap til gruppen og derefter vælge denne knap programmatisk for at slå alle de viste radioknapper fra. For eksempel kunne en normal knap med etiketten 'ingen' kobles til at vælge den usynlige radioknap.

Nå, ikke rigtig. Du kan bruge en hvilken som helst knap, der sidder hvor som helst i din applikation, synlig eller ej, og endda deaktiveret. Ja, du kan endda bruge knapgruppen til at vælge en deaktiveret knap uden for gruppen, og den vil stadig fravælge alle dens knapper. For at få referencer til alle knapperne i gruppen skal du kalde det latterlige getElements (). Hvad "elementer" har at gøre med ButtonGroup er nogens gæt. Navnet var sandsynligvis inspireret af Optælling klassens metoder (hasMoreElements () og nextElement ()), men getElements () klart burde have været navngivet getButtons (). En knapgruppe grupperer knapper, ikke elementer.

Løsning: JButtonGroup

Af alle disse grunde ville jeg implementere en ny klasse, der ville rette fejlene i ButtonGroup og giver brugeren en vis funktionalitet og bekvemmelighed. Jeg måtte beslutte, om klassen skulle være en ny klasse eller arve fra ButtonGroup. Alle de tidligere argumenter antyder, at du opretter en ny klasse snarere end en ButtonGroup underklasse. Men den ButtonModel interface kræver en metode setGroup () det tager en ButtonGroup argument. Medmindre jeg også var klar til at genimplementere knapmodeller, var min eneste mulighed at underklasse ButtonGroup og tilsidesætter de fleste af dens metoder. Apropos ButtonModel interface, bemærker fraværet af en kaldet metode getGroup ().

Et andet problem, jeg ikke har nævnt, er det ButtonGroup internt holder henvisninger til sine knapper i en Vektor. Således bliver det unødvendigt synkroniseret Vektoroverhead, når det skal bruge en ArrayList, da klassen selv ikke er trådsikker, og Swing alligevel er engevindet. Den beskyttede variabel knapper erklæres en Vektor type og ikke Liste som man kunne forvente af god programmeringsstil. Således kunne jeg ikke genimplementere variablen som en ArrayList; og fordi jeg ville ringe super.add () og super.remove (), Jeg kunne ikke skjule superklassevariablen. Så jeg opgav problemet.

Jeg foreslår klassen JButtonGroup, i tone med de fleste af Swing-klassens navne. Klassen tilsidesætter de fleste metoder i ButtonGroup og giver yderligere bekvemmelighed metoder. Den indeholder en henvisning til den aktuelt valgte knap, som du kan hente med et simpelt opkald til getSelected (). Tak til ButtonGroup's dårlige implementering, kunne jeg navngive min metode getSelected (), siden getSelection () er metoden, der returnerer knapmodellen.

Følgende er JButtonGroup's metoder.

For det første lavede jeg to ændringer til tilføje() metode: Hvis den knap, der skal tilføjes, allerede findes i gruppen, vender metoden tilbage. Således kan du ikke tilføje en knap til en gruppe mere end én gang. Med ButtonGroup, kan du oprette en JRadioButton og tilføj det 10 gange til gruppen. Ringer getButtonCount () returnerer derefter 10. Dette skulle ikke ske, så jeg tillader ikke duplikatreferencer. Derefter, hvis den tilføjede knap tidligere var valgt, bliver den den valgte knap (dette er standardadfærden i ButtonGroup, hvilket er rimeligt, så jeg tilsidesatte det ikke). Det valgt Knappen variabel er en reference til den aktuelt valgte knap i gruppen:

public void add (AbstractButton knap) knapper. indeholder (knap)) retur; super.add (knap); hvis (getSelection () == button.getModel ()) valgtButton = knap; 

Den overbelastede tilføje() metode tilføjer en hel række knapper til gruppen. Det er nyttigt, når du gemmer knapreferencer i en matrix til blokbehandling (dvs. indstilling af grænser, tilføjelse af handlingslyttere osv.):

offentlig ugyldig tilføj (AbstractButton [] knapper) {hvis (knapper == null) returnerer; for (int i = 0; i

Følgende to metoder fjerner en knap eller en række knapper fra gruppen:

offentlig tomrum fjern (AbstractButton-knap) {hvis (knap! = null) {hvis (valgtKnap == knap) valgtKnap = null; super.remove (knap); }} public void remove (AbstractButton [] buttons) {if (buttons == null) return; for (int i = 0; i

Herefter den første setSelected () metode lader dig indstille en knaps valgtilstand ved at videregive knapreferencen i stedet for dens model. Den anden metode tilsidesætter den tilsvarende setSelected () i ButtonGroup for at sikre, at gruppen kun kan vælge eller fravælge en knap, der tilhører gruppen:

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

Det getButton () metode henter en henvisning til den knap, hvis model er givet. setSelected () bruger denne metode til at hente den knap, der skal vælges, givet sin model. Hvis modellen, der sendes til metoden, tilhører en knap uden for gruppen, nul returneres. Denne metode skal eksistere i ButtonModel implementeringer, men desværre gør det ikke:

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

getSelected () og isSelected () er de mest enkle og sandsynligvis mest nyttige metoder til JButtonGroup klasse. getSelected () returnerer en henvisning til den valgte knap, og isSelected () overbelaster metoden med samme navn i ButtonGroup at tage en knapreference:

offentlig AbstractButton getSelected () {return valgtButton; } public boolean isSelected (AbstractButton button) {return button == selectedButton; } 

Denne metode kontrollerer, om en knap er en del af gruppen:

public boolean indeholder (AbstractButton-knap) {return-knapper. indeholder (knap); } 

Du forventer en navngivet metode getButtons () i en ButtonGroup klasse. Det returnerer en uforanderlig liste, der indeholder referencer til knapperne i gruppen. Den uforanderlige liste forhindrer tilføjelse eller fjernelse af knapper uden at gå gennem knappegruppens metoder. getElements () i ButtonGroup ikke kun har et helt uinspireret navn, men det returnerer et Optælling, som er en forældet klasse, du ikke bør bruge. Collections Framework indeholder alt hvad du behøver for at undgå optællinger. Sådan her getButtons () returnerer en uforanderlig liste:

offentlig liste getButtons () {returner Collections.unmodifiableList (knapper); } 

Forbedre ButtonGroup

Det JButtonGroup klasse tilbyder et bedre og mere praktisk alternativ til gyngen ButtonGroup klasse, mens du bevarer al superklassens funktionalitet.

Daniel Tofan er postdoktor i Chemistry Department ved State University of New York, Stony Brook. Hans arbejde involverer udvikling af kernedelen af ​​et kursusledelsessystem med anvendelse i kemi. Han er Sun-certificeret programmør for Java 2-platformen og har en ph.d. i kemi.

Lær mere om dette emne

  • Download kildekoden, der ledsager denne artikel

    //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-dokumentation

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

  • ButtonGroup klasse

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

  • Se alle forrige Java-tip og indsend din egen

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

  • Gennemse AWT / Swing sektion af JavaWorld 's Aktuelt indeks

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

  • Gennemse Foundation klasser sektion af JavaWorld 's Aktuelt indeks

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

  • Gennemse Design af brugergrænseflade sektion af JavaWorld 's Aktuelt indeks

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

  • Besøg JavaWorld Forum

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

  • Tilmeld dig JavaWorld 's gratis ugentlige e-mail-nyhedsbreve

    //www.javaworld.com/subscribe

Denne historie, "Java Tip 142: Pushing JButtonGroup" blev oprindeligt udgivet af JavaWorld.