12.12 使用FormattedTextField和JTextPanel创建格式文本 12.11 使用JTable和TableModel创建表格

12.12 使用FormattedTextField和JTextPanel创建格式文本

这里有一张图片
Swing使用JtextComponent作为所有文本输入组件的父类,从图12.1中可以看出:
SwingJtextComponent类提供了三个子类:JTextAreaJTextFieldJEditorPane.

JEditorPane

JEditorPane提供了一个JTextPane子类,JEditorPaneJTextPane是两个典型的格式文本编辑器,也是本节介绍的重点。
JTextAreaJTextField是两个常见的文本组件,比较简单,本节不会再次介绍它们。

JTextField

JTextField派生了两个子类:JPasswordFieldJFormattedTextField,它们代表密码输入框格式化文本输入框

文本输入组件的model是Document接口

与其他的Swing组件类似,所有的文本输入组件也遵循了MVC的设计模式,即每个文本输入组件都有对应的model来保存其状态数据;
与其他的Swing组件不同的是,文本输入组件的model接口不是XxxModel接口,而是Document接口。
Document既包括有格式的文本,也包括无格式的文本。不同的文本输入组件对应的Document不同。

12.12.1 监听Document的变化

如果希望检测到任何文本输入组件里所输入内容的变化,则可以通过监听该组件对应的Document来实现。

如何获取文本输入组件的Document对象

JTextComponent类里提供了一个getDocument()方法,该方法用于获取所有文本输入组件对应的Document对象。

JTextComponent类的方法 描述
Document getDocument() Fetches the model associated with the editor.

为Document添加监听器

Document提供了一个addDocumentListener()方法来为Document添加监听器,该监听器必须实现DocumentListener接口.

Document接口的方法 描述
void addDocumentListener(DocumentListener listener) Registers the given observer to begin receiving notifications when changes are made to the document.

DocumentListener接口方法

DocumentListener接口里提供了如下三个方法。

方法 描述
void changedUpdate(DocumentEvent e) Document里的属性或属性集发生了变化时触发该方法。
void insertUpdate(DocumentEvent e) 当向Document中插入文本时触发该方法。
void removeUpdate(DocumentEvent e) 当从Document中删除文本时触发该方法。

对于上面的三个方法而言,如果仅需要检测文本的变化,则无须实现第一个方法。但Swing并没有为DocumentListener接口提供适配器,所以程序依然要为第一个方法提供空实现。

撤销监听器

除此之外,还可以为文件输入组件添加一个撤销监听器,这样就允许用户撤销以前的修改。添加撤销监听器的方法是addUndoableEditListener:

Document接口的方法 描述
void addUndoableEditListener(UndoableEditListener listener) Registers the given observer to begin receiving notifications when undoable edits are made to the document.

UndoableEditListener

该方法需要接收一个UndoableEditListener监听器,该监听器里包含了undoableEditHappened()方法:

UndoableEditListener接口的方法 描述
void undoableEditHappened(UndoableEditEvent e) 当文档里发生了可撤销的编辑操作时将会触发该方法

程序 为文本域的Document添加监听器

下面程序示范了如何为一个普通文本域的Document添加监听器,当用户在目标文本域里输入、删除文本时,程序会显示出用户所做的修改。该文本域还支持撤销操作,当用户按“Ctrl+Z”键时,该文本域会撤销用户刚刚输入的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import java.util.LinkedList;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.undo.*;
import javax.swing.event.*;

