Съвет за Java 142: Натискане на JButtonGroup

Swing има много полезни класове, които улесняват разработването на графичен потребителски интерфейс (GUI). Някои от тези класове обаче не са добре приложени. Един пример за такъв клас е ButtonGroup. Тази статия обяснява защо ButtonGroupе лошо проектирана и предлага заместващ клас JButtonGroup, който наследява ButtonGroupи решава някои от проблемите му.

Забележка: Можете да изтеглите изходния код на тази статия от ресурси.

Бутон Групови дупки

Ето един често срещан сценарий при разработването на графичен потребителски интерфейс на Swing: Изграждате формуляр за събиране на данни за елементи, които някой ще въведе в база данни или ще запише във файл. Формулярът може да съдържа текстови полета, квадратчета за отметка, радиобутони и други приспособления. Използвате ButtonGroupкласа, за да групирате всички радио бутони, които се нуждаят от единичен избор. Когато дизайнът на формуляра е готов, започвате да прилагате данните на формуляра. Срещате набора от радио бутони и трябва да знаете кой бутон в групата е избран, за да можете да съхранявате подходящата информация в базата данни или файл. Вече сте заседнали. Защо? В ButtonGroupклас не ви дава препратка към бутона избрания в момента в групата.

ButtonGroupима getSelection()метод, който връща модела на избрания бутон (като ButtonModelтип), а не самия бутон. Това може да е наред, ако можете да получите справка за бутона от неговия модел, но не можете. В ButtonModelинтерфейса и класовете по прилагането му не позволяват да извличате референтен бутон от своя модел. Та какво правиш? Поглеждате ButtonGroupдокументацията и виждате getActionCommand()метода. Спомняте си, че ако създадете инстанция с JRadioButtonс Stringза текста, показан до бутона, и след това извикате getActionCommand()бутона, текстът в конструктора се връща. Може би си мислите, че все още можете да продължите с кода, защото дори ако нямате препратка към бутона, поне имате неговия текст и пак знаете избрания бутон.

Е, изненада! Вашият код се счупва по време на изпълнение с NullPointerException. Защо? Защото getActionCommand()при ButtonModelвръщания null. Ако заложите (както направих аз), че getActionCommand()води до същия резултат, дали призова бутона или от модела (какъвто е случаят с много други методи, като например isSelected(), isEnabled()или getMnemonic()), сте загубили. Ако не извикате изрично setActionCommand()бутона, не задавате командата за действие в неговия модел и методът getter се връща nullза модела. Методът getter обаче връща текста на бутона при извикване на бутона. Ето getActionCommand()метода в AbstractButton, наследен от всички класове бутони в Swing:

публичен String getActionCommand () {String ac = getModel (). getActionCommand (); if (ac == null) {ac = getText (); } върнете променлив ток; }

Това несъответствие в настройването и получаването на командата за действие е неприемливо. Можете да избегнете тази ситуация, ако setText()in AbstractButtonзадава командата за действие на модела на текста на бутона, когато командата за действие е нула. В края на краищата, освен ако не setActionCommand()е изрично извикан с някакъв Stringаргумент (не null), текстът на бутона се счита за команда за действие от самия бутон. Защо моделът трябва да се държи по различен начин?

Когато вашият код се нуждае от препратка към избрания в момента бутон в ButtonGroup, трябва да изпълните следните стъпки, нито една от които не включва извикване getSelection():

  • Обадете се getElements()на ButtonGroup, която връщаEnumeration
  • Повтаряйте през, за Enumerationда получите препратка към всеки бутон
  • Обадете се isSelected()на всеки бутон, за да определите дали е избран
  • Върнете препратка към бутона, който е върнал true
  • Или, ако имате нужда от командата за действие, извикайте getActionCommand()бутона

Ако това изглежда като много стъпки, само за да получите справка за бутон, прочетете. Вярвам, че ButtonGroupприлагането е фундаментално погрешно. ButtonGroupзапазва препратка към модела на избрания бутон, когато всъщност трябва да запази препратка към самия бутон. Освен това, тъй като getSelection()извлича метода на избрания бутон, може да си помислите, че съответният метод за настройка е setSelection(), но не е: това е setSelected(). Сега setSelected()има голям проблем. Аргументите му са a ButtonModelи boolean. Ако се обадите setSelected()на ButtonGroupи предадете модел на бутон, който не е част от групата и trueкато аргументи, този бутон става избран и всички бутони в групата стават отменени. С други думи,ButtonGroupима силата да избира или премахва избора на който и да е бутон, предаден на метода му, въпреки че бутонът няма нищо общо с групата. Това поведение се случва, защото setSelected()in ButtonGroupне проверява дали ButtonModelпрепратката, получена като аргумент, представлява бутон в групата. И тъй като методът налага единичен избор, той всъщност отменя собствените си бутони, за да избере един, който не е свързан с групата.

