4、 构建专业GUI组件:深入理解JFC中的数值输入组件

构建专业GUI组件:深入理解JFC中的数值输入组件

1 引言

在现代图形用户界面(GUI)开发中,创建高度交互的专业组件是提升用户体验的重要手段。本篇文章将带你深入了解如何通过Java基础类(JFC)开发数值输入组件。我们将从基础的 JTextField 类出发,逐步构建专门的数值输入组件,并探讨其内部和外部行为的实现机制。

2 数值输入组件的设计原则

在设计数值输入组件时,预防错误比事后纠正更为重要。通过精心设计,我们可以确保用户只能输入有效的数值,从而避免不必要的错误提示和数据验证。以下是几个关键的设计原则:

  • 限制输入范围 :确保用户只能输入合法的数值,如浮点数或整数。
  • 即时反馈 :当用户输入无效字符时,应立即给予反馈,如发出警告音或视觉提示。
  • 易于使用 :简化用户操作,如直接按回车键提交数值。

2.1 示例:浮点数输入框

为了实现上述原则,我们开发了一个名为 NumericTextField 的组件。这个组件继承自 JTextField ,并通过重写 processKeyEvent() 方法来拦截和处理用户输入的事件。这样可以确保用户只能输入合法的浮点数值。

3 NumericTextField 类的实现

NumericTextField 类的主要功能是确保用户只能输入合法的浮点数值。下面是其实现的关键部分:

3.1 构造函数

package numericinput;

import java.awt.event.*;
import javax.swing.*;

public class NumericTextField extends JTextField {

    public NumericTextField() {
        super();
        this.setInputMethod(false);
        this.enableEvents(AWTEvent.KEY_EVENT_MASK);
    }
}

3.2 事件处理

为了处理用户输入的事件,我们需要重写 processKeyEvent() 方法。这个方法负责拦截并处理键盘事件,确保用户只能输入合法的字符。

protected void processKeyEvent(KeyEvent e) {
    // 处理数字字符
    if (Character.isDigit(e.getKeyChar())) {
        processDigitChar(e.getKeyChar());
    }
    // 处理编辑命令(如退格键)
    else if (isEditKey(e.getKeyCode())) {
        processEditCommand(e.getKeyCode());
    }
    // 处理导航命令(如光标移动)
    else if (isNavigationKey(e.getKeyCode())) {
        processNavigationCommand(e.getKeyCode());
    }
    // 处理符号字符(如正负号)
    else if (isSignChar(e.getKeyChar()) && isCursorAtStart()) {
        processSignChar(e.getKeyChar());
    }
    // 处理小数点
    else if (e.getKeyChar() == '.' && !containsPeriod()) {
        processPeriodChar(e.getKeyChar());
    }
    // 处理回车键
    else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
        dispatchActionEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ""));
    }
}

3.3 方法列表

NumericTextField 类提供了以下方法来获取和设置数值:

方法名 描述
getValue() 获取当前输入的数值(作为 double 类型)
getFloatValue() 获取当前输入的数值(作为 float 类型)
setValue() 设置当前输入的数值
setText() 设置文本内容(仅当文本为合法浮点数时生效)

3.4 状态转换图

下图展示了 NumericTextField 的状态转换过程:

stateDiagram-v2
    [*] --> StaticState
    StaticState : digit key typed
    StaticState : process digit char
    StaticState : edit key typed
    StaticState : process edit command
    StaticState : navigation key typed
    StaticState : process navigation command
    StaticState : sign key typed
    StaticState : process sign char
    StaticState : cursor at start and sign not already present
    StaticState : period key typed
    StaticState : process period char
    StaticState : period not already present
    StaticState : enter key typed
    StaticState : dispatch action event

4 IntegerTextField 类的实现

NumericTextField 的基础上,我们进一步开发了 IntegerTextField 类。这个类不仅限制用户只能输入整数值,还支持设置最小值和最大值,确保输入的数值在指定范围内。

4.1 构造函数

public class IntegerTextField extends NumericTextField {

    private long minimumValue;
    private long maximumValue;

    public IntegerTextField() {
        super();
        this.minimumValue = Long.MIN_VALUE;
        this.maximumValue = Long.MAX_VALUE;
    }

    public IntegerTextField(long maxValue) {
        super();
        this.minimumValue = 0;
        this.maximumValue = maxValue;
    }

    public IntegerTextField(long minValue, long maxValue) {
        super();
        this.minimumValue = minValue;
        this.maximumValue = maxValue;
    }
}

