Java图形程序设计(五)——选择组件

上一篇博客我们探讨了如何获取用户输入的文本。然而,在很多情况下,可能更加愿意给用户几个选项,而不让用户在文本组件中输入数据。使用一组按钮或者选项列表让用户做出选择(这样也免去了检查错误的麻烦)。下面我们来探讨如何编写程序来实现复选框、单选按钮、选项列表以及滑块。

复选框

如果想要接受的输入只是 “是” 或 “非”,就可以使用复选框组件。复选框自动地带有标识标签。用户通过点击某个复选框来选择相应的选项,再点击则取消选取。当复选框获得焦点时,用户也可以通过按空格键来切换选择。

复选框需要一个紧邻它的标签来说明其用途。在构造器中指定标签文本。

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);
        });
    }

}

运行结果截图:
Java图形程序设计(五)——选择组件_第1张图片
Java图形程序设计(五)——选择组件_第2张图片
Java图形程序设计(五)——选择组件_第3张图片
Java图形程序设计(五)——选择组件_第4张图片

单选钮

在前一个例子中,对于两个复选框,用户既可以选择一个、两个,也可以两个都不选。在很多情况下,我们需要用户只选择几个选项中的一个。当用户选择另一项的时候,前一项就会自动地取消选择。这样一组选框通常称为单选钮。

在 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);
        });
    }

}

运行结果截图:
Java图形程序设计(五)——选择组件_第5张图片

边框

如果在一个窗口中有多个单选按钮,就需要用可视化形式指明哪些按钮属于同一组。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);
        });
    }

}

运行结果截图:
Java图形程序设计(五)——选择组件_第6张图片
Java图形程序设计(五)——选择组件_第7张图片
Java图形程序设计(五)——选择组件_第8张图片
Java图形程序设计(五)——选择组件_第9张图片
Java图形程序设计(五)——选择组件_第10张图片
Java图形程序设计(五)——选择组件_第11张图片
Java图形程序设计(五)——选择组件_第12张图片

组合框

如果有多个选择框,使用单选框按钮就不太适合了,因为它占据的屏幕空间太大。这时就可以使用组合框。当用户点击这个组件时,选择列表就会下拉出来,用户可以从中选择一项。

如果下拉列表框被设置为可编辑(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);
        });
    }

}

运行结果截图:
Java图形程序设计(五)——选择组件_第13张图片

滑动条

组合框可以让用户从一组离散值中进行选择。滑动条允许进行连续值的选择,例如,从 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);
        });
    }

}

运行结果截图:
Java图形程序设计(五)——选择组件_第14张图片


你可能感兴趣的:(编程语言-Java)