Programování

Java Tip 142: Zatlačení JButtonGroup

Swing má mnoho užitečných tříd, které usnadňují vývoj grafického uživatelského rozhraní (GUI). Některé z těchto tříd však nejsou dobře implementovány. Jedním příkladem takové třídy je Skupina tlačítek. Tento článek vysvětluje proč Skupina tlačítek je špatně navržen a nabízí náhradní třídu, JButtonGroup, který dědí z Skupina tlačítek a opravuje některé z jeho problémů.

Poznámka: Zdrojový kód tohoto článku si můžete stáhnout ze zdrojů.

Otvory pro knoflíkové skupiny

Tady je běžný scénář ve vývoji Swing GUI: Vytvoříte formulář pro shromažďování dat o položkách, které někdo zadá do databáze nebo uloží do souboru. Formulář může obsahovat textová pole, zaškrtávací políčka, přepínače a další widgety. Používáte Skupina tlačítek třídy seskupit všechny přepínače, které vyžadují jeden výběr. Když je návrh formuláře připraven, začnete implementovat data formuláře. Setkáte se se sadou přepínačů a potřebujete vědět, které tlačítko ve skupině bylo vybráno, abyste mohli příslušné informace uložit do databáze nebo souboru. Nyní jste uvízl. Proč? The Skupina tlačítek třída vám nedává odkaz na tlačítko aktuálně vybrané ve skupině.

Skupina tlačítekgetSelection () metoda, která vrací model vybraného tlačítka (jako a ButtonModel typ), nikoli samotné tlačítko. Nyní by to mohlo být v pořádku, pokud byste mohli získat odkaz na tlačítko z jeho modelu, ale nemůžete. The ButtonModel rozhraní a jeho implementační třídy vám neumožňují načíst odkaz na tlačítko z jeho modelu. Tak co děláš? Podíváte se na Skupina tlačítek dokumentaci a viz getActionCommand () metoda. Pamatujete si, že pokud vytvoříte instanci a JRadioButton s Tětiva pro text zobrazený vedle tlačítka a poté zavoláte getActionCommand () na tlačítku se vrátí text v konstruktoru. Možná si myslíte, že můžete v kódu pokračovat, protože i když nemáte odkaz na tlačítko, máte alespoň jeho text a stále znáte vybrané tlačítko.

No, překvapení! Váš kód se za běhu rozbije s NullPointerException. Proč? Protože getActionCommand () v ButtonModel se vrací nula. Pokud vsadíte (jako já), že getActionCommand () produkuje stejný výsledek, ať už je vyvolán na tlačítku nebo na modelu (což je případ mnoho jiné metody, jako např isSelected (), je povoleno()nebo getMnemonic ()), prohrál jsi. Pokud výslovně nezavoláte setActionCommand () na tlačítku nenastavíte příkaz action v jeho modelu a vrátí se metoda getter nula pro model. Metoda getru dělá při vyvolání tlačítka vrátí text tlačítka. Zde je getActionCommand () metoda v AbstractButton, zděděné všemi třídami tlačítek v Swingu:

 public String getActionCommand () {String ac = getModel (). getActionCommand (); if (ac == null) {ac = getText (); } vrátit ac; } 

Tato nekonzistence v nastavení a získávání příkazu akce je nepřijatelná. Této situaci se můžete vyhnout, pokud setText () v AbstractButton nastaví příkaz akce modelu na text tlačítka, když je příkaz akce null. Koneckonců, pokud setActionCommand () je výslovně volána s některými Tětiva argument (ne null), text tlačítka je považováno za akční příkaz samotným tlačítkem. Proč by se model měl chovat jinak?

Když váš kód potřebuje odkaz na aktuálně vybrané tlačítko v Skupina tlačítek, musíte postupovat podle těchto kroků, z nichž žádný nezahrnuje volání getSelection ():

  • Volání getElements () na Skupina tlačítek, který vrací Výčet
  • Iterovat skrz Výčet získat odkaz na každé tlačítko
  • Volání isSelected () na každém tlačítku zjistit, zda je vybráno
  • Vrátí odkaz na tlačítko, které vrátilo true
  • Nebo pokud potřebujete příkaz akce, zavolejte getActionCommand () na tlačítku