4.2 方法列表

IntegerTextField 类提供了以下方法来获取和设置数值:

方法名 描述
getMinimum() 获取最小值
getMaximum() 获取最大值
getLong() 获取当前输入的数值(作为 long 类型)
getInt() 获取当前输入的数值(作为 int 类型)
setValue() 设置当前输入的数值
setText() 设置文本内容(仅当文本为合法整数时生效)

4.3 状态转换图

下图展示了 IntegerTextField 的状态转换过程:

stateDiagram-v2
    [*] --> StaticState
    StaticState : digit key typed
    StaticState : process digit char
    StaticState : edit key typed
    StaticState : process edit command
    StaticState : navigation key typed
    StaticState : process navigation command
    StaticState : sign key typed
    StaticState : process sign char
    StaticState : cursor at start and sign not already present and component allows negative values
    StaticState : period key typed
    StaticState : ignore period key
    StaticState : enter key typed
    StaticState : dispatch action event

在接下来的部分中,我们将详细介绍 SpinBox 组件的实现,并探讨如何通过事件源/监听器机制实现组件的外部行为。同时,我们还会介绍如何通过拦截用户行为来实现组件的内部行为。

5 SpinBox 组件的实现

SpinBox 组件是一个复合组件,它包含一个整数输入区域和一对带箭头的控制按钮,允许用户通过点击按钮来增加或减少数值。这种设计使得用户可以更直观地调整数值,特别适用于需要频繁输入小整数值的场景,如字体大小选择。

5.1 构造函数

SpinBox 组件的实现涉及到多个子组件的组合。以下是 SpinBox 类的构造函数:

package numericinput;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class SpinBox extends JComponent implements ActionListener, ChangeListener {

    private IntegerTextField inputArea;
    private JButton upBox;
    private JButton downBox;
    private ActionListenerList actionListenerList;
    private boolean propagateEvents;

    public SpinBox() {
        this(Long.MIN_VALUE, Long.MAX_VALUE);
    }

    public SpinBox(long maxValue) {
        this(0, maxValue);
    }

    public SpinBox(long minValue, long maxValue) {
        this.inputArea = new IntegerTextField(minValue, maxValue);
        this.upBox = new JButton("▲");
        this.downBox = new JButton("▼");
        this.actionListenerList = new ActionListenerList();
        this.propagateEvents = true;

        upBox.addActionListener(this);
        downBox.addActionListener(this);
        inputArea.addActionListener(this);

        setLayout(new BorderLayout());
        add(inputArea, BorderLayout.CENTER);
        JPanel buttonsPanel = new JPanel();
        buttonsPanel.setLayout(new GridLayout(2, 1));
        buttonsPanel.add(upBox);
        buttonsPanel.add(downBox);
        add(buttonsPanel, BorderLayout.EAST);
    }
}

5.2 方法列表

SpinBox 类提供了以下方法来获取和设置数值:

方法名 描述
getValue() 获取当前输入的数值(作为 long 类型)
getIntValue() 获取当前输入的数值(作为 int 类型)
setEnabled() 设置组件是否启用
setEditable() 设置用户是否可以编辑输入区域
isEditable() 查询输入区域是否可编辑
getPreferredSize() 获取首选大小
getMinimumSize() 获取最小大小
getMaximumSize() 获取最大大小
doLayout() 执行布局
addActionListener() 添加ActionListener
removeActionListener() 移除ActionListener

5.3 状态转换图

下图展示了 SpinBox 的状态转换过程:

stateDiagram-v2
    [*] --> StaticState
    StaticState : digit key typed
    StaticState : process digit char
    StaticState : edit key typed
    StaticState : process edit command
    StaticState : navigation key typed
    StaticState : process navigation command
    StaticState : sign key typed
    StaticState : process sign char
    StaticState : cursor at start and sign not already present and component allows negative values
    StaticState : enter key typed
    StaticState : dispatch action event
    StaticState --> SpinningState : arrow control pressed
    SpinningState : time out spinning up and incremented value not greater than max increment value
    SpinningState : time out spinning up and incremented value greater than max set value to min
    SpinningState : time out spinning down and incremented value not less than min decrement value
    SpinningState : time out spinning down and incremented value less than min set value to max
    SpinningState --> StaticState : arrow control released

6 内部和外部行为的区别