Тази уговорка в ButtonGroupдокументацията е още по-интересна:

Няма начин програмен бутон да се изключи, за да се изчисти групата бутони. За да изглежда „никой не е избран“, добавете невидим радио бутон към групата и след това програмно изберете този бутон, за да изключите всички показани радио бутони. Например, нормален бутон с етикет „няма“ може да бъде свързан, за да изберете невидимия радио бутон.

Е, не наистина. Можете да използвате всеки бутон, седнал навсякъде във вашето приложение, видим или не, и дори деактивиран. Да, можете дори да използвате групата бутони, за да изберете деактивиран бутон извън групата и той все още ще премахне избора на всички нейни бутони. За да получите препратки към всички бутони в групата, трябва да се обадите на смешния getElements(). Какво общо имат „елементите“ ButtonGroupе никой да предположи. Името вероятно е вдъхновено от Enumerationметодите ( hasMoreElements()и nextElement()) на класа , но getElements()очевидно е трябвало да бъде назовано getButtons(). Група бутони групира бутони, а не елементи.

Решение: JButtonGroup

Поради всички тези причини исках да внедря нов клас, който да коригира грешките ButtonGroupи да предостави известна функционалност и удобство на потребителя. Трябваше да реша дали класът трябва да бъде нов клас или да наследи от ButtonGroup. Всички предишни аргументи предполагат създаване на нов клас, а не на ButtonGroupподклас. Въпреки това, ButtonModelинтерфейсът изисква метод setGroup(), който взема ButtonGroupаргумент. Освен ако не бях готов да пренасоча и модели на бутони, единствената ми възможност беше да подкласирам ButtonGroupи да отменя повечето от методите му. Говорейки за ButtonModelинтерфейса, забележете липсата на метод, наречен getGroup().

Друг проблем, който не съм споменал, е, че ButtonGroupвътрешно запазва препратки към своите бутони в Vector. По този начин той ненужно получава Vectorрежийните на синхронизираните , когато трябва да използва ArrayList, тъй като самият клас не е безопасен за нишките и Swing е така или иначе. Защитената променлива обаче buttonsе обявена за Vectorтип, а не Listкакто можете да очаквате от добър стил на програмиране. По този начин не можах да изпълня променливата като ArrayList; и тъй като исках да се обадя super.add()и super.remove(), не можах да скрия променливата superclass. Затова изоставих въпроса.

Предлагам класа JButtonGroup, в тон с повечето имена на класа Swing. Класът заменя повечето методи ButtonGroupи предоставя допълнителни удобни методи. Той запазва препратка към избрания в момента бутон, който можете да извлечете с просто повикване getSelected(). Благодарение на ButtonGroupлошото изпълнение, бих могъл да назова метода си getSelected(), тъй като getSelection()е методът, който връща модела на бутона.

Следват JButtonGroupметодите на.

Първо направих две модификации на add()метода: Ако бутонът за добавяне вече е в групата, методът се връща. По този начин не можете да добавите бутон към група повече от веднъж. С ButtonGroup, можете да създадете JRadioButtonи да го добавите 10 пъти към групата. getButtonCount()Тогава извикването ще се върне 10. Това не трябва да се случва, така че не позволявам дублирани препратки. Тогава, ако добавеният бутон е бил предварително избран, той се превръща в избрания бутон (това е поведението по подразбиране в ButtonGroup, което е разумно, така че не го замених). Най- selectedButtonпроменлива е препратка към текущо избрания бутон в групата:

public void add (бутон AbstractButton) бутони. съдържа (бутон)) return; super.add (бутон); ако (getSelection () == button.getModel ()) selectedButton = бутон;  

Претовареният add()метод добавя цял набор от бутони към групата. Полезно е, когато съхранявате препратки към бутони в масив за блокова обработка (т.е. задаване на граници, добавяне на слушатели на действия и т.н.):

публично невалидно добавяне (бутони AbstractButton []) {if (бутони == нула) return; за (int i = 0; i
   
    

Следните два метода премахват бутон или масив от бутони от групата:

public void remove (AbstractButton button) {if (button! = null) {if (selectedButton == button) selectedButton = null; super.remove (бутон); }} public void remove (AbstractButton [] бутони) {if (бутони == null) return; за (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 .