public class MonitorText {
JFrame mainWin = new JFrame("监听Document对象");
JTextArea target = new JTextArea(4, 35);
JTextArea msg = new JTextArea(5, 35);
JLabel label = new JLabel("文本域的修改信息");
Document doc = target.getDocument();
// 保存撤销操作的List对象
LinkedList<UndoableEdit> undoList = new LinkedList<>();
// 最多允许撤销多少次
final int UNDO_COUNT = 20;

public void init() {
msg.setEditable(false);
// 添加DocumentListener
doc.addDocumentListener(new DocumentListener() {
// 当Document的属性或属性集发生了改变时触发该方法
public void changedUpdate(DocumentEvent e) {
}

// 当向Document中插入文本时触发该方法
public void insertUpdate(DocumentEvent e) {
int offset = e.getOffset();
int len = e.getLength();
// 取得插入事件的位置
msg.append("插入文本的长度:" + len + "\n");
msg.append("插入文本的起始位置:" + offset + "\n");
try {
msg.append("插入文本内容:" + doc.getText(offset, len) + "\n");
} catch (BadLocationException evt) {
evt.printStackTrace();
}
}

// 当从Document中删除文本时触发该方法
public void removeUpdate(DocumentEvent e) {
int offset = e.getOffset();
int len = e.getLength();
// 取得插入事件的位置
msg.append("删除文本的长度:" + len + "\n");
msg.append("删除文本的起始位置:" + offset + "\n");
}
});
// 添加可撤销操作的监听器
doc.addUndoableEditListener(e -> {
// 每次发生可撤销操作都会触发该代码块 // ①
UndoableEdit edit = e.getEdit();
if (edit.canUndo() && undoList.size() < UNDO_COUNT) {
// 将撤销操作装入List内
undoList.add(edit);
}
// 已经达到了最大撤销次数
else if (edit.canUndo() && undoList.size() >= UNDO_COUNT) {
// 弹出第一个撤销操作
undoList.pop();
// 将撤销操作装入List内
undoList.add(edit);
}
});
// 为Ctrl+Z添加监听器
target.addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent e) // ②
{
System.out.println(e.getKeyChar() + 0);
// 如果按键是Ctrl + Z或Ctrl + z
if (e.getKeyChar() == 26) {
if (undoList.size() > 0) {
// 移出最后一个可撤销操作,并取消该操作
undoList.removeLast().undo();
}
}
}
});
Box box = new Box(BoxLayout.Y_AXIS);
box.add(new JScrollPane(target));
JPanel panel = new JPanel();
panel.add(label);
box.add(panel);
box.add(new JScrollPane(msg));
mainWin.add(box);
mainWin.pack();
mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWin.setVisible(true);
}

public static void main(String[] args) throws Exception {
new MonitorText().init();
}
}

代码分析

上面程序中的两段粗体字代码实现了Document中插入文本、删除文本的事件处理器,当用户向Document中插入文本、删除文本时,程序将会把这些修改信息添加到下面的一个文本域里

添加可撤销操作监听器

程序中①号代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 添加可撤销操作的监听器
doc.addUndoableEditListener(e -> {
// 每次发生可撤销操作都会触发该代码块 // ①
UndoableEdit edit = e.getEdit();
if (edit.canUndo() && undoList.size() < UNDO_COUNT) {
// 将撤销操作装入List内
undoList.add(edit);
}
// 已经达到了最大撤销次数
else if (edit.canUndo() && undoList.size() >= UNDO_COUNT) {
// 弹出第一个撤销操作
undoList.pop();
// 将撤销操作装入List内
undoList.add(edit);
}
});

是可撤销操作的事件处理器,当用户在该文本域内进行可撤销操作时,这段代码将会被触发,这段代码把用户刚刚进行的可撤销操作以List保存起来,以便在合适的时候撤销用户所做的修改。

添加Ctrl+Z监听器

程序中②号代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 为Ctrl+Z添加监听器
target.addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent e) // ②
{
System.out.println(e.getKeyChar() + 0);
// 如果按键是Ctrl + Z或Ctrl + z
if (e.getKeyChar() == 26) {
if (undoList.size() > 0) {
// 移出最后一个可撤销操作,并取消该操作
undoList.removeLast().undo();
}
}
}
});

主要用于为“Ctrl+Z”按键添加按键监听器,当用户按下“Ctrl+Z”键时,程序从保存可撤销操作的List中取岀最后一个可撤销操作,并撤销该操作的修改。
运行上面程序,会看到如图12.58所示的运行结果。