1、Java 技 巧 : 推 动 JButtonGroup发 布 时 间 : 2005-01-19 08:00:00 来 源 : 作 者 : 点 击 : 283摘要 Swing 的 ButtonGroup 类允许单选按钮分组以保证单一选择;但是,这个实现引起了许多问题标记。你不能检索到组中当前选择的按钮的引用,并且类允许你选择或者取消选择通过引用访问的任何按钮,不是组中成员也可。本技巧描述了当提供便利的、使得JButtonGroup 更加易于使用的方法时 JButtonGroup 子类ButtonGroup 如何提供更为可靠的实现。 Swing 有许多有用的类,这些类可简化绘图用户界面(GUI)的
2、开发。 但是,其中有一些类实现起来不那么容易,如 ButtonGroup。本文解释了为什么 ButtonGroup 难于设计并提供了一个代替的类,JButtonGroup,它继承了 ButtonGroup 并解决了 ButtonGroup 的某些问题。 注意: 你可从 Resources 处下载本文的源代码。 ButtonGroup 突破口 在开发 Swing GUI 经常会出现这样的情况:你建立了一个表格来收集关于输入到数据库或者保存到文件中的数据项。这个表格可能包括文本框、单选按钮和其他窗口小部件。你使用 ButtonGroup 类将所有需要单一选择的单选按钮分组。当表格设计准备好了的时候
3、,你开始填写表格数据。你遇到一组单选按钮,你需要知道组中哪个按钮被选了这样你就能储存恰当的信息到数据库或者文件中。你现在卡住了。为什么? ButtonGroup 类不提供到组中当前所选按钮的引用。 ButtonGroup 有一个 getSelection()方法,它返回所选按钮的模型(作为 ButtonModel 类型),但不是按钮本身。现在,如果你能够从模型中得到按钮引用那也好,但是你得不到。ButtonModel 接口和它的实现类不允许你从它的模型中检索按钮引用。那你怎么办?你仔细看看 ButtonGroup 文档你会看到 getActionCommand()方法。你想想如果你使用按钮旁边
4、显示文本的 String 来例示 JRadioButton 的话,你调用按钮上的 getActionCommand(),构造器中的文本就会返回。你可能会以为你仍然使用代码工作,因为虽然你没有按钮引用,至少你有了它的文本而且仍然知道所选的按钮。 好,太棒了!可奇怪的是,你的代码在运行过程出现NullPointerException。为什么?因为 ButtonModel 中的getActionCommand() 返回 null。如果你赌(像我一样) getActionCommand()在按钮上的调用或者在模型上的调用也出现了同样的异常(许多其他的方法都是这样,如 isSelected()、isEn
5、abled()、或者 getMnemonic()),那你就错了。如果你确实没有调用按钮上的 setActionCommand(),你没有在它的模型中设置作用指令的话, 获得器方法就会返回对于模型的 null 。但是,调用按钮上的获得器方法,它确实会返回按钮文本。下面就是AbstractButton 中的 getActionCommand() ,继承于 Swing 中的所有按钮类: public String getActionCommand() String ac = getModel().getActionCommand();if(ac = null) ac = getText();retu
6、rn ac;设置和获得行为指令之间的矛盾是无法接受的。如果当行为指令为空时,AbstractButton 中的 setText()设置模型的行为指令到按钮文本上,你就能避免这种矛盾。毕竟,除非 setActionCommand()使用某些 String 参数(非空)来调用,否则按钮文本总是被按钮本身当作行为指令。为什么模型表现如此不同呢? 当你的代码需要到 ButtonGroup 中的当前所选按钮的引用,你得遵循下列步骤,这些步骤中没有一个调用了 getSelection(): ?调用 ButtonGroup 上的 getElements(),它将返回一个Enumeration ?通过 Enu
7、meration 迭代,以便得到每个按钮的引用 ?调用每个按钮上的 isSelected(),判断出它是否被选 ?返回引用到返回 true 的按钮上 ?或者,如果你需要行为指令,调用按钮上的 getActionCommand() 如果这些步骤看起来过于繁杂的话,请往下读。我认为 ButtonGroup 的实现根本上是错误的。它保存的是到所选按钮模型的引用,实际上它应该保存到所选按钮的引用。而且, getSelection()检索所选按钮的方法,你会认为相应的设置器方法就是 setSelection(),可实际上它不是,而是 setSelected()。现在,setSelected()有一个大问
8、题。它的参数是 ButtonModel 和布尔值。如果你调用 ButtonGroup 上的 setSelected(),并传递按钮的非该组成员的模型和参数 true , 那么该按钮被选,组中其他按钮则为非被选。换句话说, ButtonGroup 有权决定传递到它的方法处的按钮选择还是不选择,即使该按钮与该组毫无关系。这种行为是存在的,因为 ButtonGroup 中的 setSelected()不检查作为参数收到的 ButtonModel 引用是否代表组中的按钮。而且由于该方法坚持单一选择,它实际上不选自己的按钮而选择与该组无关的按钮。 这种约定在 ButtonGroup 文档中表现得更为有趣
9、: 我们无法为了清除按钮组而程序化地关闭按钮。为了表现出“非被选”,添加一个可见的单选按钮到组中,然后程序化地选择该按钮来关闭所有显示的单选按钮。例如:可安装带有卷标none的普通按钮来选择可见的单选按钮。 很好,但是这不现实:你可以使用任何按钮,放在你的应用程序中的任何位置,可见或不可见,设置也可以是无效的。是的,你甚至可以使用按钮组来选择一个组外的无效按钮,并且它仍然还可以取消对它所有按钮的选择。要得到组中所有按钮的引用,你得调用这个机械的 getElements()。“elements“ 能对 ButtonGroup 做些什么,大家都知道。getElements() ,的命名可能就是受
10、Enumeration 类的方法 (hasMoreElements()和 nextElement()所启发,但是getElements()无疑应该命名为 getButtons()。因为按钮组分组的是按钮,而不是元素。 你可以在本文最后一页的 JavaWorld Talkback 发表你的评论,看看你的同行们会如何回答你。Solution: JButtonGroup 由于以上原因,我想要实现一个新的类,这个类可以解决ButtonGroup 中的错误并且提供一些功能和便利给用户。我不得不决定创建一个新类还是由 ButtonGroup 衍生。所有上述的讨论都建议我创建一个新类而不是创建 Button
11、Group 的子类。但是, ButtonModel 接口需要一个能够接受 ButtonGroup 参数的方法setGroup()。除非我也准备重新实现按钮模型,否则我只能选择创建 ButtonGroup 子类并用它覆盖 ButtonGroup 的大部分方法。说到 ButtonModel 接口,注意它不包括方法 getGroup()。 我没有提到的另一个方案就是 ButtonGroup 内部将到它的按钮的引用保存在 Vector 中。当他使用 ArrayList 时,因为这个类本身为非线程安全,同时 Swing 是单线程的。因此,它没有必要得到同步的Vector 的系统开销。但是,受保护的变量
12、buttons 公告为 Vector 类型,而不是你希望的良好编程的类型 List 。因此,我不能把这个变量当作 ArrayList 来实现;并且因为我想调用 super.add()和super.remove(),我不能隐藏超类变量。所以我放弃了这个方案。 我假设类 JButtonGroup 与大部分的 Swing 类的名称保持一致。该类可遍历 ButtonGroup 中的大部分方法并能提供额外的便利的方法。它保存到当前所选按钮的引用,你可以使用一个简单的到getSelected()的调用来检索它。由于 ButtonGroup 的实现能力很差,我可以将我的方法命名为 getSelected()
13、,因为 getSelection()方法返回的是按钮模型。 下面就是 JButtonGroup 的方法。 首先,我得对 add()方法作两个修改:如果需要添加的按钮在组中,方法返回。因此,你只能添加按钮到组中一次。有了ButtonGroup,你可以创建一个 JRadioButton 并将它添加到组中10 次。调用 getButtonCount() 则会返回 10。这一般不会这样用,所以我不允许复制的引用。这样,如果所添加的按钮前面选择过,那它就会变成所选按钮(这种行为在 ButtonGroup 中是默认的,这是合理的。所以我不想修改它)。变量 selectedButton 是到组中当前所选按钮
14、的引用: public void add(AbstractButton button) if (button = null | buttons.contains(button) return;super.add(button);if (getSelection() = button.getModel() selectedButton = button;超载的 add()方法添加了整个排列的按钮到组中。当你想储存按钮引用到排列中以便于块处理(如设置边界,添加行为听取器等等),这是很有用的: public void add(AbstractButton buttons)if (buttons =
15、null) return;for (int i=0; ibuttons.length; i+)add(buttonsi);下面的两个方法删除了组中的一个按钮或者一个排列的按钮: public void remove(AbstractButton button)if (button != null)if (selectedButton = button) selectedButton = null;super.remove(button);public void remove(AbstractButton buttons)if (buttons = null) return;for (int i
16、=0; ibuttons.length; i+)remove(buttonsi);今后,第一个 setSelected()方法允许你通过按钮引用而不是通过按钮模型来设置按钮的选择状态。第二个方法顶替了 ButtonGroup 中相应的 setSelected(),以保证组只能选择或者取消选择组中的按钮:public void setSelected(AbstractButton button, boolean selected)if (button != null if (getSelection() = button.getModel() selectedButton = button;pu
17、blic void setSelected(ButtonModel model, boolean selected)AbstractButton button = getButton(model);if (buttons.contains(button) super.setSelected(model, selected);getButton()方法检索了到模型给出的按钮的引用。setSelected()使用这个方法来检索 所选的要求给出它的模型的按钮。如果传递到方法的模型属于组外的按钮,则 getButton()返回 null 。这个方法应该存在于 ButtonModel 实现中,不幸的是它
18、没有: 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()和 isSelected()是 JButtonGroup 类中最简单的,也是最有用的方法。getSelected()返回到所选按钮的引用并且isSelected() 超载
19、ButtonGroup 中的同名方法来得到按钮引用: public AbstractButton getSelected()return selectedButton;public boolean isSelected(AbstractButton button)return button = selectedButton;该方法检查按钮是否为组中成员: public boolean contains(AbstractButton button)return buttons.contains(button);你可能希望在 ButtonGroup 类中有一个名为 getButtons()的方法。
20、它返回一个固定的表单,这个表单包含到组中按钮的引用。固定的表单可以防止不经过按钮组的方法就添加或者删除按钮。ButtonGroup 中的 getElements()不但名字缺乏创见,而且它返回的Enumeration 是你不应该使用的已废弃的类。收集框架(Collections Framework )提供了你需要避免列举的每件事物。下面的程序显示了 getButtons() 如何返回一个固定不变的表单: public List getButtons()return Collections.unmodifiableList(buttons);改进 ButtonGroup JButtonGroup 类在保留所有超类的功能的同时,为 Swing ButtonGroup 类提供了一个更好更便利的备选类。 关于作者 Daniel Tofan 是纽约 Stony Brook 州立大学化学部的一个博士后的助手。他的工作包括使用化学中的应用程序开发课程管理系统的核心部分。他也是 Sun 认证的 Java 2 平台的程序员同时还是一名化学博士。