在设计和实现专业组件时,区分内部行为和外部行为是非常重要的。内部行为是指组件在用户交互过程中自动执行的行为,如按钮按下时的视觉反馈或滑块拖动时的实时更新。外部行为则是指组件对外界事件的响应,如用户点击按钮或拖动滑块时触发的动作。

6.1 内部行为的实现

内部行为通常通过拦截用户行为(如鼠标移动或键盘按键)生成的事件来实现。在组件内部处理这些事件,确保组件的基本功能正常工作,然后再将事件传递给任何注册的监听器。例如, JButton 在按下时会给出视觉反馈,并在释放时触发动作事件。

6.2 外部行为的实现

外部行为通常通过事件源/监听器机制来实现。当用户与组件交互时,组件会生成相应的事件并通知注册的监听器。监听器可以根据事件类型执行相应的操作。例如, JSlider 在拖动时会生成 ChangeEvent ,并通知注册的监听器。

7 示例应用: NumericInputDemo

为了验证这些组件的功能,我们开发了一个名为 NumericInputDemo 的示例应用。这个应用展示了 NumericTextField IntegerTextField SpinBox 组件的工作原理,并允许用户通过键盘和鼠标与这些组件进行交互。

7.1 NumericInputDemo 类的实现

package numericinput;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class NumericInputDemo extends JApplet implements ActionListener {

    private NumericTextField floatingPoint;
    private IntegerTextField defaultInteger;
    private IntegerTextField positiveInteger;
    private IntegerTextField limitedInteger;

    @Override
    public void init() {
        setResources();

        JPanel labelPanel = new JPanel();
        JPanel inputPanel = new JPanel();

        labelPanel.setLayout(new GridLayout(4, 1));
        inputPanel.setLayout(new GridLayout(4, 1));

        JLabel floatLabel = new JLabel("Floating point");
        floatingPoint = new NumericTextField();
        floatingPoint.setName("Floating point");
        floatingPoint.addActionListener(this);

        JLabel defaultLabel = new JLabel("Default int");
        defaultInteger = new IntegerTextField();
        defaultInteger.setName("Default int");
        defaultInteger.addActionListener(this);

        JLabel positiveLabel = new JLabel("Positive int");
        positiveInteger = new IntegerTextField(Long.MAX_VALUE);
        positiveInteger.setName("Positive int");
        positiveInteger.addActionListener(this);

        JLabel limitedLabel = new JLabel("Limited int");
        limitedInteger = new IntegerTextField(-999, +999);
        limitedInteger.setName("Limited int");
        limitedInteger.addActionListener(this);

        labelPanel.add(floatLabel);
        labelPanel.add(defaultLabel);
        labelPanel.add(positiveLabel);
        labelPanel.add(limitedLabel);

        inputPanel.add(floatingPoint);
        inputPanel.add(defaultInteger);
        inputPanel.add(positiveInteger);
        inputPanel.add(limitedInteger);

        getContentPane().add(labelPanel, BorderLayout.WEST);
        getContentPane().add(inputPanel, BorderLayout.CENTER);
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        Component theSource = (Component) event.getSource();
        String theName = theSource.getName();

        System.out.print(theName + ": ");
        if (theName.equals("Floating point")) {
            System.out.println(((NumericTextField) theSource).getValue());
        } else {
            System.out.println(((IntegerTextField) theSource).getValue());
        }
    }

    private void setResources() {
        // 初始化资源,如字体、颜色等
    }
}

7.2 界面布局

NumericInputDemo 的界面布局如下表所示:

标签 输入框类型
Floating point NumericTextField
Default int IntegerTextField
Positive int IntegerTextField
Limited int IntegerTextField

每个输入框都绑定了一个 ActionListener ,当用户按下回车键时,会触发相应的事件处理程序,输出当前输入的数值。

8 总结与展望

通过本文的介绍,我们深入了解了如何使用JFC开发专业的数值输入组件。从基础的 JTextField 类出发,我们逐步构建了 NumericTextField IntegerTextField SpinBox 组件,并探讨了如何通过事件源/监听器机制实现组件的外部行为,以及如何通过拦截用户行为来实现组件的内部行为。

在实际开发中,这些组件可以帮助我们创建更加健壮和易用的用户界面,提升用户体验。未来,我们可以进一步扩展这些组件的功能,如添加更多的输入验证规则、支持更多类型的数值输入等,以满足不同应用场景的需求。


通过本文的介绍,希望读者能够掌握如何使用JFC开发专业的数值输入组件,并能够在实际项目中灵活应用这些技术。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

csdn_te_download_001

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值