Pokud to vypadá jako spousta kroků, jen abyste získali odkaz na tlačítko, přečtěte si dále. věřím Skupina tlačítekImplementace je zásadně špatná. Skupina tlačítek udržuje odkaz na model vybraného tlačítka, když by měl ve skutečnosti ponechat odkaz na samotné tlačítko. Kromě toho od getSelection () načte metodu vybraného tlačítka, možná si myslíte, že je odpovídající metoda setteru setSelection (), ale není: je setSelected (). Nyní, setSelected () má velký problém. Jeho argumenty jsou a ButtonModel a booleovský. Pokud zavoláte setSelected () na Skupina tlačítek a předat knoflíkový model, který není součástí skupiny a skutečný jako argumenty bude toto tlačítko vybráno a všechna tlačítka ve skupině budou zrušena. Jinými slovy, Skupina tlačítek má moc vybrat nebo zrušit výběr libovolného tlačítka předaného jeho metodě, přestože tlačítko nemá nic společného se skupinou. K tomuto chování dochází, protože setSelected () v Skupina tlačítek nekontroluje, zda ButtonModel reference přijatá jako argument představuje tlačítko ve skupině. A protože metoda vynucuje jeden výběr, ve skutečnosti zruší výběr vlastních tlačítek pro výběr jednoho, který nesouvisí se skupinou.

Toto ustanovení v Skupina tlačítek dokumentace je ještě zajímavější:

Neexistuje způsob, jak programově vypnout tlačítko, aby se skupina tlačítek vymazala. Chcete-li vypadat jako „není vybráno“, přidejte do skupiny neviditelný přepínač a poté programově výběrem tohoto tlačítka vypněte všechny zobrazené přepínače. Například normální tlačítko se štítkem „none“ lze připojit k výběru neviditelného přepínače.

No ne tak úplně. Můžete použít jakékoli tlačítko, sedět kdekoli ve své aplikaci, ať už je viditelné nebo ne, a dokonce i deaktivované. Ano, můžete dokonce použít skupinu tlačítek k výběru deaktivovaného tlačítka mimo skupinu a stále zruší výběr všech jejích tlačítek. Chcete-li získat odkazy na všechna tlačítka ve skupině, musíte zavolat absurdní getElements (). S čím mají „prvky“ společného Skupina tlačítek je někdo hádat. Jméno bylo pravděpodobně inspirováno Výčet metody třídy (hasMoreElements () a nextElement ()), ale getElements () jasně měl být pojmenován getButtons (). Skupina tlačítek seskupuje tlačítka, nikoli prvky.

Řešení: JButtonGroup

Ze všech těchto důvodů jsem chtěl implementovat novou třídu, která by opravila chyby Skupina tlačítek a poskytovat uživateli určité funkce a pohodlí. Musel jsem se rozhodnout, zda by měla být třída novou třídou nebo zdědit Skupina tlačítek. Všechny předchozí argumenty naznačují vytvoření nové třídy spíše než a Skupina tlačítek podtřída. Nicméně ButtonModel rozhraní vyžaduje metodu setGroup () to trvá a Skupina tlačítek argument. Pokud jsem nebyl připraven znovu implementovat i knoflíkové modely, byla moje jediná možnost podtřída Skupina tlačítek a přepsat většinu svých metod. Když už mluvíme o ButtonModel rozhraní, všimněte si absence volané metody getGroup ().

Jeden další problém, který jsem nezmínil, je ten Skupina tlačítek interně udržuje odkazy na svá tlačítka v a Vektor. Zbytečně tedy synchronizuje Vektorrežii, kdy by měla použít ArrayList, protože třída sama o sobě není bezpečná pro vlákna a Swing je stejně jedno vlákno. Chráněná proměnná tlačítka je prohlášen za Vektor typ, a ne Seznam jak můžete očekávat od dobrého programovacího stylu. Proto jsem nemohl znovu implementovat proměnnou jako ArrayList; a protože jsem chtěl zavolat super.add () a super.odstranit (), Nemohl jsem skrýt proměnnou nadtřídy. Takže jsem problém opustil.

Navrhuji třídu JButtonGroup, tónově s většinou názvů tříd Swing. Třída přepíše většinu metod v Skupina tlačítek a poskytuje další pohodlné metody. Udržuje odkaz na aktuálně vybrané tlačítko, které můžete načíst jednoduchým voláním getSelected (). Díky Skupina tlačítekšpatná implementace, mohl bych pojmenovat svoji metodu getSelected (), od té doby getSelection () je metoda, která vrací model tlačítka.

Následující jsou JButtonGroupmetody.

Nejprve jsem provedl dvě úpravy přidat() metoda: Pokud je tlačítko, které se má přidat, již ve skupině, vrátí se metoda. Nelze tedy přidat tlačítko do skupiny více než jednou. S Skupina tlačítek, můžete vytvořit JRadioButton a přidejte jej 10krát do skupiny. Povolání getButtonCount () pak se vrátí 10. To by se nemělo stát, takže nepovoluji duplicitní odkazy. Pak, pokud bylo přidané tlačítko dříve vybráno, stane se vybraným tlačítkem (toto je výchozí chování v Skupina tlačítek, což je rozumné, takže jsem to nepřepsal). The vybrané tlačítko proměnná je odkaz na aktuálně vybrané tlačítko ve skupině:

