上一篇博客我们探讨了如何获取用户输入的文本。然而,在很多情况下,可能更加愿意给用户几个选项,而不让用户在文本组件中输入数据。使用一组按钮或者选项列表让用户做出选择(这样也免去了检查错误的麻烦)。下面我们来探讨如何编写程序来实现复选框、单选按钮、选项列表以及滑块。
如果想要接受的输入只是 “是” 或 “非”,就可以使用复选框组件。复选框自动地带有标识标签。用户通过点击某个复选框来选择相应的选项,再点击则取消选取。当复选框获得焦点时,用户也可以通过按空格键来切换选择。
复选框需要一个紧邻它的标签来说明其用途。在构造器中指定标签文本。
bold = new JCheckBox("Bold");
可以使用 setSelected 方法来选定或取消选定复选框。例如:
bold.setSelected(true);
isSelected 方法将返回每个复选框的当前状态。如果没有选取则为 false,否则为 true。
当用户点击复选框时将触发一个动作事件。通常,可以为复选框设置一个动作监听器。在下面程序中,两个复选框使用了同一个动作监听器。
ActionListener listener = ...;
bold.addActionListener(listener);
italic.addActionListener(listener);
actionPerformed 方法查询 bold 和 italic 两个复选框的状态,并且把面板中的字体设置为常规、加粗、倾斜或者粗斜体。
ActionListener listener = event -> {
int mode = 0;
if(bold.isSelected()) mode += Font.BOLD;
if(italic.isSelected()) mode += Font.ITALIC;
label.setFont(new Font(Font.SERIF, mode, FONTSIZE));
};
我们来用代码说明怎样使用这个复选框组件:
下面程序中实现了两个复选框,一个用于打开或关闭字体倾斜属性,而另一个同于控制加粗属性。
代码:
CheckBoxFrame.java
package checkbox;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CheckBoxFrame extends JFrame {
private JLabel label;
private JCheckBox bold;
private JCheckBox italic;
private static final int FONTSIZE = 24;
public CheckBoxFrame() {
// add the sample text label
label = new JLabel("The quick brown fox jumps over the lazy dog.");
label.setFont(new Font("Serif", Font.BOLD, FONTSIZE));
add(label, BorderLayout.CENTER);
// this listener sets the font attribute of
// the label to the check box state
ActionListener listener = event -> {
int mode = 0;
if(bold.isSelected()) mode += Font.BOLD;
if(italic.isSelected()) mode += Font.ITALIC;
label.setFont(new Font("Serif", mode, FONTSIZE));
};
// add the check boxes
JPanel buttonPanel = new JPanel();
bold = new JCheckBox("Bold");
bold.addActionListener(listener);
bold.setSelected(true);
buttonPanel.add(bold);
italic = new JCheckBox("Italic");
italic.addActionListener(listener);
buttonPanel.add(italic);
add(buttonPanel, BorderLayout.SOUTH);
pack();
}
}
CheckBoxFrameTest.java
package checkbox;
import java.awt.*;
import javax.swing.*;
public class CheckBoxFrameTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new CheckBboxFrame();
frame.setTitle("ActionFrame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
在前一个例子中,对于两个复选框,用户既可以选择一个、两个,也可以两个都不选。在很多情况下,我们需要用户只选择几个选项中的一个。当用户选择另一项的时候,前一项就会自动地取消选择。这样一组选框通常称为单选钮。
在 Swing 中,实现单选钮组非常简单。为单选钮组构造一个 Buttongroup 的对象。然后,再将 JRadioButton 类型的对象添加到按钮组中。按钮组负责在新按钮被按下时,取消前一个被按下的按钮的选择状态。
ButtonGroup group = new ButtonGroup();
JRadioButton smallButton = new JRadioButton("Small", false);
group.add(smallButton);
JRadioButton mediumButton = new JRadioButton("Medium", true);
group.add(mediumButton);
...
构造器的第二个参数为 true 表明这个按钮初始状态是被选择,其他按钮构造器的这个参数为 false。注意,按钮组仅仅控制按钮的行为,如果想把这些按钮组织在一起布局,需要把它们添加到容器中,如 JPanel。
单选钮和复选框的外观是不一样的。复选框为正方形,并且如果被选择,这个正方形中会出现一个对勾的符号。单选钮是圆形,选择以后圈内出现一个圆点。
单选钮的事件通知机制与其他按钮一样。当用户点击一个单选钮时,这个按钮将产生一个动作事件。在示例中,定义了一个动作监听器用来把字体大小设置特定值:
ActionListener listener = event ->
label.setFont(new Font("Serif", Font.PLAIN, size));
用这个监听器与复选框中的监听器做一个对比。每个单选钮都对应一个不同的监听器对象。每个监听器都非常清楚所要做的事情——把字体尺寸设置为一个特定值。在复选框实例中,使用的是一种不同的方法,两个复选框共享一个动作监听器。这个监听器调用一个方法来检查两个复选框的当前状态。
对于单选钮可以使用同一个方法吗?可以试一下使用一个监听器来计算尺寸,如:
if(smallButton.isSelected()) size = 8;
else if(mediumButton.isSelected()) size = 12;
...
然而,更愿意使用各自独立的动作监听器,因为这样可以将尺寸值与按钮紧密地绑定在一起。
下面是一个选择字体大小的完整程序,它演示了单选钮的工作过程。
RadioButtonFrame.java
package radiobutton;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class RadioButtonFrame extends JFrame {
private JPanel buttonPanel;
private ButtonGroup group;
private JLabel label;
private static final int DEFAULT_SIZE = 36;
public RadioButtonFrame() {
// add the sample text label
label = new JLabel("The quick brown fox jumps over the lazy dog.");
label.setFont(new Font("Serif", Font.PLAIN, DEFAULT_SIZE));
add(label, BorderLayout.CENTER);
//add the radio buttons
buttonPanel = new JPanel();
group = new ButtonGroup();
addRadioButton("Small", 8);
addRadioButton("Medium", 12);
addRadioButton("Large", 18);
addRadioButton("Extra large", 36);
add(buttonPanel, BorderLayout.SOUTH);
pack();
}
public void addRadioButton(String name, int size) {
boolean selected = size == DEFAULT_SIZE;
JRadioButton button = new JRadioButton(name, selected);
group.add(button);
buttonPanel.add(button);
// this listener sets the label font size
ActionListener listener = event ->
label.setFont(new Font("Serif", Font.PLAIN, size));
button.addActionListener(listener);
}
}
RadioButtonFrameTest.java
package radiobutton;
import java.awt.*;
import javax.swing.*;
public class RadioButtonFrameTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new RadioButtonFrame();
frame.setTitle("ActionFrame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
如果在一个窗口中有多个单选按钮,就需要用可视化形式指明哪些按钮属于同一组。Swing 提供了一组很有用的边框(borders)来解决这个问题。可以在任何继承了 JComponent 的组件上应用边框。最常用的用途是在一个面板周围放置一个边框,然后用其他用户界面元素(如单选钮)填充面板。
有几种不同的边框可供选择,但是使用它们的步骤完全一样。
1 调用 BorderFactory 的静态方法创建边框。下面是几种可选的风格:
2 如果愿意的话,可以给边框添加标题,具体的实现方法是将边框传递给 BorderFactory.createTitledTitledBorder。
3 如果确实想把一切凸显出来,可以调用下列方法将几种边框组合起来使用:
BorderFactory.createComponentBorder。
4 调用 JComponent 类中 setBorder 方法将结果边框添加到组件中。
例如,下列代码说明了如何把一个带有标题的蚀刻边框添加到一个面板上:
Border etched = BorderFactory.createEtchedBorder();
Border titled = BorderFactory.createTitledBorder(etched, "Border types");
buttonPanel.setBorder(titled);
下面程序清单中程序可以看到各种边框的外观:
不同的外框有不同的用于设置边框得宽度和颜色的选项。详情请看 API 注释。偏爱使用边框的人都很欣赏者这一点,SoftBevelBorder 类用于构造具有柔和拐角的斜面边框,LineBorder 类也能够构造圆拐角。这些边框只能通过类的某个构造器构造,而没有 BorderFactory 方法。
代码:
BorderFrame.java
package border;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
public class BorderFrame extends JFrame{
private JPanel demoPanel;
private JPanel buttonPanel;
private ButtonGroup group;
public BorderFrame() {
demoPanel = new JPanel();
buttonPanel = new JPanel();
group = new ButtonGroup();
addRadioButton("Lowered bevel", BorderFactory.createLoweredBevelBorder());
addRadioButton("Raised bevel", BorderFactory.createRaisedBevelBorder());
addRadioButton("Etched", BorderFactory.createEtchedBorder());
addRadioButton("Line", BorderFactory.createLineBorder(Color.BLUE));
addRadioButton("Matte", BorderFactory.createMatteBorder(10, 10, 10, 10, Color.BLUE));
addRadioButton("Empty", BorderFactory.createEmptyBorder());
Border etched = BorderFactory.createEtchedBorder();
Border titled = BorderFactory.createTitledBorder(etched, "Border types");
buttonPanel.setBorder(titled);
setLayout(new GridLayout(2, 1));
add(buttonPanel);
add(demoPanel);
pack();
}
public void addRadioButton(String buttonName, Border b) {
JRadioButton button = new JRadioButton(buttonName);
button.addActionListener(event -> demoPanel.setBorder(b));
group.add(button);
buttonPanel.add(button);
}
}
BorderFrameTest.java
package border;
import java.awt.*;
import javax.swing.*;
public class BorderFrameTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new BorderFrame();
frame.setTitle("ActionFrame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
如果有多个选择框,使用单选框按钮就不太适合了,因为它占据的屏幕空间太大。这时就可以使用组合框。当用户点击这个组件时,选择列表就会下拉出来,用户可以从中选择一项。
如果下拉列表框被设置为可编辑(editable),就可以像编辑列表一样编辑当前的选项内容。鉴于这个原因,这种组件被称为组合框(comobo box),它将文本域的灵活性与一组预定义的选项组合起来。JComboxBox 类提供了组合框的组件。
在 Java SE 7 中,JcomboBox 类是一个泛型类。例如,JComboBox< String > 包含 String 类型的对象,JComboBox< Integer > 包含整数。
调用 setEditable 方法可以让组合框可编辑。注意,编辑只会影响当前项,而不会改变列表内容。
可以调用 getSelectedItem 方法获取当前选项,如果组合框是可编辑的,当前选项则是可编辑的。不过,对于可编辑组合框,其中的选项可以是任何类型,这取决于编译器。如果你的组合框并不是可编辑的,最好调用
como.getItemAt(combo.getSelectedIndex())
这会为所选选项提供正确的类型。
在示例程序中,用户可以从字体列表 ( Serif, SansSerif, Monospaced 等) 中选择一个字体,用户也可以键入其他的字体。
可以调用 addItem 方法增加选项。在示例程序中,只在构造器中调用了 addItem 方法,实际上,可以在任何地方调用它。
JComboBox faceCombo = new JComboBox<>();
faceCombo.addItem("Serif");
faceCombo.addItem("SansSerif");
...
这个方法将字符串添加到列表的尾部。可以利用 insertItemAt 方法在列表的任何位置插入一个新选项:
faceCombo.insertItemAt("Monospaced", 0);
可以增加任何类型的选项,组合框可以调用每个选项的 toString 方法显示其内容。
当用户从组合框中选择一个选项时,组合框就将产生一个动作事件。为了判断哪个选项被选择,可以通过事件参数调用 getSource 方法来得到发送事件的组合框引用,接着调用 getSelectedItem 方法获取当前选择的选项。需要把这个方法的返回值转化为相应的类型,通常是 string 型。
faceCombo.addActionListener(event ->
label.setFont(
new Font(
faceCombo.getItemAt(faceCombo.getSelectedIndex()), Font.PLAIN, DEFAULT_SIZE)));
上代码:
ComboBoxFrame.java
package comboBox;
import java.awt.BorderLayout;
import java.awt.Font;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class ComboBoxFrame extends JFrame {
private JComboBox faceCombo;
private JLabel label;
private static final int DEFAULT_SIZE = 24;
public ComboBoxFrame() {
// add the sample text label
label = new JLabel("The quick brown fox jumps over the lazy dog.");
label.setFont(new Font("Serif", Font.PLAIN, DEFAULT_SIZE));
add(label, BorderLayout.CENTER);
// make a combo box and add face names
faceCombo = new JComboBox<>();
faceCombo.addItem("Serif");
faceCombo.addItem("SansSerif");
faceCombo.addItem("Monospaced");
faceCombo.addItem("Dialog");
faceCombo.addItem("dialogInput");
// the combo box listener changes the label font to the selected face name
faceCombo.addActionListener(event ->
label.setFont(
new Font(
faceCombo.getItemAt(faceCombo.getSelectedIndex()), Font.PLAIN, DEFAULT_SIZE)));
// add combo box to panel at frame's southern border
JPanel comboPanel = new JPanel();
comboPanel.add(faceCombo);
add(comboPanel, BorderLayout.SOUTH);
pack();
}
}
ComboBoxFrameTest.java
package comboBox;
import java.awt.*;
import javax.swing.*;
public class ComboBoxFrameTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new ComboBoxFrame();
frame.setTitle("ActionFrame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
组合框可以让用户从一组离散值中进行选择。滑动条允许进行连续值的选择,例如,从 0 ~ 100 之间选择任意数值。
通常,可以使用下列方式构造滑动条:
JSlider slider = new JSlider(min, max, initialValue);
如果省略最小值、最大值和初始值,其默认值分别为 0、100 和 50。
或者如果需要垂直滑动条,可以按照下列方式调用构造器:
JSlider slider = new JSlider(SwingConstants.VERTICAL, min, max, initialValue);
这些构造器构造了一个无格式的滑动条。下面来看一下如何为滑动条添加装饰。
当用户滑动滑动条时,滑动条的值就会在最小值和最大值之间变化。当值发生变化时,ChangeEvent 就会发送给所有变化的监听器。为了得到这些改变的通知,需要调用 addChangeListener 方法并且安装一个实现了 ChangeListener 接口的对象。这个接口只有一个方法 StateChanged。在这个方法中,可以获取滑动条的当前值:
ChangeListener listener = Event -> {
JSlider slider = (JSlider)event.getSource();
int value = slider.getValue();
...
};
可以通过显示标尺(tick)对滑动条进行修饰。例如,在示例程序中,第二个滑动条使用了下面的设置:
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
上述滑动条在每 20 个单位的位置显示一个大标尺标记,每 5 个单位显示一个小标尺标记。所谓单位是指滑动条值,而不是像素。
这些代码只设置了标尺标记,想要将它们显示出来,还需要调用:
slider.setPaintTicks(true);
注意: 大标尺和小标尺是相互独立的。
可以调用下列方法为大标尺添加标尺标记标签(tick mark labels):
slider.setPaintLabels(true);
例如,对于一个范围为 0 到 100 的滑动条,如果大标尺的间距 20,每个大标尺的标签就应该是 0、20、40、60、80 和 100。
还可以提供其他形式的标尺标记,如字符串或者图标。首先需要填充一个键为 Integer 类型且值为 Component 类型的散列表。然后再调用 setLabelTable 方法,组件就会放置在标尺标记处。通常组件使用的是 JLabel 对象。下面代码说明了如何将标尺标签设置为 A、B、C、D、E 和 F。
Dictionary<Integer, Component> labelTable = new Hashtable<>();
labelTable.put(0, new JLabel("A"));
labelTable.put(20, new JLabel("B"));
labelTable.put(40, new JLabel("C"));
labelTable.put(60, new JLabel("D"));
labelTable.put(80, new JLabel("E"));
labelTable.put(100, new JLabel("F"));
slider.setLabelTable(labelTable);
下面示例程序演示了所有不同视觉效果的滑动条。
SliderFrame.java
package slider;
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
public class SliderFrame extends JFrame {
private JPanel sliderPanel;
private JTextField textField;
private ChangeListener listener;
public SliderFrame() {
sliderPanel = new JPanel();
sliderPanel.setLayout(new GridBagLayout());
//common listener for all sliders
listener = event -> {
//update text field when the slider value changes
JSlider source = (JSlider)event.getSource();
textField.setText("" + source.getValue());
};
// add a plain slider
JSlider slider = new JSlider();
addSlider(slider, "Plain");
// add a slider with major and minor ticks
slider = new JSlider();
slider.setPaintTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
addSlider(slider, "Ticks");
// add a slider that snaps to ticks
slider = new JSlider();
slider.setPaintTicks(true);
slider.setSnapToTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
addSlider(slider, "Snap to ticks");
// add a slider with no track
slider = new JSlider();
slider.setPaintTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
slider.setPaintTrack(false);
addSlider(slider, "No track");
// add an inverted slider
slider = new JSlider();
slider.setPaintTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
slider.setInverted(true);
addSlider(slider, "Inverted");
// add a slider with numeric labels
slider = new JSlider();
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
addSlider(slider, "Labels");
// add a slider with alphabetic labels
slider = new JSlider();
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);
Dictionary labelTable = new Hashtable<>();
labelTable.put(0, new JLabel("A"));
labelTable.put(20, new JLabel("B"));
labelTable.put(40, new JLabel("C"));
labelTable.put(60, new JLabel("D"));
labelTable.put(80, new JLabel("E"));
labelTable.put(100, new JLabel("F"));
slider.setLabelTable(labelTable);
addSlider(slider, "Custom labels");
// add a slider with icon labels
slider = new JSlider();
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.setSnapToTicks(true);
slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(20);
labelTable = new Hashtable();
// add card imags
labelTable.put(0, new JLabel(new ImageIcon("nine.gif")));
labelTable.put(20, new JLabel(new ImageIcon("ten.gif")));
labelTable.put(40, new JLabel(new ImageIcon("jack.gif")));
labelTable.put(60, new JLabel(new ImageIcon("queen.gif")));
labelTable.put(80, new JLabel(new ImageIcon("king.gif")));
labelTable.put(100, new JLabel(new ImageIcon("ace.gif")));
slider.setLabelTable(labelTable);
addSlider(slider, "Icon labels");
// add the text field that displays the slider value
textField = new JTextField();
add(sliderPanel, BorderLayout.CENTER);
add(textField, BorderLayout.SOUTH);
pack();
}
public void addSlider(JSlider s, String description) {
s.addChangeListener(listener);
JPanel panel = new JPanel();
panel.add(s);
panel.add(new JLabel(description));
panel.setAlignmentX(Component.LEFT_ALIGNMENT);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = sliderPanel.getComponentCount();
gbc.anchor = GridBagConstraints.WEST;
sliderPanel.add(panel,gbc);
}
}
SliderFrameTest.java
package slider;
import java.awt.*;
import javax.swing.*;
public class SliderFrameTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new SliderFrame();
frame.setTitle("ActionFrame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}