第6章:事件处理 - 让控件响应你的心意
作者:步子哥 (steper@foxmail.com)
6.1 事件系统的原理
监听器的本质
Button button = new Button(shell, SWT.PUSH);
button.addListener(SWT.Selection, event -> {
System.out.println("按钮被点击了");
});
费曼解释:监听器是什么?
监听器 = 守门人
- 守在门口(按钮)
- 等待事件(点击)
- 事件来了,立即处理(执行代码)
类比:
监听器 = 门铃
- 你安装了门铃(添加监听器)
- 有人按门铃(事件发生)
- 你听到铃声,去开门(执行代码)
事件对象的含义
button.addListener(SWT.Selection, event -> {
System.out.println("事件类型:" + event.type);
System.out.println("控件:" + event.widget);
System.out.println("时间:" + event.time);
System.out.println("数据:" + event.data);
});
费曼解释:事件对象携带了什么信息?
事件对象 = 快递包裹
- 包裹上有标签(事件类型)
- 包裹里有内容(数据)
- 收件人是你(处理代码)
类比:
事件对象 = 110 报警
- 你拨打 110(添加监听器)
- 接警员记录信息(事件对象)
- 报警时间(event.time)
- 报警类型(event.type)
- 报警地点(event.data)
- 派出警力(执行代码)
Event 对象的常用属性:
| 属性 | 类型 | 含义 | 类比 |
|---|---|---|---|
type | int | 事件类型 | 快递单类型 |
widget | Widget | 触发事件的控件 | 发件人 |
time | int | 事件发生的时间 | 时间戳 |
data | Object | 用户自定义数据 | 包裹内容 |
detail | int | 事件详细信息 | 额外信息 |
6.2 常用事件监听器
SelectionListener:点击与选择
// 方法 1:使用 addListener
Button button = new Button(shell, SWT.PUSH);
button.addListener(SWT.Selection, event -> {
System.out.println("按钮被点击了");
});
// 方法 2:使用 SelectionListener
button.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
System.out.println("按钮被选中");
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
System.out.println("按钮默认选中");
}
});
// 方法 3:使用 Lambda(推荐)
button.addSelectionListener(event -> {
System.out.println("按钮被选中");
});
费曼解释:SelectionListener 的两个方法
widgetSelected = 用户点击
- 按钮被点击
- 复选框被选中/取消
- 列表项被选择
widgetDefaultSelected = 用户双击
- 文本框双击
- 列表项双击
- 树节点双击
类比:
widgetSelected = 单击
- 你单击鼠标,选择一个选项
widgetDefaultSelected = 双击
- 你双击鼠标,打开一个文件
代码示例:各种 SelectionListener
public class SelectionListenerExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("SelectionListener 示例");
shell.setLayout(new FillLayout(SWT.VERTICAL));
// 按钮
Button button = new Button(shell, SWT.PUSH);
button.setText("点击我");
button.addListener(SWT.Selection, event -> {
System.out.println("按钮被点击");
});
// 复选框
Button checkBox = new Button(shell, SWT.CHECK);
checkBox.setText("记住密码");
checkBox.addListener(SWT.Selection, event -> {
boolean selected = checkBox.getSelection();
System.out.println("复选框状态:" + selected);
});
// 单选按钮
Composite radioGroup = new Composite(shell, SWT.NONE);
radioGroup.setLayout(new RowLayout(SWT.VERTICAL));
Label radioLabel = new Label(radioGroup, SWT.NONE);
radioLabel.setText("选择主题:");
Button radio1 = new Button(radioGroup, SWT.RADIO);
radio1.setText("深色");
Button radio2 = new Button(radioGroup, SWT.RADIO);
radio2.setText("浅色");
Button radio3 = new Button(radioGroup, SWT.RADIO);
radio3.setText("跟随系统");
radio3.setSelection(true);
radio1.addListener(SWT.Selection, event -> {
if (radio1.getSelection()) {
System.out.println("选择了深色主题");
}
});
radio2.addListener(SWT.Selection, event -> {
if (radio2.getSelection()) {
System.out.println("选择了浅色主题");
}
});
radio3.addListener(SWT.Selection, event -> {
if (radio3.getSelection()) {
System.out.println("选择了跟随系统主题");
}
});
// 列表
List list = new List(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
list.add("选项 1");
list.add("选项 2");
list.add("选项 3");
list.addListener(SWT.Selection, event -> {
String[] selectedItems = list.getSelection();
System.out.println("选中项:" + String.join(", ", selectedItems));
});
// 下拉列表
Combo combo = new Combo(shell, SWT.DROP_DOWN | SWT.READ_ONLY);
combo.add("苹果");
combo.add("香蕉");
combo.add("橙子");
combo.addListener(SWT.Selection, event -> {
System.out.println("下拉列表选择:" + combo.getText());
});
shell.setBounds(100, 100, 400, 500);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
ModifyListener:内容改变
Text text = new Text(shell, SWT.BORDER);
text.addListener(SWT.Modify, event -> {
System.out.println("文本内容:" + text.getText());
});
费曼解释:ModifyListener 的触发时机
ModifyListener = 看门狗
- 文本框的内容有任何变化
- 立即触发监听器
- 每个字符输入都会触发
类比:
ModifyListener = 文件监控
- 文件被修改
- 立即通知你
- 每次保存都会触发
代码示例:实时字数统计
public class ModifyListenerExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("ModifyListener 示例");
shell.setLayout(new GridLayout(2, false));
// 标签
Label label = new Label(shell, SWT.NONE);
label.setText("输入文本:");
// 文本框
Text text = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
GridData gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.verticalAlignment = GridData.FILL;
gridData.grabExcessHorizontalSpace = true;
gridData.grabExcessVerticalSpace = true;
text.setLayoutData(gridData);
// 字数统计
Label countLabel = new Label(shell, SWT.NONE);
countLabel.setText("字数:0");
GridData countData = new GridData();
countData.horizontalSpan = 2;
countLabel.setLayoutData(countData);
// 监听文本改变
text.addListener(SWT.Modify, event -> {
String content = text.getText();
int charCount = content.length();
int wordCount = content.split("\\s+").length;
int lineCount = text.getLineCount();
countLabel.setText(String.format("字符数:%d,单词数:%d,行数:%d",
charCount, wordCount, lineCount));
});
shell.setBounds(100, 100, 400, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
FocusListener:焦点获得与失去
Text text = new Text(shell, SWT.BORDER);
text.addListener(SWT.FocusIn, event -> {
System.out.println("文本框获得焦点");
});
text.addListener(SWT.FocusOut, event -> {
System.out.println("文本框失去焦点");
});
费曼解释:焦点的概念
焦点 = 舞光灯
- 只有一个控件有焦点(被舞台灯照亮)
- 这个控件接收键盘输入
- 其他控件处于"黑暗"中
类比:
焦点 = 主持人
- 只有一个人在说话(有焦点)
- 其他人听(无焦点)
- 切换焦点 = 换主持人
代码示例:焦点验证
public class FocusListenerExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("FocusListener 示例");
shell.setLayout(new GridLayout(2, false));
// 用户名
Label usernameLabel = new Label(shell, SWT.NONE);
usernameLabel.setText("用户名:");
Text usernameText = new Text(shell, SWT.BORDER);
GridData usernameData = new GridData();
usernameData.horizontalAlignment = GridData.FILL;
usernameData.grabExcessHorizontalSpace = true;
usernameText.setLayoutData(usernameData);
// 邮箱
Label emailLabel = new Label(shell, SWT.NONE);
emailLabel.setText("邮箱:");
Text emailText = new Text(shell, SWT.BORDER);
GridData emailData = new GridData();
emailData.horizontalAlignment = GridData.FILL;
emailData.grabExcessHorizontalSpace = true;
emailText.setLayoutData(emailData);
// 验证邮箱
emailText.addListener(SWT.FocusOut, event -> {
String email = emailText.getText();
if (!email.contains("@")) {
emailText.setBackground(display.getSystemColor(SWT.COLOR_RED));
emailText.setMessage("邮箱格式不正确(需要包含 @)");
} else {
emailText.setBackground(null);
emailText.setMessage("");
}
});
// 电话
Label phoneLabel = new Label(shell, SWT.NONE);
phoneLabel.setText("电话:");
Text phoneText = new Text(shell, SWT.BORDER);
GridData phoneData = new GridData();
phoneData.horizontalAlignment = GridData.FILL;
phoneData.grabExcessHorizontalSpace = true;
phoneText.setLayoutData(phoneData);
// 验证电话(必须是数字)
phoneText.addListener(SWT.FocusOut, event -> {
String phone = phoneText.getText();
if (!phone.matches("\\d+")) {
phoneText.setBackground(display.getSystemColor(SWT.COLOR_RED));
phoneText.setMessage("电话格式不正确(必须是数字)");
} else {
phoneText.setBackground(null);
phoneText.setMessage("");
}
});
// 提交按钮
Button submitButton = new Button(shell, SWT.PUSH);
submitButton.setText("提交");
GridData submitData = new GridData();
submitData.horizontalSpan = 2;
submitData.horizontalAlignment = GridData.CENTER;
submitButton.setLayoutData(submitData);
submitButton.addListener(SWT.Selection, event -> {
System.out.println("用户名:" + usernameText.getText());
System.out.println("邮箱:" + emailText.getText());
System.out.println("电话:" + phoneText.getText());
});
shell.setBounds(100, 100, 400, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
KeyListener:键盘按键
Text text = new Text(shell, SWT.BORDER);
text.addListener(SWT.KeyDown, event -> {
System.out.println("按下按键:" + event.keyCode);
System.out.println("字符:" + event.character);
// 检测是否按下 Enter 键
if (event.keyCode == SWT.CR || event.keyCode == SWT.LF) {
System.out.println("按下了 Enter 键");
}
// 检测是否按下 Ctrl+C
if (event.stateMask == SWT.CTRL && event.keyCode == 'c') {
System.out.println("按下了 Ctrl+C");
}
});
费曼解释:按键事件的两个属性
keyCode = 按键的代码
- 键盘上的每个键都有唯一的代码
- 例如:'A' 键的代码是 65
- 例如:Enter 键的代码是 SWT.CR
character = 输入的字符
- 按键对应的字符
- 例如:按下 'A' 键,character = 'A'
- 例如:按下 Shift+'A',character = 'a'
类比:
keyCode = 按钮的位置
- 第 1 行第 1 列的按钮
character = 按钮上的字
- 按钮上写着 'A'
常用键码:
| 键码 | 含义 | 类比 |
|---|---|---|
SWT.CR | Enter | 回车键 |
SWT.LF | Line Feed | 换行键 |
SWT.TAB | Tab | 制表键 |
SWT.ESC | Esc | 退出键 |
SWT.BS | Backspace | 退格键 |
SWT.DEL | Delete | 删除键 |
SWT.INSERT | Insert | 插入键 |
SWT.HOME | Home | 首页键 |
SWT.END | End | 结尾键 |
SWT.PAGE_UP | Page Up | 上翻页 |
SWT.PAGE_DOWN | Page Down | 下翻页 |
SWT.F1 ~ SWT.F12 | F1 ~ F12 | 功能键 |
修饰键:
| 修饰键 | 含义 | 类比 |
|---|---|---|
SWT.SHIFT | Shift | 上档键 |
SWT.CTRL | Ctrl | 控制键 |
SWT.ALT | Alt | 替换键 |
SWT.COMMAND | Command (Mac) | 命令键 |
代码示例:键盘快捷键
public class KeyListenerExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("KeyListener 示例");
shell.setLayout(new GridLayout(1, false));
// 文本框
Text text = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
GridData gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.verticalAlignment = GridData.FILL;
gridData.grabExcessHorizontalSpace = true;
gridData.grabExcessVerticalSpace = true;
text.setLayoutData(gridData);
// 监听按键
text.addListener(SWT.KeyDown, event -> {
// Ctrl+S:保存
if (event.stateMask == SWT.CTRL && event.keyCode == 's') {
event.doit = false; // 阻止默认行为(不在文本框中输入 's')
System.out.println("快捷键:Ctrl+S(保存)");
System.out.println("文本内容:" + text.getText());
}
// Ctrl+C:复制(默认行为)
if (event.stateMask == SWT.CTRL && event.keyCode == 'c') {
System.out.println("快捷键:Ctrl+C(复制)");
}
// Ctrl+V:粘贴(默认行为)
if (event.stateMask == SWT.CTRL && event.keyCode == 'v') {
System.out.println("快捷键:Ctrl+V(粘贴)");
}
// Ctrl+A:全选
if (event.stateMask == SWT.CTRL && event.keyCode == 'a') {
event.doit = false;
text.selectAll();
System.out.println("快捷键:Ctrl+A(全选)");
}
// Enter:换行
if (event.keyCode == SWT.CR || event.keyCode == SWT.LF) {
System.out.println("按下 Enter 键");
}
// Tab:插入 4 个空格
if (event.keyCode == SWT.TAB) {
event.doit = false;
text.insert(" ");
System.out.println("按下 Tab 键");
}
});
// 状态栏
Label statusLabel = new Label(shell, SWT.BORDER);
GridData statusData = new GridData();
statusData.horizontalAlignment = GridData.FILL;
statusData.grabExcessHorizontalSpace = true;
statusLabel.setLayoutData(statusData);
text.addListener(SWT.Modify, event -> {
statusLabel.setText("字符数:" + text.getText().length());
});
shell.setBounds(100, 100, 400, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
MouseListener:鼠标点击
Button button = new Button(shell, SWT.PUSH);
button.setText("点击我");
button.addListener(SWT.MouseDown, event -> {
System.out.println("鼠标按下:x=" + event.x + ", y=" + event.y);
System.out.println("按钮:" + event.button);
});
button.addListener(SWT.MouseUp, event -> {
System.out.println("鼠标释放:x=" + event.x + ", y=" + event.y);
});
button.addListener(SWT.MouseDoubleClick, event -> {
System.out.println("鼠标双击:x=" + event.x + ", y=" + event.y);
});
费曼解释:鼠标事件的三个类型
MouseDown = 按下鼠标
- 鼠标按键按下
- 还没释放
MouseUp = 释放鼠标
- 鼠标按键释放
- 完成一次点击
MouseDoubleClick = 双击鼠标
- 快速点击两次
- 系统判断为双击
类比:
MouseDown = 按门铃
- 手指按下门铃
MouseUp = 松开手指
- 手指松开
MouseDoubleClick = 按两下门铃
- 快速按两次
鼠标按键:
| 按钮 | 含义 | 类比 |
|---|---|---|
event.button == 1 | 左键 | 主要按钮 |
event.button == 2 | 中键 | 滚轮按下 |
event.button == 3 | 右键 | 上下文菜单 |
代码示例:鼠标事件追踪
public class MouseListenerExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("MouseListener 示例");
shell.setLayout(new GridLayout(2, false));
// 绘制区域
Canvas canvas = new Canvas(shell, SWT.BORDER);
GridData canvasData = new GridData();
canvasData.horizontalAlignment = GridData.FILL;
canvasData.verticalAlignment = GridData.FILL;
canvasData.grabExcessHorizontalSpace = true;
canvasData.grabExcessVerticalSpace = true;
canvasData.widthHint = 300;
canvasData.heightHint = 200;
canvas.setLayoutData(canvasData);
// 绘制圆圈的位置
int[] circle = new int[]{150, 100}; // x, y
// 绘制
canvas.addPaintListener(event -> {
GC gc = event.gc;
gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
gc.fillRectangle(canvas.getClientArea());
gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE));
gc.fillOval(circle[0] - 20, circle[1] - 20, 40, 40);
});
// 鼠标移动
canvas.addListener(SWT.MouseMove, event -> {
System.out.println("鼠标移动:x=" + event.x + ", y=" + event.y);
});
// 鼠标按下
canvas.addListener(SWT.MouseDown, event -> {
System.out.println("鼠标按下:x=" + event.x + ", y=" + event.y + ", 按钮=" + event.button);
// 左键:移动圆圈
if (event.button == 1) {
circle[0] = event.x;
circle[1] = event.y;
canvas.redraw();
}
// 右键:改变颜色
if (event.button == 3) {
circle[0] = event.x;
circle[1] = event.y;
canvas.redraw();
}
});
// 鼠标双击
canvas.addListener(SWT.MouseDoubleClick, event -> {
System.out.println("鼠标双击:x=" + event.x + ", y=" + event.y);
// 双击:重置圆圈位置
circle[0] = 150;
circle[1] = 100;
canvas.redraw();
});
// 信息面板
Composite infoPanel = new Composite(shell, SWT.NONE);
infoPanel.setLayout(new GridLayout(1, false));
Label infoLabel = new Label(infoPanel, SWT.NONE);
infoLabel.setText("鼠标事件信息:");
Text infoText = new Text(infoPanel, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
GridData infoData = new GridData();
infoData.horizontalAlignment = GridData.FILL;
infoData.verticalAlignment = GridData.FILL;
infoData.grabExcessHorizontalSpace = true;
infoData.grabExcessVerticalSpace = true;
infoText.setLayoutData(infoData);
// 添加日志的辅助方法
canvas.addListener(SWT.MouseDown, event -> {
String info = String.format("按下:x=%d, y=%d, 按钮=%d\n",
event.x, event.y, event.button);
infoText.append(info);
});
canvas.addListener(SWT.MouseUp, event -> {
String info = String.format("释放:x=%d, y=%d, 按钮=%d\n",
event.x, event.y, event.button);
infoText.append(info);
});
canvas.addListener(SWT.MouseDoubleClick, event -> {
String info = String.format("双击:x=%d, y=%d\n",
event.x, event.y);
infoText.append(info);
});
shell.setBounds(100, 100, 500, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
MouseMoveListener:鼠标移动
Canvas canvas = new Canvas(shell, SWT.NONE);
canvas.addListener(SWT.MouseMove, event -> {
System.out.println("鼠标位置:x=" + event.x + ", y=" + event.y);
});
费曼解释:MouseMoveListener 的触发频率
MouseMoveListener = 雷达
- 持续追踪鼠标位置
- 每次移动都会触发
- 触发频率很高
类比:
MouseMoveListener = 摄像头追踪
- 摄像头持续追踪目标
- 目标移动,摄像头跟随
- 实时更新位置
6.3 匿名类 vs Lambda 表达式
从冗长到简洁的演进
匿名类(Java 7 及之前):
button.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
System.out.println("按钮被点击");
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
// 不需要实现,空着
}
});
匿名内部类(简化):
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
System.out.println("按钮被点击");
}
});
Lambda 表达式(Java 8+):
button.addListener(SWT.Selection, event -> {
System.out.println("按钮被点击");
});
费曼解释:Lambda 的优势
匿名类 = 手写信
- 写得很详细
- 占很多纸
- 看起来很正式
Lambda = 短信
- 写得很简练
- 占很少字
- 一目了然
类比:
匿名类 = 正式场合穿西装
- 庄重,但繁琐
Lambda = 休闲场合穿 T 恤
- 轻松,效率高
Lambda 的局限(需要 Java 8+)
Lambda 只适用于函数式接口(只有一个抽象方法的接口)
// 可以用 Lambda
button.addListener(SWT.Selection, event -> {
System.out.println("按钮被点击");
});
// 不能用 Lambda(SelectionListener 有两个抽象方法)
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
System.out.println("按钮被点击");
}
});
费曼解释:为什么 SelectionListener 不能用 Lambda?
Lambda = 单一任务
- 只能处理一种情况
- SelectionListener 有两种情况(widgetSelected、widgetDefaultSelected)
- 无法用 Lambda
SelectionAdapter = 默认实现
- 实现了 SelectionListener
- 提供了默认实现
- 可以只重写需要的方法
类比:
Lambda = 单功能工具
- 只能拧螺丝
SelectionAdapter = 多功能工具
- 可以拧螺丝
- 可以锯木头
- 可以锤钉子
- 你只拿你需要的功能
6.4 事件的传播与取消
doit 字段的秘密
Text text = new Text(shell, SWT.BORDER);
text.addListener(SWT.KeyDown, event -> {
// 检测是否按下 Enter 键
if (event.keyCode == SWT.CR || event.keyCode == SWT.LF) {
System.out.println("按下了 Enter 键");
// 阻止默认行为(不在文本框中输入换行)
event.doit = false;
// 执行自定义行为
System.out.println("执行提交操作");
}
});
费曼解释:doit 字段的作用
doit = 放行标志
- doit = true:放行(执行默认行为)
- doit = false:拦截(阻止默认行为)
类比:
doit = 保安
- doit = true:放行,让你进入
- doit = false:拦截,不让你进入
代码示例:阻止文本框输入特殊字符
public class EventCancelExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("事件取消示例");
shell.setLayout(new GridLayout(2, false));
// 文本框:只允许输入数字
Label numberLabel = new Label(shell, SWT.NONE);
numberLabel.setText("数字输入(只允许数字):");
Text numberText = new Text(shell, SWT.BORDER);
GridData numberData = new GridData();
numberData.horizontalAlignment = GridData.FILL;
numberData.grabExcessHorizontalSpace = true;
numberText.setLayoutData(numberData);
numberText.addListener(SWT.Verify, event -> {
char c = event.character;
if (c >= '0' && c <= '9') {
// 数字,允许输入
event.doit = true;
} else {
// 非数字,阻止输入
event.doit = false;
System.out.println("阻止输入:" + c);
}
});
// 文本框:限制长度
Label lengthLabel = new Label(shell, SWT.NONE);
lengthLabel.setText("长度限制(最多 10 字符):");
Text lengthText = new Text(shell, SWT.BORDER);
GridData lengthData = new GridData();
lengthData.horizontalAlignment = GridData.FILL;
lengthData.grabExcessHorizontalSpace = true;
lengthText.setLayoutData(lengthData);
lengthText.addListener(SWT.Verify, event -> {
int currentLength = lengthText.getText().length();
int selectionLength = lengthText.getSelectionCount();
int newLength = currentLength - selectionLength + event.text.length();
if (newLength > 10) {
event.doit = false;
System.out.println("阻止输入:超过最大长度 10");
}
});
// 文本框:不允许粘贴
Label pasteLabel = new Label(shell, SWT.NONE);
pasteLabel.setText("不允许粘贴:");
Text pasteText = new Text(shell, SWT.BORDER);
GridData pasteData = new GridData();
pasteData.horizontalAlignment = GridData.FILL;
pasteData.grabExcessHorizontalSpace = true;
pasteText.setLayoutData(pasteData);
pasteText.addListener(SWT.KeyDown, event -> {
// 检测是否按下 Ctrl+V(粘贴)
if (event.stateMask == SWT.CTRL && event.keyCode == 'v') {
event.doit = false;
System.out.println("阻止粘贴");
}
});
shell.setBounds(100, 100, 400, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
事件链与优先级
Shell shell = new Shell(display);
shell.addListener(SWT.MouseDown, event -> {
System.out.println("Shell 鼠标按下");
});
Composite composite = new Composite(shell, SWT.NONE);
composite.addListener(SWT.MouseDown, event -> {
System.out.println("Composite 鼠标按下");
});
Button button = new Button(composite, SWT.PUSH);
button.setText("按钮");
button.addListener(SWT.MouseDown, event -> {
System.out.println("Button 鼠标按下");
event.doit = false; // 阻止事件继续传播
});
费曼解释:事件传播的顺序
事件传播 = 泼水
- 从下往上(Button → Composite → Shell)
- 每一层都可以拦截
- 拦截后,不再向上传播
类比:
事件传播 = 汇报工作
- 员工(Button)汇报给经理(Composite)
- 经理汇报给老板(Shell)
- 任何一层都可以"拦截"(不汇报)
代码示例:事件传播测试
public class EventPropagationExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("事件传播示例");
shell.setLayout(new FillLayout());
shell.addListener(SWT.MouseDown, event -> {
System.out.println("Shell: MouseDown");
});
shell.addListener(SWT.MouseUp, event -> {
System.out.println("Shell: MouseUp");
});
Composite composite = new Composite(shell, SWT.NONE);
composite.addListener(SWT.MouseDown, event -> {
System.out.println("Composite: MouseDown");
});
composite.addListener(SWT.MouseUp, event -> {
System.out.println("Composite: MouseUp");
});
Button button = new Button(composite, SWT.PUSH);
button.setText("点击我");
button.addListener(SWT.MouseDown, event -> {
System.out.println("Button: MouseDown");
event.doit = false; // 阻止事件传播
});
button.addListener(SWT.MouseUp, event -> {
System.out.println("Button: MouseUp");
// 不设置 event.doit = false,允许事件传播
});
shell.setBounds(100, 100, 400, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
运行结果(点击按钮):
Button: MouseDown
Button: MouseUp
Composite: MouseUp
Shell: MouseUp
费曼解释:为什么 MouseUp 传播了?
MouseDown:
- 按钮拦截了(event.doit = false)
- 不向上传播
MouseUp:
- 按钮没有拦截(默认 event.doit = true)
- 向上传播到 Composite
- 继续传播到 Shell
6.5 本章小结
事件监听器总结
| 监听器 | 事件类型 | 用途 | 类比 |
|---|---|---|---|
| SelectionListener | SWT.Selection | 点击、选择 | 门铃 |
| ModifyListener | SWT.Modify | 内容改变 | 看门狗 |
| FocusListener | SWT.FocusIn/Out | 焦点获得/失去 | 舞光灯 |
| KeyListener | SWT.KeyDown/Up | 键盘按键 | 钢琴键 |
| MouseListener | SWT.MouseDown/Up/DoubleClick | 鼠标点击 | 门铃 |
| MouseMoveListener | SWT.MouseMove | 鼠标移动 | 雷达 |
费曼测试:你能解释清楚吗?
- event.doit = false 的作用?
- 阻止事件的默认行为
- widgetSelected 和 widgetDefaultSelected 的区别?
- widgetSelected:单击 - widgetDefaultSelected:双击
- 为什么 SelectionListener 不能用 Lambda?
- SelectionListener 有两个抽象方法,不是函数式接口
- 事件传播的顺序?
- 从下往上(Button → Composite → Shell)
下一章预告
现在你已经掌握了事件处理,
控件可以响应用户的每一次操作,
但你还只能创建简单的界面。
下一章,我们将学习高级控件,
包括:表格、树、菜单、工具栏...
让界面功能更强大,体验更丰富。
练习题:
- 创建一个文本框,监听键盘输入,按下 Enter 键时打印文本内容。
- 创建一个 Canvas,监听鼠标移动,实时显示鼠标坐标。
- 创建一个表单,包含用户名、邮箱,失去焦点时验证格式,错误时背景变红。
(提示:邮箱验证可以检查是否包含 '@',文本框背景用 setBackground(display.getSystemColor(SWT.COLOR_RED)))