构建专业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开发专业的数值输入组件,并能够在实际项目中灵活应用这些技术。