public void add (tlačítko AbstractButton) buttons.contains (button)) return; super.add (tlačítko); if (getSelection () == button.getModel ()) selectedButton = button; 

Přetížený přidat() metoda přidá do skupiny celou řadu tlačítek. Je užitečné, když uložíte odkazy na tlačítka v poli pro zpracování bloku (tj. Nastavení okrajů, přidání posluchačů akcí atd.):

public void add (AbstractButton [] tlačítka) {if (buttons == null) návrat; pro (int i = 0; i

Následující dvě metody odstraní tlačítko nebo řadu tlačítek ze skupiny:

public void remove (tlačítko AbstractButton) {if (button! = null) {if (selectedButton == button) selectedButton = null; super.odstranit (tlačítko); }} public void remove (AbstractButton [] tlačítka) {if (buttons == null) návrat; pro (int i = 0; i

Dále první setSelected () metoda umožňuje nastavit stav výběru tlačítka předáním odkazu na tlačítko namísto jeho modelu. Druhá metoda přepíše odpovídající setSelected () v Skupina tlačítek zajistit, aby skupina mohla vybrat nebo zrušit výběr pouze tlačítka, které patří do skupiny:

public void setSelected (tlačítko AbstractButton, vybráno boolean) {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 () metoda načte odkaz na tlačítko, jehož model je uveden. setSelected () používá tuto metodu k načtení tlačítka, které má být vybráno vzhledem k jeho modelu. Pokud model předaný metodě patří tlačítku mimo skupinu, nula je vrácen. Tato metoda by měla existovat v ButtonModel implementace, ale bohužel ne:

public AbstractButton getButton (model ButtonModel) {Iterator it = buttons.iterator (); while (it.hasNext ()) {AbstractButton ab = (AbstractButton) it.next (); if (ab.getModel () == model) vrátit ab; } return null; } 

getSelected () a isSelected () jsou nejjednodušší a pravděpodobně nejužitečnější metody JButtonGroup třída. getSelected () vrací odkaz na vybrané tlačítko a isSelected () přetíží metodu stejného jména v Skupina tlačítek vzít odkaz na tlačítko:

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

Tato metoda kontroluje, zda je tlačítko součástí skupiny:

public boolean obsahuje (tlačítko AbstractButton) {návrat buttons.contains (tlačítko); } 

Očekávali byste metodu s názvem getButtons () v Skupina tlačítek třída. Vrátí neměnný seznam obsahující odkazy na tlačítka ve skupině. Neměnný seznam zabrání přidání nebo odebrání tlačítka, aniž by prošel metodami skupiny tlačítek. getElements () v Skupina tlačítek má nejen zcela neinspirované jméno, ale také vrací Výčet, což je zastaralá třída, kterou byste neměli používat. Kolekce Framework poskytuje vše, co potřebujete, abyste se vyhnuli výčtu. Takhle getButtons () vrací neměnný seznam:

public List getButtons () {návrat Collections.unmodifiableList (tlačítka); } 

Vylepšete ButtonGroup

The JButtonGroup třída nabízí lepší a pohodlnější alternativu k Swingu Skupina tlačítek třídy, při zachování všech funkcí nadtřídy.

Daniel Tofan je postdoktorským spolupracovníkem na katedře chemie na State University of New York, Stony Brook. Jeho práce zahrnuje vývoj základní části systému řízení kurzu s aplikací v chemii. Je Sun Certified Programmer pro platformu Java 2 a je držitelem titulu PhD v chemii.

Další informace o tomto tématu

  • Stáhněte si zdrojový kód, který je přiložen k tomuto článku

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

  • Domovská stránka tříd Java Foundation Classes společnosti Sun Microsystems

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

  • Dokumentace k platformě Java 2 Platform, Standard Edition (J2SE) 1.4.2 API

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

  • Třída ButtonGroup

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

  • Zobrazit všechny předchozí Tipy pro Java a odešlete vlastní

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

  • Procházejte AWT / Swing část JavaWorld 's Aktuální rejstřík

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

  • Procházejte Třídy nadace část JavaWorld 's Aktuální rejstřík

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

  • Procházejte Návrh uživatelského rozhraní část JavaWorld 's Aktuální rejstřík

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

  • Navštivte fórum JavaWorld

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

  • Přihlásit se JavaWorld 's týdenní e-mailové zpravodaje zdarma

    //www.javaworld.com/subscribe

Tento příběh, „Java Tip 142: Pushing JButtonGroup“, původně publikoval JavaWorld.