当我们希望创建一个相斥的可切换组件组时我们可以使用JRadioButton。尽管由技术上来说,我们可以将一组JCheckBox组件放在一个ButtonGroup中,并且每次只有一个可以选中,但是他们看起来并不正确。至少由预定义的观感类型来看,JRadioButton与JCheckBox组件看起来是不同的,如图5-10所示。这种外观上的区别可以告诉终端用户可以期望组件的特定行为。
JRadioButton是由几方面构成的。类似于JToggleButton与JCheckBox,JRadioButton也使用一个ToggleButtonModel来表示其数据模型。他使用ButtonGroup通过AbstractButton来提供互斥的组合,并且用户界面委托是RadioButtonUI。
下面我们就来探讨如何使用JRadioButton的不同方同。
与JCheckBox以及JToggleButton类似,JRadioButton有八个构造函数:
public JRadioButton() JRadioButton aRadioButton = new JRadioButton(); public JRadioButton(Icon icon) JRadioButton aRadioButton = new JRadioButton(new DiamondIcon(Color.CYAN, false)); aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true)); public JRadioButton(Icon icon, boolean selected) JRadioButton aRadioButton = new JRadioButton(new DiamondIcon(Color.CYAN, false), true); aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true)); public JRadioButton(String text) JRadioButton aRadioButton = new JRadioButton("4 slices"); public JRadioButton(String text, boolean selected) JRadioButton aRadioButton = new JRadioButton("8 slices", true); public JRadioButton(String text, Icon icon) JRadioButton aRadioButton = new JRadioButton("12 slices", new DiamondIcon(Color.CYAN, false)); aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true)); public JRadioButton(String text, Icon icon, boolean selected) JRadioButton aRadioButton = new JRadioButton("16 slices", new DiamondIcon(Color.CYAN, false), true); aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true)); public JRadioButton(Action action) Action action = ...; JRadioButton aRadioButton = new JRadioButton(action);
每一个都允许我们定制一个或是多个标签,图标或是初始选中状态属性。除非特别指定,在标签中并没有文本,而且复选框的默认选中/未选中状态图标为未选中。在创建一组单选按钮组件之后,我们需要将每一个放在一个ButtonGroup中,从而他们可以正常工作,在组合中每次只有一个按钮可以选中。如果我们在构造函数中初始化图标,则是复选框未选中状态的图标,当复选框被选中时也显示相同的图标。我们或者是使用JCheckBox中所描述的setSelectedIcon(Icon newValue)方法初始选中图标,或者是确保图标是状态感知的并进行自动更新。
JRadioButton具有两个覆盖了父类JToggleButton的属性,如图表5-5所示。
JRadioButton属性
属性名 |
数据类型 |
访问性 |
accessibleContext |
AccessibleContext |
只读 |
UIClassID |
String |
只读 |
JRadioButton是唯一一个为了正常作用需要放在ButtonGroup中的JToggleButton子类。仅仅是创建一组单选按钮并将其放置在屏幕中是不足够的。除了将每一个单选按钮放在一个容器中之外,我们需要创建一个ButtonGroup,并且将每一个单选按钮放在相同的ButtonGroup中。一旦所有的JRadioButton项目都放在一个组合中,当一个未选中的单选按钮被选中时,ButtonGroup会使得当前被选中的单选按钮取消选中。
将一个JRaidonButton组件集合放在一个ButtonGroup中是一个基本的四步过程: 1 为组合创建一个容器
JPanel aPanel = new JPanel(new GridLayout(0,1));
2 在窗口周围放置一个边框以标识组合。这是可选的一步,但是我们通常希望放置一个边框来为用户标识组合。我们将会在第7章中了解更多关于边框的内容。
Border border = BorderFactory.createTitledBorder("Slice Count"); aPanel.setBorder(border)
3 创建一个ButtonGroup
ButtonGroup aGroup = new ButtonGroup();
4 对于每一个可选择的选项,创建一个JRadioButton,将其添加到容器中,然后将其添加到组合中。
JRadioButton aRadioButton = new JRadioButton(); aPanel.add(aRadioButton); aGroup.add(aRadioButton);
我们也许会发现整个过程,特殊是第四步,在一段时间之后会显得繁琐,特殊是当我们添加处理选中事件步骤时更是如此。列表5-5所示的助手类,具有一个静态的createRadioButtonGrouping(String elements[], String title)方法,证明是有用的。这个方法需要一个单选按钮的String的数组以及边框标题,然后在一个具有标题边框的JPanel内创建一个带有通常ButtonGroup的JRadioButton对象集合。
package net.ariel.ch05; import java.awt.Container; import java.awt.GridLayout; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.border.Border; public class RadioButtonUtils { private RadioButtonUtils() { } public static Container createRadioButtonGrouping(String elements[], String title) { JPanel panel = new JPanel(new GridLayout(0,1)); if(title != null) { Border border = BorderFactory.createTitledBorder(title); panel.setBorder(border); } ButtonGroup group = new ButtonGroup(); JRadioButton aRadioButton; for(int i=0, n=elements.length; inew JRadioButton(elements[i]); panel.add(aRadioButton); group.add(aRadioButton); } return panel; } }
现在我们可以更为简单的创建组合了,如列表5-6中的示例程序所示。
/** * */ package net.ariel.ch05; import java.awt.BorderLayout; import java.awt.Container; import java.awt.EventQueue; import javax.swing.JFrame; /** * @author mylxiaoyi * */ public class GroupRadio { private static final String sliceOptions[] = { "4 slices", "8 slices", "12 slices", "16 slices" }; private static final String crustOptions[] = { "Sicilian", "Thin Crust", "Thick Crust", "Stuffed Crust" }; /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Grouping Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container sliceContainer = RadioButtonUtils.createRadioButtonGrouping(sliceOptions, "Slice Count"); Container crustContainer = RadioButtonUtils.createRadioButtonGrouping(crustOptions, "Crust Type"); frame.add(sliceContainer, BorderLayout.WEST); frame.add(crustContainer, BorderLayout.EAST); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
当我们运行这个程序时,我们将会看到图5-11所示的结果。
与JToggleButton以及JCheckBox类似,JRadioButton支持ActionListener,ItemListener以及ChangeListener的注册。而且,对于JRadioButton而言这些监听器的用法与其他组件的用法不同。
使用ActionListener监听JRadioButton事件
对于JRadioButton,通常是将相同的ActionListener注册到ButtonGroup中的所有单选按钮上。采用这种方法,当一个单选按钮被选中时,所订阅的ActionListener就会得到通知。通过覆盖前面的createRadioButtonGrouping()方法,这个方法就可以接受一个ActionListener作为参数,并将这个监听器对象关联到他们所创建的每一个按钮之上。
public static Container createRadionButtonGrouping(String elements[], String title, ActionListener actionListener) { JPanel panel = new JPanel(new GridLayout(0, 1)); if(title != null) { Border border = BorderFactory.createTitledBorder(title); panel.setBorder(border); } ButtonGroup group = new ButtonGroup(); JRadioButton aRadioButton; for(int i=0, n=elements.length; i现在,如果使用下面的代码创建一个组合,则所创建的每一个JRadioButton组件的AcitonListener都会得到通知。这里,监听器只是输出当前选中的值。我们所选择的响应方式会有所不同。
ActionListener sliceActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { AbstractButton aButton = (AbstractButton)actionEvent.getSource(); System.out.println("Selected: " + aButton.getText()); } }; Container sliceContainer = RadioButtonUtils.createRadioButtonGrouping(sliceOptions, "Slice Count", sliceActionListener);然而我们需要注意,这种方法有两个问题。首先,如果一个JRadioBtton已经处理选中状态,并且被再次选中时,任何已关联的ActionListener对象仍然会再次得到通知。尽管通过少量的工作我们并不能阻止所订阅的ActionListener的再次通知,但是我们依然可以进行正常的处理。我们需要重新获取到最后一个选中项目的引用,并且检测是否重新选中。下面修改的ActionListener进行这种检测:
ActionListener crustActionListener = new ActionListener() { String lastSelected; public void actionPerformed(ActionEvent actionEvent) { AbstractButton aButton = (AbstractButton)actionEvent.getSource(); String label = aButton.getText(); String msgStart; if (label.equals(lastSelected)) { msgStart = "Reselected: "; } else { msgStart = "Selected: "; } lastSelected = label; System.out.println(msgStart + label); } };第二个需要处理的问题就是确定在任意时刻哪一个JRadioButton被选中。通过重写RadioButtonUtils.createRadioButtonGrouping()助手方法,在方法外部ButtonGroup与JRadioButton组件都是不可见的。所以,并没有直接的方法在返回容器的ButtonGroup内部确定哪一个JRadioButton对象被选中。这也许是必须的,例如,如果在屏幕上有一个Order Pizza按钮,而我们希望在用户点击这个按钮之后我们可以确定哪一个匹萨预订选项被选中。
下面的助手方法,public Enumeration getSelectedElements(Container container),当添加到前面的RadioButtonUtils类中时将会提供这种必须的答案。助手方法只在传递给方法的容器装满AbstractButton对象时才会起作用。在前面所描述的createRadioButtonGrouping()方法所创建的容器正适合这种情况,尽管getSelectedElements()方法可以单独使用。
public static Enumeration getSelectedEllements(Container container) { Vector selections = new Vector(); Component components[] = container.getComponents(); for(int i=0, n=components.length; i为了使用getSelectedElements()方法,我们只需要将createRadionButtonGrouping()方法中返回的容器传递经getSelectedElements()方法来获得选中项目的String对象的Enumeration。下面的示例演示了使用方法。
final Container crustContainer = RadioButtonUtils.createRadioButtonGrouping(crustOptions, "Crust Type"); ActionListener buttonActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { Enumeration selected = RadioButtonUtils.getSelectedElements(crustContainer); while (selected.hasMoreElements()) { System.out.println ("Selected -> " + selected.nextElement()); } } }; JButton button = new JButton ("Order Pizza"); button.addActionListener(buttonActionListener);对于getSelectedElements()方法返回多个值也许是必要的,因为如果在容器中多个按钮之间共享ButtonModel时,ButtonGroup的多个组件将会被选中。在组件之间共享ButtonModel并不是通常用法。如果我们确定我们的按钮模型并不会被共享,那么我们也许需要提供一个返回String的类似方法。
使用ItemListener监听JRadioButton事件
依赖于我们正在尝试作的事情,对于JRadionButton使用ItemListener通常并不是所希望的事件监听方法。当注册一个ItemListener时,一个新的JRadionButton选中会通知这个监听器两次:一次用于取消旧值,一次用于选中新值。对于重新选中(选中同一选项两次),监听器并不会被通知两次。
为了进行演示,下面的监听器会检测重新选中,正如前面的AcitonListener所做的,这个监听器会报告选中(或是取消选中)的元素。
ItemListener itemListener = new ItemListener() { String lastSelected; public void itemStateChanged(ItemEvent itemEvent) { AbstractButton aButton = (AbstractButton)itemEvent.getSource(); int state = itemEvent.getStateChange(); String label = aButton.getText(); String msgStart; if (state == ItemEvent.SELECTED) { if (label.equals(lastSelected)) { msgStart = "Reselected -> "; } else { msgStart = "Selected -> "; } lastSelected = label; } else { msgStart = "Deselected -> "; } System.out.println(msgStart + label); } };为了正确的作用,对于RadioButtonUtils类需一些新的方法来允许我们将ItemListener关联到ButtonGroup中的每一个JRadioButton上。相应的代码会出现在后面的完整示例代码中。
使用ChangeListener监听JRadioButton事件
对于JRadioButton而言,ChangeListener的响应类似于JToggleButton与JCheckBox中的响应。当选中的单选按钮被armed,pressed,selected,released以及按钮模型的各种其他属性变化时所订阅的监听器都会得到通知。JRadioButton的唯一区别就在于ChangeListener也会被通知关于单选按钮正被取消选中的状态变化。前面例子中的ChangeListener也可以关联到JRadioButton。他也会被经常通知。
列表5-7中的相同程序演示了注册到两种不同的JRadioButton对象事件的所有监听器。另外,JButton报告了单选按钮被选中的元素。图5-12显示了程序运行时的主窗体。
/** * */ package net.ariel.ch05; import java.awt.BorderLayout; import java.awt.Container; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.Enumeration; import javax.swing.AbstractButton; import javax.swing.ButtonModel; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * @author mylxiaoyi * */ public class GroupActionRadio { private static final String sliceOptions[] = { "4 slices", "8 slices", "12 slices", "16 slices" }; private static final String crustOptions[] = { "Sicilian", "Thin Crust", "Thick Crust", "Stuffed Crust" }; /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Grouping Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ActionListener sliceActionListener = new ActionListener() { public void actionPerformed(ActionEvent event) { AbstractButton aButton = (AbstractButton)event.getSource(); System.out.println("Selected: "+aButton.getText()); } }; Container sliceContainer = RadioButtonUtils.createRadioButtonGrouping(sliceOptions, "Slice Count", sliceActionListener); ActionListener crustActionListener = new ActionListener() { String lastSelected; public void actionPerformed(ActionEvent event) { AbstractButton aButton = (AbstractButton)event.getSource(); String label = aButton.getText(); String msgStart; if(label.equals(lastSelected)) { msgStart = "Reselected: "; } else { msgStart = "Selected: "; } lastSelected = label; System.out.println(msgStart + label); } }; ItemListener itemListener = new ItemListener() { String lastSelected; public void itemStateChanged(ItemEvent event) { AbstractButton aButton = (AbstractButton)event.getSource(); int state = event.getStateChange(); String label = aButton.getText(); String msgStart; if(state == ItemEvent.SELECTED) { if(label.equals(lastSelected)) { msgStart = "Reselected -> "; } else { msgStart = "Selected -> "; } lastSelected = label; } else { msgStart = "Deselected -> "; } System.out.println(msgStart + label); } }; ChangeListener changeListener = new ChangeListener() { public void stateChanged(ChangeEvent event) { AbstractButton aButton = (AbstractButton)event.getSource(); ButtonModel aModel = aButton.getModel(); boolean armed = aModel.isArmed(); boolean pressed = aModel.isPressed(); boolean selected = aModel.isSelected(); System.out.println("Changed: "+armed+"/"+pressed+"/"+selected); } }; final Container crustContainer = RadioButtonUtils.createRadioButtonGrouping(crustOptions, "Crust Type", crustActionListener, itemListener, changeListener); ActionListener buttonActionListener = new ActionListener() { public void actionPerformed(ActionEvent event) { Enumeration selected = RadioButtonUtils.getSelectedElements(crustContainer); while(selected.hasMoreElements()) { System.out.println("Selected -> "+selected.nextElement()); } } }; JButton button = new JButton("Order Pizza"); button.addActionListener(buttonActionListener); frame.add(sliceContainer, BorderLayout.WEST); frame.add(crustContainer, BorderLayout.EAST); frame.add(button, BorderLayout.SOUTH); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }为了处理向ButtonGroup中所有的单选按钮注册ChangeListener对象,我们对RadioButtonUtils类进行了一些修改。完整的最终类定义如下列表5-8所示。
/** * */ package net.ariel.ch05; import java.awt.Component; import java.awt.Container; import java.awt.event.ActionListener; import java.awt.event.ItemListener; import java.util.Enumeration; import java.util.Vector; import javax.swing.AbstractButton; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.border.Border; import javax.swing.event.ChangeListener; /** * @author mylxiaoyi * */ public class RadioButtonUtils2 { private RadioButtonUtils2() { } public static Enumeration getSelectedElements(Container container) { Vector selections = new Vector(); Component components[] = container.getComponents(); for(int i=0, n=components.length; i5.5.5 自定义JRadioButton观感
每一个已安装的Swing观感都会提供一个不同的JRadioButton外观以及默认的UIResource值集合。图5-13显示了预安装的观感类型集合的JRadioButton组件的外观:Motif,Windows,Ocean。下图显示了Thin Crust pizza预定程序的界面。另外,Thick Crust选项具有输入焦点。
表5-6显示了JRadioButton的UIResource相关的属性集合。JRadioButton组件具有20个不同的属性。
JRadioButton UIResource元素
属性字符串 |
对象类型 |
RadioButton.background |
Color |
RadioButton.border |
Border |
RadioButton.darkShadow |
Color |
RadioButton.disabledText |
Color |
RadioButton.focus |
Color |
RadioButton.focusInputMap |
Object[] |
RadioButton.font |
Font |
RadioButton.foreground |
Color |
RadioButton.gradient |
List |
RadioButton.highlight |
Color |
RadioButton.icon |
Icon |
RadioButton.interiorBackground |
Color |
RadioButton.light |
Color |
RadioButton.margin |
Insets |
RadioButton.rollover |
Boolean |
RadioButton.select |
Color |
RadioButton.shadow |
Color |
RadioButton.textIconGap |
Integer |
RadioButton.textShiftOffset |
Integer |
RadioButtonUI |
String |
本章描述了可切换的组件:JToggleButton,JCheckBox与JRadioButton。我们已经了解了每一个组件如何使用JToggleButton。其数据模型ToggleButtonModel类以及如何将组件组合在一个ButtonGroup中。另外,我们同时了解了如何处理每一个组件的选中事件。
第6章将会解释如何使用各种面向菜单的Swing组件。