第7章:高级控件 - 丰富的交互体验
作者:步子哥 (steper@foxmail.com)
7.1 表格(Table)
列与行的概念
Table table = new Table(shell, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
table.setHeaderVisible(true); // 显示表头
table.setLinesVisible(true); // 显示网格线
// 创建列
TableColumn column1 = new TableColumn(table, SWT.NONE);
column1.setText("姓名");
column1.setWidth(100);
TableColumn column2 = new TableColumn(table, SWT.NONE);
column2.setText("年龄");
column2.setWidth(80);
TableColumn column3 = new TableColumn(table, SWT.NONE);
column3.setText("邮箱");
column3.setWidth(200);
// 创建行
TableItem item1 = new TableItem(table, SWT.NONE);
item1.setText(new String[]{"张三", "25", "zhangsan@example.com"});
TableItem item2 = new TableItem(table, SWT.NONE);
item2.setText(new String[]{"李四", "30", "lisi@example.com"});
TableItem item3 = new TableItem(table, SWT.NONE);
item3.setText(new String[]{"王五", "28", "wangwu@example.com"});
费曼解释:Table 的结构
Table = 电子表格
- 列:表头(姓名、年龄、邮箱)
- 行:数据(张三、25、zhangsan@example.com)
- 单元格:交叉点(张三的年龄 = 25)
类比:
Table = 电影院座位表
- 列:座位号(A列、B列、C列)
- 行:排号(1排、2排、3排)
- 单元格:具体座位(A列1排 = A1座位)
表头的添加与排序
// 创建列
TableColumn column = new TableColumn(table, SWT.NONE);
column.setText("姓名");
column.setWidth(100);
// 添加排序功能
column.addListener(SWT.Selection, event -> {
// 获取列索引
int columnIndex = table.indexOf(column);
// 获取所有项
TableItem[] items = table.getItems();
// 排序
Arrays.sort(items, (a, b) -> {
String textA = a.getText(columnIndex);
String textB = b.getText(columnIndex);
return textA.compareTo(textB);
});
// 重新添加排序后的项
table.removeAll();
for (TableItem item : items) {
TableItem newItem = new TableItem(table, SWT.NONE);
newItem.setText(item.getText());
}
});
费曼解释:表格排序的原理
表格排序 = 整理扑克牌
- 打乱顺序的牌(未排序的行)
- 按花色整理(按列排序)
- 按数字整理(按内容排序)
类比:
表格排序 = 按身高的班级排队
- 打乱的班级(未排序的表格)
- 按身高排队(按列排序)
- 最矮的排前面(升序)
- 最高的排后面(升序)
单选、多选、复选框列
// 单选表格
Table singleTable = new Table(shell, SWT.BORDER | SWT.SINGLE);
// 多选表格
Table multiTable = new Table(shell, SWT.BORDER | SWT.MULTI);
// 复选框表格
Table checkTable = new Table(shell, SWT.BORDER | SWT.CHECK);
// 判断复选框是否选中
TableItem item = checkTable.getItem(0);
boolean checked = item.getChecked();
费曼解释:三种选择模式的区别
单选 = 单选题
- 只能选一个
- 选了新的,取消旧的
多选 = 多选题
- 可以选多个
- 按住 Ctrl 点击多选
复选框 = 打钩题
- 每行都有复选框
- 独立勾选,互不影响
类比:
单选 = 单选按钮(圆圈)
- 只能选一个
多选 = 按住 Shift 选择文件
- 可以选多个
复选框 = 购物车商品列表
- 每个商品前面都有复选框
- 勾选要买的
虚拟表格(Virtual Table):处理大数据
// 创建虚拟表格
Table virtualTable = new Table(shell, SWT.BORDER | SWT.VIRTUAL | SWT.MULTI);
virtualTable.setHeaderVisible(true);
virtualTable.setLinesVisible(true);
// 创建列
TableColumn column1 = new TableColumn(virtualTable, SWT.NONE);
column1.setText("姓名");
column1.setWidth(100);
TableColumn column2 = new TableColumn(virtualTable, SWT.NONE);
column2.setText("年龄");
column2.setWidth(80);
// 设置总行数(10000行)
virtualTable.setItemCount(10000);
// 设置数据提供者(只在需要时创建行)
virtualTable.addListener(SWT.SetData, event -> {
TableItem item = (TableItem) event.item;
int index = virtualTable.indexOf(item);
// 模拟数据
item.setText(new String[]{
"用户 " + index,
String.valueOf(20 + index % 50),
"user" + index + "@example.com"
});
});
费曼解释:虚拟表格的原理
普通表格 = 手工记账
- 10000 笔账,全部手写
- 纸张堆成山
- 找一笔账,翻半天
虚拟表格 = 电子记账
- 10000 笔账,存在数据库
- 只显示当前页面的账
- 找一笔账,立即显示
类比:
普通表格 = 翻书
- 10000 页,全部翻开
- 需要大量内存
虚拟表格 = 电子书阅读器
- 10000 页,只加载当前页
- 省内存,加载快
代码示例:表格完整功能
public class TableExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("Table 示例");
shell.setLayout(new GridLayout(2, false));
// 表格
Table table = new Table(shell, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION | SWT.CHECK);
table.setHeaderVisible(true);
table.setLinesVisible(true);
GridData tableData = new GridData();
tableData.horizontalAlignment = GridData.FILL;
tableData.verticalAlignment = GridData.FILL;
tableData.grabExcessHorizontalSpace = true;
tableData.grabExcessVerticalSpace = true;
tableData.horizontalSpan = 2;
table.setLayoutData(tableData);
// 创建列
TableColumn column1 = new TableColumn(table, SWT.NONE);
column1.setText("姓名");
column1.setWidth(100);
TableColumn column2 = new TableColumn(table, SWT.NONE);
column2.setText("年龄");
column2.setWidth(80);
TableColumn column3 = new TableColumn(table, SWT.NONE);
column3.setText("邮箱");
column3.setWidth(200);
TableColumn column4 = new TableColumn(table, SWT.NONE);
column4.setText("选择");
column4.setWidth(60);
// 添加列排序
column1.addListener(SWT.Selection, event -> {
sortTable(table, 0);
});
column2.addListener(SWT.Selection, event -> {
sortTable(table, 1);
});
column3.addListener(SWT.Selection, event -> {
sortTable(table, 2);
});
// 添加初始数据
addTableItem(table, "张三", "25", "zhangsan@example.com", true);
addTableItem(table, "李四", "30", "lisi@example.com", false);
addTableItem(table, "王五", "28", "wangwu@example.com", true);
addTableItem(table, "赵六", "35", "zhaoliu@example.com", false);
addTableItem(table, "孙七", "22", "sunqi@example.com", true);
// 按钮面板
Composite buttonPanel = new Composite(shell, SWT.NONE);
buttonPanel.setLayout(new FillLayout(SWT.VERTICAL));
GridData buttonData = new GridData();
buttonData.verticalAlignment = GridData.FILL;
buttonData.grabExcessVerticalSpace = true;
buttonPanel.setLayoutData(buttonData);
// 添加按钮
Button addButton = new Button(buttonPanel, SWT.PUSH);
addButton.setText("添加行");
addButton.addListener(SWT.Selection, event -> {
addTableItem(table, "新用户", "0", "new@example.com", false);
});
// 删除按钮
Button removeButton = new Button(buttonPanel, SWT.PUSH);
removeButton.setText("删除选中");
removeButton.addListener(SWT.Selection, event -> {
int[] indices = table.getSelectionIndices();
Arrays.sort(indices);
for (int i = indices.length - 1; i >= 0; i--) {
table.remove(indices[i]);
}
});
// 清空按钮
Button clearButton = new Button(buttonPanel, SWT.PUSH);
clearButton.setText("清空");
clearButton.addListener(SWT.Selection, event -> {
table.removeAll();
});
// 获取选中按钮
Button getSelectedButton = new Button(buttonPanel, SWT.PUSH);
getSelectedButton.setText("获取选中");
getSelectedButton.addListener(SWT.Selection, event -> {
TableItem[] selectedItems = table.getSelection();
for (TableItem item : selectedItems) {
System.out.println("选中:");
System.out.println(" 勾选:" + item.getChecked());
System.out.println(" 姓名:" + item.getText(0));
System.out.println(" 年龄:" + item.getText(1));
System.out.println(" 邮箱:" + item.getText(2));
}
});
// 获取勾选按钮
Button getCheckedButton = new Button(buttonPanel, SWT.PUSH);
getCheckedButton.setText("获取勾选");
getCheckedButton.addListener(SWT.Selection, event -> {
TableItem[] items = table.getItems();
for (TableItem item : items) {
if (item.getChecked()) {
System.out.println("勾选:" + item.getText(0));
}
}
});
// 信息面板
Composite infoPanel = new Composite(shell, SWT.NONE);
infoPanel.setLayout(new GridLayout(1, false));
GridData infoData = new GridData();
infoData.verticalAlignment = GridData.FILL;
infoData.grabExcessVerticalSpace = true;
infoPanel.setLayoutData(infoData);
Label infoLabel = new Label(infoPanel, SWT.NONE);
infoLabel.setText("选中信息:");
Text infoText = new Text(infoPanel, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
GridData infoTextData = new GridData();
infoTextData.horizontalAlignment = GridData.FILL;
infoTextData.verticalAlignment = GridData.FILL;
infoTextData.grabExcessHorizontalSpace = true;
infoTextData.grabExcessVerticalSpace = true;
infoText.setLayoutData(infoTextData);
// 监听选择事件
table.addListener(SWT.Selection, event -> {
TableItem[] selectedItems = table.getSelection();
StringBuilder sb = new StringBuilder();
for (TableItem item : selectedItems) {
sb.append(item.getText(0)).append(", ")
.append(item.getText(1)).append(", ")
.append(item.getText(2)).append("\n");
}
infoText.setText(sb.toString());
});
shell.setBounds(100, 100, 600, 400);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private static void addTableItem(Table table, String name, String age, String email, boolean checked) {
TableItem item = new TableItem(table, SWT.NONE);
item.setText(new String[]{name, age, email, checked ? "是" : "否"});
item.setChecked(checked);
}
private static void sortTable(Table table, int columnIndex) {
TableItem[] items = table.getItems();
Arrays.sort(items, (a, b) -> {
String textA = a.getText(columnIndex);
String textB = b.getText(columnIndex);
// 尝试按数字排序
try {
int numA = Integer.parseInt(textA);
int numB = Integer.parseInt(textB);
return Integer.compare(numA, numB);
} catch (NumberFormatException e) {
// 按字符串排序
return textA.compareTo(textB);
}
});
table.removeAll();
for (TableItem item : items) {
TableItem newItem = new TableItem(table, SWT.NONE);
newItem.setText(item.getText());
newItem.setChecked(item.getChecked());
}
}
}
7.2 树(Tree)
节点与子节点
Tree tree = new Tree(shell, SWT.BORDER | SWT.MULTI);
// 创建根节点
TreeItem root1 = new TreeItem(tree, SWT.NONE);
root1.setText("部门 A");
// 创建子节点
TreeItem child1 = new TreeItem(root1, SWT.NONE);
child1.setText("员工 1");
TreeItem child2 = new TreeItem(root1, SWT.NONE);
child2.setText("员工 2");
// 创建根节点
TreeItem root2 = new TreeItem(tree, SWT.NONE);
root2.setText("部门 B");
// 创建子节点
TreeItem child3 = new TreeItem(root2, SWT.NONE);
child3.setText("员工 3");
费曼解释:Tree 的结构
Tree = 家族树
- 根节点:祖先(部门)
- 子节点:子孙(员工)
- 多层嵌套:曾孙、玄孙...
类比:
Tree = 文件夹树
- 根节点:C盘
- 子节点:Program Files
- 孙节点:Java
- 曾孙节点:jdk1.8
展开/折叠的控制
// 展开所有
treeItem.setExpanded(true);
// 折叠
treeItem.setExpanded(false);
// 展开整个树
treeItem.setExpanded(true);
// 判断是否展开
boolean expanded = treeItem.getExpanded();
费曼解释:展开/折叠的含义
展开 = 打开文件夹
- 显示文件夹里面的文件
- 你能看到里面的内容
折叠 = 关闭文件夹
- 隐藏文件夹里面的文件
- 你看不到里面的内容
类比:
展开/折叠 = 折扇
- 展开:扇子打开,看到所有扇骨
- 折叠:扇子合上,只看到一个扇子
树项的图标
// 创建树
Tree tree = new Tree(shell, SWT.BORDER);
// 创建图标(实际应用中应该从文件加载)
// Image folderIcon = new Image(display, "folder.png");
// Image fileIcon = new Image(display, "file.png");
// 创建根节点
TreeItem root = new TreeItem(tree, SWT.NONE);
root.setText("我的电脑");
// root.setImage(folderIcon);
// 创建子节点
TreeItem cDrive = new TreeItem(root, SWT.NONE);
cDrive.setText("C盘");
// cDrive.setImage(folderIcon);
TreeItem file1 = new TreeItem(cDrive, SWT.NONE);
file1.setText("文件1.txt");
// file1.setImage(fileIcon);
TreeItem file2 = new TreeItem(cDrive, SWT.NONE);
file2.setText("文件2.txt");
// file2.setImage(fileIcon);
// 展开根节点
root.setExpanded(true);
cDrive.setExpanded(true);
费曼解释:图标的作用
图标 = 路标
- 帮你快速识别类型
- 文件夹图标 = 这是一个文件夹
- 文件图标 = 这是一个文件
类比:
图标 = 交通标志
- 红灯 = 停
- 绿灯 = 行
- 一眼就能看懂
复杂的树形数据
// 创建树
Tree tree = new Tree(shell, SWT.BORDER);
// 辅助方法:递归创建树
public static TreeItem createTreeItem(TreeItem parent, String text) {
TreeItem item = parent == null ? new TreeItem(tree, SWT.NONE) : new TreeItem(parent, SWT.NONE);
item.setText(text);
return item;
}
// 创建公司组织结构
TreeItem company = createTreeItem(null, "公司");
company.setExpanded(true);
// 部门A
TreeItem deptA = createTreeItem(company, "技术部");
deptA.setExpanded(true);
TreeItem teamA1 = createTreeItem(deptA, "后端组");
createTreeItem(teamA1, "张三");
createTreeItem(teamA1, "李四");
TreeItem teamA2 = createTreeItem(deptA, "前端组");
createTreeItem(teamA2, "王五");
createTreeItem(teamA2, "赵六");
// 部门B
TreeItem deptB = createTreeItem(company, "产品部");
deptB.setExpanded(true);
TreeItem teamB1 = createTreeItem(deptB, "产品组");
createTreeItem(teamB1, "孙七");
createTreeItem(teamB1, "周八");
TreeItem teamB2 = createTreeItem(deptB, "运营组");
createTreeItem(teamB2, "吴九");
createTreeItem(teamB2, "郑十");
费曼解释:递归创建树的思想
递归 = 俄罗斯套娃
- 一个套娃里面还有套娃
- 大娃套中娃,中娃套小娃,小娃套小小娃...
- 层层嵌套,无限可能
类比:
递归 = 调查案件
- 调查嫌疑人A
- 嫌疑人A说嫌疑人B知道
- 调查嫌疑人B
- 嫌疑人B说嫌疑人C知道
- 调查嫌疑人C
- ...
- 直到找到真相
代码示例:文件管理器风格的树
public class TreeExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("Tree 示例");
shell.setLayout(new GridLayout(2, false));
// 树
Tree tree = new Tree(shell, SWT.BORDER | SWT.MULTI);
GridData treeData = new GridData();
treeData.horizontalAlignment = GridData.FILL;
treeData.verticalAlignment = GridData.FILL;
treeData.grabExcessHorizontalSpace = true;
treeData.grabExcessVerticalSpace = true;
tree.setLayoutData(treeData);
// 创建公司组织结构
createCompanyTree(tree);
// 监听选择事件
tree.addListener(SWT.Selection, event -> {
TreeItem[] selectedItems = tree.getSelection();
for (TreeItem item : selectedItems) {
System.out.println("选中:" + item.getText());
}
});
// 监听展开事件
tree.addListener(SWT.Expand, event -> {
TreeItem item = (TreeItem) event.item;
System.out.println("展开:" + item.getText());
});
// 监听折叠事件
tree.addListener(SWT.Collapse, event -> {
TreeItem item = (TreeItem) event.item;
System.out.println("折叠:" + item.getText());
});
// 按钮面板
Composite buttonPanel = new Composite(shell, SWT.NONE);
buttonPanel.setLayout(new FillLayout(SWT.VERTICAL));
GridData buttonData = new GridData();
buttonData.verticalAlignment = GridData.FILL;
buttonPanel.setLayoutData(buttonData);
// 展开全部按钮
Button expandAllButton = new Button(buttonPanel, SWT.PUSH);
expandAllButton.setText("展开全部");
expandAllButton.addListener(SWT.Selection, event -> {
TreeItem[] items = tree.getItems();
for (TreeItem item : items) {
expandAll(item);
}
});
// 折叠全部按钮
Button collapseAllButton = new Button(buttonPanel, SWT.PUSH);
collapseAllButton.setText("折叠全部");
collapseAllButton.addListener(SWT.Selection, event -> {
TreeItem[] items = tree.getItems();
for (TreeItem item : items) {
collapseAll(item);
}
});
// 获取选中按钮
Button getSelectedButton = new Button(buttonPanel, SWT.PUSH);
getSelectedButton.setText("获取选中");
getSelectedButton.addListener(SWT.Selection, event -> {
TreeItem[] selectedItems = tree.getSelection();
for (TreeItem item : selectedItems) {
// 获取完整路径
TreeItem parent = item.getParentItem();
StringBuilder path = new StringBuilder(item.getText());
while (parent != null) {
path.insert(0, parent.getText() + " > ");
parent = parent.getParentItem();
}
System.out.println("完整路径:" + path.toString());
}
});
// 添加节点按钮
Button addButton = new Button(buttonPanel, SWT.PUSH);
addButton.setText("添加子节点");
addButton.addListener(SWT.Selection, event -> {
TreeItem[] selectedItems = tree.getSelection();
if (selectedItems.length > 0) {
TreeItem parent = selectedItems[0];
TreeItem newItem = new TreeItem(parent, SWT.NONE);
newItem.setText("新节点 " + System.currentTimeMillis());
parent.setExpanded(true);
} else {
System.out.println("请先选择一个节点");
}
});
// 删除节点按钮
Button removeButton = new Button(buttonPanel, SWT.PUSH);
removeButton.setText("删除选中");
removeButton.addListener(SWT.Selection, event -> {
TreeItem[] selectedItems = tree.getSelection();
for (TreeItem item : selectedItems) {
item.dispose();
}
});
shell.setBounds(100, 100, 600, 400);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private static void createCompanyTree(Tree tree) {
// 公司
TreeItem company = new TreeItem(tree, SWT.NONE);
company.setText("公司");
company.setExpanded(true);
// 技术部
TreeItem techDept = new TreeItem(company, SWT.NONE);
techDept.setText("技术部");
TreeItem backend = new TreeItem(techDept, SWT.NONE);
backend.setText("后端组");
new TreeItem(backend, SWT.NONE).setText("张三");
new TreeItem(backend, SWT.NONE).setText("李四");
TreeItem frontend = new TreeItem(techDept, SWT.NONE);
frontend.setText("前端组");
new TreeItem(frontend, SWT.NONE).setText("王五");
new TreeItem(frontend, SWT.NONE).setText("赵六");
// 产品部
TreeItem productDept = new TreeItem(company, SWT.NONE);
productDept.setText("产品部");
TreeItem productGroup = new TreeItem(productDept, SWT.NONE);
productGroup.setText("产品组");
new TreeItem(productGroup, SWT.NONE).setText("孙七");
new TreeItem(productGroup, SWT.NONE).setText("周八");
TreeItem operationGroup = new TreeItem(productDept, SWT.NONE);
operationGroup.setText("运营组");
new TreeItem(operationGroup, SWT.NONE).setText("吴九");
new TreeItem(operationGroup, SWT.NONE).setText("郑十");
techDept.setExpanded(true);
productDept.setExpanded(true);
backend.setExpanded(true);
productGroup.setExpanded(true);
}
private static void expandAll(TreeItem item) {
item.setExpanded(true);
TreeItem[] children = item.getItems();
for (TreeItem child : children) {
expandAll(child);
}
}
private static void collapseAll(TreeItem item) {
item.setExpanded(false);
TreeItem[] children = item.getItems();
for (TreeItem child : children) {
collapseAll(child);
}
}
}
7.3 滚动条(Scrollable)
为什么有些控件自带滚动条?
// 自动显示滚动条的控件
Text text = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
List list = new List(shell, SWT.BORDER | SWT.V_SCROLL);
Table table = new Table(shell, SWT.BORDER | SWT.V_SCROLL);
Tree tree = new Tree(shell, SWT.BORDER | SWT.V_SCROLL);
// 不自动显示滚动条的控件
Label label = new Label(shell, SWT.BORDER); // Label 不支持滚动
Button button = new Button(shell, SWT.PUSH); // Button 不支持滚动
费曼解释:滚动条的显示逻辑
滚动条 = 电梯
- 楼太高(内容太多),需要电梯(滚动条)
- 楼不高(内容不多),不需要电梯(不显示滚动条)
类比:
滚动条 = 纸张的长短
- 纸短(内容少),不需要翻页
- 纸长(内容多),需要翻页
- 翻页就是滚动
滚动条的显示策略
// 总是显示滚动条
Text text1 = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
// 需要时显示滚动条
Text text2 = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
// 隐藏滚动条
Text text3 = new Text(shell, SWT.BORDER | SWT.MULTI);
// 获取滚动条
ScrollBar verticalBar = text.getVerticalBar();
ScrollBar horizontalBar = text.getHorizontalBar();
费曼解释:何时显示滚动条?
自动显示 = 智能门
- 有人来,自动开门(内容超出,显示滚动条)
- 没人来,自动关门(内容不超,隐藏滚动条)
类比:
自动显示 = 自动雨伞
- 下雨(内容超出),自动打开(显示滚动条)
- 不下雨(内容不超),自动收起(隐藏滚动条)
滚动事件的监听
Text text = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
text.setText("第一行\n第二行\n第三行\n...\n第一百行");
// 监听垂直滚动事件
ScrollBar verticalBar = text.getVerticalBar();
verticalBar.addListener(SWT.Selection, event -> {
System.out.println("垂直滚动:位置 = " + verticalBar.getSelection());
});
// 监听水平滚动事件
ScrollBar horizontalBar = text.getHorizontalBar();
horizontalBar.addListener(SWT.Selection, event -> {
System.out.println("水平滚动:位置 = " + horizontalBar.getSelection());
});
费曼解释:滚动事件的发生
滚动事件 = 翻书
- 你翻到第 10 页(滚动到位置 10)
- 事件告诉你:"你现在在第 10 页"
- 你可以做些事情(显示页码、加载内容...)
类比:
滚动事件 = 计步器
- 你走路,计步器记录步数
- 每走一步,步数+1
- 你可以看到当前的步数
7.4 菜单(Menu)
顶部菜单 vs 右键菜单
// 顶部菜单
Menu menuBar = new Menu(shell, SWT.BAR);
shell.setMenuBar(menuBar);
MenuItem fileMenu = new MenuItem(menuBar, SWT.CASCADE);
fileMenu.setText("文件");
Menu fileMenuContent = new Menu(shell, SWT.DROP_DOWN);
fileMenu.setMenu(fileMenuContent);
MenuItem newItem = new MenuItem(fileMenuContent, SWT.PUSH);
newItem.setText("新建");
MenuItem openItem = new MenuItem(fileMenuContent, SWT.PUSH);
openItem.setText("打开");
// 右键菜单
Menu popupMenu = new Menu(shell, SWT.POP_UP);
shell.setMenu(popupMenu);
MenuItem cutItem = new MenuItem(popupMenu, SWT.PUSH);
cutItem.setText("剪切");
MenuItem copyItem = new MenuItem(popupMenu, SWT.PUSH);
copyItem.setText("复制");
MenuItem pasteItem = new MenuItem(popupMenu, SWT.PUSH);
pasteItem.setText("粘贴");
费曼解释:两种菜单的区别
顶部菜单 = 餐厅菜单
- 贴在墙上
- 所有人都能看到
- 分类清晰(前菜、主菜、甜点)
右键菜单 = 秘密菜单
- 只有知道的人才能看到
- 右键点击才显示
- 上下文相关(针对选中的内容)
类比:
顶部菜单 = 超市的导航牌
- 一进门就能看到
- 所有商品分类
- 一目了然
右键菜单 = 购物车的推荐
- 看到一件商品,右键显示相关推荐
- 个性化推荐
- 针对当前商品
级联菜单(子菜单)
Menu menuBar = new Menu(shell, SWT.BAR);
shell.setMenuBar(menuBar);
MenuItem fileMenu = new MenuItem(menuBar, SWT.CASCADE);
fileMenu.setText("文件");
Menu fileMenuContent = new Menu(shell, SWT.DROP_DOWN);
fileMenu.setMenu(fileMenuContent);
MenuItem newItem = new MenuItem(fileMenuContent, SWT.CASCADE); // 级联菜单
newItem.setText("新建");
Menu newMenuContent = new Menu(shell, SWT.DROP_DOWN);
newItem.setMenu(newMenuContent);
MenuItem newText = new MenuItem(newMenuContent, SWT.PUSH);
newText.setText("文本文档");
MenuItem newFolder = new MenuItem(newMenuContent, SWT.PUSH);
newFolder.setText("文件夹");
费曼解释:级联菜单的结构
级联菜单 = 俄罗斯套娃
- 大套娃(文件菜单)
- 中套娃(新建菜单)
- 小套娃(文本文档、文件夹)
类比:
级联菜单 = 分类树
- 文件(根节点)
- 新建(子节点)
- 文本文档(孙子节点)
- 文件夹(孙子节点)
分隔线
Menu menuBar = new Menu(shell, SWT.BAR);
shell.setMenuBar(menuBar);
MenuItem fileMenu = new MenuItem(menuBar, SWT.CASCADE);
fileMenu.setText("文件");
Menu fileMenuContent = new Menu(shell, SWT.DROP_DOWN);
fileMenu.setMenu(fileMenuContent);
MenuItem newItem = new MenuItem(fileMenuContent, SWT.PUSH);
newItem.setText("新建");
MenuItem openItem = new MenuItem(fileMenuContent, SWT.PUSH);
openItem.setText("打开");
MenuItem saveItem = new MenuItem(fileMenuContent, SWT.PUSH);
saveItem.setText("保存");
// 分隔线
MenuItem separator = new MenuItem(fileMenuContent, SWT.SEPARATOR);
MenuItem exitItem = new MenuItem(fileMenuContent, SWT.PUSH);
exitItem.setText("退出");
费曼解释:分隔线的作用
分隔线 = 书签
- 书太长,用书签标记位置
- 让阅读更方便
类比:
分隔线 = 电影的分段
- 电影太长,分段播放
- 每段之间有间隔
- 让观影更轻松
菜单项的快捷键
MenuItem newItem = new MenuItem(fileMenuContent, SWT.PUSH);
newItem.setText("新建\tCtrl+N");
newItem.setAccelerator(SWT.CTRL + 'N');
MenuItem openItem = new MenuItem(fileMenuContent, SWT.PUSH);
openItem.setText("打开\tCtrl+O");
openItem.setAccelerator(SWT.CTRL + 'O');
MenuItem saveItem = new MenuItem(fileMenuContent, SWT.PUSH);
saveItem.setText("保存\tCtrl+S");
saveItem.setAccelerator(SWT.CTRL + 'S');
// 监听快捷键
newItem.addListener(SWT.Selection, event -> {
System.out.println("新建");
});
openItem.addListener(SWT.Selection, event -> {
System.out.println("打开");
});
saveItem.addListener(SWT.Selection, event -> {
System.out.println("保存");
});
费曼解释:快捷键的含义
快捷键 = 秘密通道
- 不用打开菜单
- 直接按键就能执行
- 省时省力
类比:
快捷键 = 近路
- 绕远路:打开菜单 → 点击新建(慢)
- 走近路:按 Ctrl+N(快)
- 目的地一样,路径不同
代码示例:完整菜单系统
public class MenuExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("Menu 示例");
shell.setLayout(new GridLayout(1, false));
// 文本编辑区域
Text text = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
GridData textData = new GridData();
textData.horizontalAlignment = GridData.FILL;
textData.verticalAlignment = GridData.FILL;
textData.grabExcessHorizontalSpace = true;
textData.grabExcessVerticalSpace = true;
textData.heightHint = 300;
text.setLayoutData(textData);
text.setText("这是一个文本编辑器示例。\n可以使用菜单或快捷键进行操作。");
// 顶部菜单
Menu menuBar = new Menu(shell, SWT.BAR);
shell.setMenuBar(menuBar);
// 文件菜单
MenuItem fileMenu = new MenuItem(menuBar, SWT.CASCADE);
fileMenu.setText("文件");
Menu fileMenuContent = new Menu(shell, SWT.DROP_DOWN);
fileMenu.setMenu(fileMenuContent);
MenuItem newItem = new MenuItem(fileMenuContent, SWT.PUSH);
newItem.setText("新建\tCtrl+N");
newItem.setAccelerator(SWT.CTRL + 'N');
newItem.addListener(SWT.Selection, event -> {
text.setText("");
System.out.println("新建");
});
MenuItem openItem = new MenuItem(fileMenuContent, SWT.PUSH);
openItem.setText("打开\tCtrl+O");
openItem.setAccelerator(SWT.CTRL + 'O');
openItem.addListener(SWT.Selection, event -> {
System.out.println("打开");
// 实际应用中应该弹出文件对话框
});
MenuItem saveItem = new MenuItem(fileMenuContent, SWT.PUSH);
saveItem.setText("保存\tCtrl+S");
saveItem.setAccelerator(SWT.CTRL + 'S');
saveItem.addListener(SWT.Selection, event -> {
System.out.println("保存:" + text.getText());
});
new MenuItem(fileMenuContent, SWT.SEPARATOR);
MenuItem exitItem = new MenuItem(fileMenuContent, SWT.PUSH);
exitItem.setText("退出\tCtrl+Q");
exitItem.setAccelerator(SWT.CTRL + 'Q');
exitItem.addListener(SWT.Selection, event -> {
shell.dispose();
System.out.println("退出");
});
// 编辑菜单
MenuItem editMenu = new MenuItem(menuBar, SWT.CASCADE);
editMenu.setText("编辑");
Menu editMenuContent = new Menu(shell, SWT.DROP_DOWN);
editMenu.setMenu(editMenuContent);
MenuItem cutItem = new MenuItem(editMenuContent, SWT.PUSH);
cutItem.setText("剪切\tCtrl+X");
cutItem.setAccelerator(SWT.CTRL + 'X');
cutItem.addListener(SWT.Selection, event -> {
text.cut();
System.out.println("剪切");
});
MenuItem copyItem = new MenuItem(editMenuContent, SWT.PUSH);
copyItem.setText("复制\tCtrl+C");
copyItem.setAccelerator(SWT.CTRL + 'C');
copyItem.addListener(SWT.Selection, event -> {
text.copy();
System.out.println("复制");
});
MenuItem pasteItem = new MenuItem(editMenuContent, SWT.PUSH);
pasteItem.setText("粘贴\tCtrl+V");
pasteItem.setAccelerator(SWT.CTRL + 'V');
pasteItem.addListener(SWT.Selection, event -> {
text.paste();
System.out.println("粘贴");
});
new MenuItem(editMenuContent, SWT.SEPARATOR);
MenuItem selectAllItem = new MenuItem(editMenuContent, SWT.PUSH);
selectAllItem.setText("全选\tCtrl+A");
selectAllItem.setAccelerator(SWT.CTRL + 'A');
selectAllItem.addListener(SWT.Selection, event -> {
text.selectAll();
System.out.println("全选");
});
// 视图菜单
MenuItem viewMenu = new MenuItem(menuBar, SWT.CASCADE);
viewMenu.setText("视图");
Menu viewMenuContent = new Menu(shell, SWT.DROP_DOWN);
viewMenu.setMenu(viewMenuContent);
MenuItem fontSizeMenu = new MenuItem(viewMenuContent, SWT.CASCADE);
fontSizeMenu.setText("字体大小");
Menu fontSizeMenuContent = new Menu(shell, SWT.DROP_DOWN);
fontSizeMenu.setMenu(fontSizeMenuContent);
MenuItem font12 = new MenuItem(fontSizeMenuContent, SWT.PUSH);
font12.setText("12");
font12.addListener(SWT.Selection, event -> {
Font font = new Font(display, "Arial", 12, SWT.NORMAL);
text.setFont(font);
font.dispose(); // 注意:字体需要手动释放
});
MenuItem font14 = new MenuItem(fontSizeMenuContent, SWT.PUSH);
font14.setText("14");
font14.addListener(SWT.Selection, event -> {
Font font = new Font(display, "Arial", 14, SWT.NORMAL);
text.setFont(font);
font.dispose();
});
MenuItem font16 = new MenuItem(fontSizeMenuContent, SWT.PUSH);
font16.setText("16");
font16.addListener(SWT.Selection, event -> {
Font font = new Font(display, "Arial", 16, SWT.NORMAL);
text.setFont(font);
font.dispose();
});
// 帮助菜单
MenuItem helpMenu = new MenuItem(menuBar, SWT.CASCADE);
helpMenu.setText("帮助");
Menu helpMenuContent = new Menu(shell, SWT.DROP_DOWN);
helpMenu.setMenu(helpMenuContent);
MenuItem aboutItem = new MenuItem(helpMenuContent, SWT.PUSH);
aboutItem.setText("关于");
aboutItem.addListener(SWT.Selection, event -> {
MessageBox messageBox = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
messageBox.setText("关于");
messageBox.setMessage("SWT 文本编辑器\n版本 1.0");
messageBox.open();
});
// 右键菜单
Menu popupMenu = new Menu(shell, SWT.POP_UP);
text.setMenu(popupMenu);
MenuItem popupCopy = new MenuItem(popupMenu, SWT.PUSH);
popupCopy.setText("复制");
popupCopy.addListener(SWT.Selection, event -> {
text.copy();
});
MenuItem popupPaste = new MenuItem(popupMenu, SWT.PUSH);
popupPaste.setText("粘贴");
popupPaste.addListener(SWT.Selection, event -> {
text.paste();
});
new MenuItem(popupMenu, SWT.SEPARATOR);
MenuItem popupSelectAll = new MenuItem(popupMenu, SWT.PUSH);
popupSelectAll.setText("全选");
popupSelectAll.addListener(SWT.Selection, event -> {
text.selectAll();
});
shell.setBounds(100, 100, 600, 400);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
7.5 工具栏(ToolBar)
工具按钮(ToolItem)
ToolBar toolBar = new ToolBar(shell, SWT.FLAT | SWT.HORIZONTAL);
ToolItem newItem = new ToolItem(toolBar, SWT.PUSH);
newItem.setText("新建");
newItem.setToolTipText("新建文档(Ctrl+N)");
ToolItem openItem = new ToolItem(toolBar, SWT.PUSH);
openItem.setText("打开");
openItem.setToolTipText("打开文档(Ctrl+O)");
ToolItem saveItem = new ToolItem(toolBar, SWT.PUSH);
saveItem.setText("保存");
saveItem.setToolTipText("保存文档(Ctrl+S)");
费曼解释:工具栏的用途
工具栏 = 工具箱
- 把常用工具放在手边
- 需要用时,立即拿取
- 省时省力
类比:
工具栏 = 厨师的调料架
- 盐、糖、酱油...
- 做菜时,随手可取
- 不用到处找
下拉式工具按钮
ToolItem dropdownItem = new ToolItem(toolBar, SWT.DROP_DOWN);
dropdownItem.setText("字体");
// 添加下拉菜单
Menu dropdownMenu = new Menu(shell, SWT.POP_UP);
dropdownItem.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
ToolItem item = (ToolItem) e.widget;
Rectangle bounds = item.getBounds();
Point point = item.getParent().toDisplay(bounds.x, bounds.y + bounds.height);
dropdownMenu.setLocation(point);
dropdownMenu.setVisible(true);
}
});
MenuItem font12 = new MenuItem(dropdownMenu, SWT.PUSH);
font12.setText("12");
MenuItem font14 = new MenuItem(dropdownMenu, SWT.PUSH);
font14.setText("14");
MenuItem font16 = new MenuItem(dropdownMenu, SWT.PUSH);
font16.setText("16");
费曼解释:下拉式工具按钮
下拉式工具按钮 = 多功能工具
- 一个按钮,多个功能
- 点击下拉,选择功能
- 省空间
类比:
下拉式工具按钮 = 瑞士军刀
- 一把刀,多个功能
- 刀、剪刀、锯子...
- 选择需要的功能
图标与提示文本
// 创建工具栏
ToolBar toolBar = new ToolBar(shell, SWT.FLAT);
// 创建工具按钮
ToolItem newItem = new ToolItem(toolBar, SWT.PUSH);
// newItem.setImage(newImage); // 设置图标
newItem.setText("新建");
newItem.setToolTipText("新建文档(Ctrl+N)");
ToolItem openItem = new ToolItem(toolBar, SWT.PUSH);
// openItem.setImage(openImage); // 设置图标
openItem.setText("打开");
openItem.setToolTipText("打开文档(Ctrl+O)");
费曼解释:图标与提示文本的作用
图标 = 交通标志
- 一眼就能看懂
- 不用看文字
提示文本 = 路标说明
- 鼠标悬停,显示说明
- 补充图标的信息
类比:
图标 + 提示文本 = 洗手间标志
- 图标:男女标志
- 提示文本:男厕所、女厕所
- 一看就懂
代码示例:完整工具栏
public class ToolBarExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("ToolBar 示例");
shell.setLayout(new GridLayout(1, false));
// 工具栏
ToolBar toolBar = new ToolBar(shell, SWT.FLAT | SWT.HORIZONTAL);
GridData toolBarData = new GridData();
toolBarData.horizontalAlignment = GridData.FILL;
toolBarData.grabExcessHorizontalSpace = true;
toolBar.setLayoutData(toolBarData);
// 新建按钮
ToolItem newItem = new ToolItem(toolBar, SWT.PUSH);
newItem.setText("新建");
newItem.setToolTipText("新建文档(Ctrl+N)");
newItem.addListener(SWT.Selection, event -> {
System.out.println("新建");
});
// 打开按钮
ToolItem openItem = new ToolItem(toolBar, SWT.PUSH);
openItem.setText("打开");
openItem.setToolTipText("打开文档(Ctrl+O)");
openItem.addListener(SWT.Selection, event -> {
System.out.println("打开");
});
// 保存按钮
ToolItem saveItem = new ToolItem(toolBar, SWT.PUSH);
saveItem.setText("保存");
saveItem.setToolTipText("保存文档(Ctrl+S)");
saveItem.addListener(SWT.Selection, event -> {
System.out.println("保存");
});
// 分隔线
new ToolItem(toolBar, SWT.SEPARATOR);
// 剪切按钮
ToolItem cutItem = new ToolItem(toolBar, SWT.PUSH);
cutItem.setText("剪切");
cutItem.setToolTipText("剪切(Ctrl+X)");
cutItem.addListener(SWT.Selection, event -> {
System.out.println("剪切");
});
// 复制按钮
ToolItem copyItem = new ToolItem(toolBar, SWT.PUSH);
copyItem.setText("复制");
copyItem.setToolTipText("复制(Ctrl+C)");
copyItem.addListener(SWT.Selection, event -> {
System.out.println("复制");
});
// 粘贴按钮
ToolItem pasteItem = new ToolItem(toolBar, SWT.PUSH);
pasteItem.setText("粘贴");
pasteItem.setToolTipText("粘贴(Ctrl+V)");
pasteItem.addListener(SWT.Selection, event -> {
System.out.println("粘贴");
});
// 分隔线
new ToolItem(toolBar, SWT.SEPARATOR);
// 撤销按钮
ToolItem undoItem = new ToolItem(toolBar, SWT.PUSH);
undoItem.setText("撤销");
undoItem.setToolTipText("撤销(Ctrl+Z)");
undoItem.addListener(SWT.Selection, event -> {
System.out.println("撤销");
});
// 重做按钮
ToolItem redoItem = new ToolItem(toolBar, SWT.PUSH);
redoItem.setText("重做");
redoItem.setToolTipText("重做(Ctrl+Y)");
redoItem.addListener(SWT.Selection, event -> {
System.out.println("重做");
});
// 分隔线
new ToolItem(toolBar, SWT.SEPARATOR);
// 下拉式工具按钮
ToolItem zoomItem = new ToolItem(toolBar, SWT.DROP_DOWN);
zoomItem.setText("缩放");
zoomItem.setToolTipText("缩放文档");
Menu zoomMenu = new Menu(shell, SWT.POP_UP);
zoomItem.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
ToolItem item = (ToolItem) e.widget;
Rectangle bounds = item.getBounds();
Point point = item.getParent().toDisplay(bounds.x, bounds.y + bounds.height);
zoomMenu.setLocation(point);
zoomMenu.setVisible(true);
}
});
MenuItem zoom25 = new MenuItem(zoomMenu, SWT.PUSH);
zoom25.setText("25%");
zoom25.addListener(SWT.Selection, event -> System.out.println("缩放 25%"));
MenuItem zoom50 = new MenuItem(zoomMenu, SWT.PUSH);
zoom50.setText("50%");
zoom50.addListener(SWT.Selection, event -> System.out.println("缩放 50%"));
MenuItem zoom100 = new MenuItem(zoomMenu, SWT.PUSH);
zoom100.setText("100%");
zoom100.addListener(SWT.Selection, event -> System.out.println("缩放 100%"));
MenuItem zoom200 = new MenuItem(zoomMenu, SWT.PUSH);
zoom200.setText("200%");
zoom200.addListener(SWT.Selection, event -> System.out.println("缩放 200%"));
// 文本区域
Text text = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
GridData textData = new GridData();
textData.horizontalAlignment = GridData.FILL;
textData.verticalAlignment = GridData.FILL;
textData.grabExcessHorizontalSpace = true;
textData.grabExcessVerticalSpace = true;
text.setLayoutData(textData);
text.setText("这是一个工具栏示例。\n点击工具栏按钮执行操作。");
shell.setBounds(100, 100, 600, 400);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
7.6 标签页(TabFolder)
多标签页管理
TabFolder tabFolder = new TabFolder(shell, SWT.BORDER);
// 第一个标签页
TabItem tab1 = new TabItem(tabFolder, SWT.NONE);
tab1.setText("标签页 1");
Composite composite1 = new Composite(tabFolder, SWT.NONE);
tab1.setControl(composite1);
composite1.setLayout(new FillLayout());
new Label(composite1, SWT.CENTER).setText("这是标签页 1 的内容");
// 第二个标签页
TabItem tab2 = new TabItem(tabFolder, SWT.NONE);
tab2.setText("标签页 2");
Composite composite2 = new Composite(tabFolder, SWT.NONE);
tab2.setControl(composite2);
composite2.setLayout(new FillLayout());
new Label(composite2, SWT.CENTER).setText("这是标签页 2 的内容");
费曼解释:标签页的用途
标签页 = 书签
- 一本书多个章节
- 用标签页标记位置
- 快速切换章节
类比:
标签页 = 浏览器的标签页
- 打开多个网页
- 每个网页一个标签页
- 点击切换,快速访问
标签页的关闭按钮
TabFolder tabFolder = new TabFolder(shell, SWT.CLOSE); // SWT.CLOSE 添加关闭按钮
TabItem tab1 = new TabItem(tabFolder, SWT.NONE);
tab1.setText("标签页 1");
// 监听关闭事件
tabFolder.addListener(SWT.Close, event -> {
TabItem item = (TabItem) event.item;
System.out.println("关闭标签页:" + item.getText());
item.dispose(); // 关闭标签页
});
费曼解释:关闭按钮的逻辑
关闭按钮 = 门铃
- 按一下,门开了(关闭标签页)
- 门开了之后,人就进来了(标签页移除)
类比:
关闭按钮 = 撕掉便利贴
- 撕掉,便利贴没了
- 位置空出来了
- 可以贴新的便利贴
代码示例:多标签页编辑器
public class TabFolderExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("TabFolder 示例");
shell.setLayout(new GridLayout(1, false));
// 标签页容器
TabFolder tabFolder = new TabFolder(shell, SWT.BORDER);
GridData tabData = new GridData();
tabData.horizontalAlignment = GridData.FILL;
tabData.verticalAlignment = GridData.FILL;
tabData.grabExcessHorizontalSpace = true;
tabData.grabExcessVerticalSpace = true;
tabData.heightHint = 300;
tabFolder.setLayoutData(tabData);
// 添加初始标签页
addTab(tabFolder, "文档 1", "这是文档 1 的内容。");
addTab(tabFolder, "文档 2", "这是文档 2 的内容。");
addTab(tabFolder, "文档 3", "这是文档 3 的内容。");
// 监听标签页选择
tabFolder.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
TabItem[] items = tabFolder.getSelection();
if (items.length > 0) {
System.out.println("选择标签页:" + items[0].getText());
}
}
});
// 按钮面板
Composite buttonPanel = new Composite(shell, SWT.NONE);
buttonPanel.setLayout(new FillLayout(SWT.HORIZONTAL));
Button newTabButton = new Button(buttonPanel, SWT.PUSH);
newTabButton.setText("新建标签页");
newTabButton.addListener(SWT.Selection, event -> {
int tabCount = tabFolder.getItemCount();
addTab(tabFolder, "文档 " + (tabCount + 1), "这是新建的文档 " + (tabCount + 1) + "。");
});
Button closeTabButton = new Button(buttonPanel, SWT.PUSH);
closeTabButton.setText("关闭当前标签页");
closeTabButton.addListener(SWT.Selection, event -> {
TabItem[] selected = tabFolder.getSelection();
if (selected.length > 0) {
selected[0].dispose();
} else {
System.out.println("没有选中的标签页");
}
});
Button closeAllButton = new Button(buttonPanel, SWT.PUSH);
closeAllButton.setText("关闭所有标签页");
closeAllButton.addListener(SWT.Selection, event -> {
tabFolder.dispose();
});
shell.setBounds(100, 100, 600, 400);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private static void addTab(TabFolder tabFolder, String title, String content) {
TabItem tab = new TabItem(tabFolder, SWT.NONE);
tab.setText(title);
Composite composite = new Composite(tabFolder, SWT.NONE);
composite.setLayout(new GridLayout(1, false));
Text text = new Text(composite, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
GridData textData = new GridData();
textData.horizontalAlignment = GridData.FILL;
textData.verticalAlignment = GridData.FILL;
textData.grabExcessHorizontalSpace = true;
textData.grabExcessVerticalSpace = true;
text.setLayoutData(textData);
text.setText(content);
tab.setControl(composite);
}
}
7.7 进度条(ProgressBar)
普通进度条 vs 不确定进度条
// 普通进度条
ProgressBar progressBar = new ProgressBar(shell, SWT.SMOOTH);
progressBar.setMinimum(0);
progressBar.setMaximum(100);
progressBar.setSelection(30);
// 不确定进度条(一直滚动)
ProgressBar indeterminateBar = new ProgressBar(shell, SWT.INDETERMINATE);
费曼解释:两种进度条的区别
普通进度条 = 加载条
- 知道总进度(100%)
- 知道当前进度(30%)
- 显示具体百分比
不确定进度条 = 等待动画
- 不知道总进度
- 不知道当前进度
- 只显示"正在处理"
类比:
普通进度条 = 加载电影
- 电影长度 2 小时
- 已播放 30 分钟
- 进度条显示 25%
不确定进度条 = 等待接电话
- 不知道什么时候来
- 只能等待
- 动画表示"正在等待"
进度的动画效果
ProgressBar progressBar = new ProgressBar(shell, SWT.SMOOTH);
progressBar.setMinimum(0);
progressBar.setMaximum(100);
// 模拟进度
Thread thread = new Thread(() -> {
for (int i = 0; i <= 100; i++) {
final int progress = i;
display.asyncExec(() -> {
progressBar.setSelection(progress);
});
try {
Thread.sleep(50); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
费曼解释:进度动画的原理
进度动画 = 跑马拉松
- 跑一步,更新一次位置
- 一直跑到终点
- 观众看到你的进度
类比:
进度动画 = 下载文件
- 下载 1%,进度条更新
- 下载 2%,进度条更新
- ...
- 下载 100%,进度条填满
代码示例:进度条演示
public class ProgressBarExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("ProgressBar 示例");
shell.setLayout(new GridLayout(1, false));
// 普通进度条
Label normalLabel = new Label(shell, SWT.NONE);
normalLabel.setText("普通进度条:");
ProgressBar normalBar = new ProgressBar(shell, SWT.SMOOTH);
normalBar.setMinimum(0);
normalBar.setMaximum(100);
GridData normalData = new GridData();
normalData.horizontalAlignment = GridData.FILL;
normalBar.setLayoutData(normalData);
// 进度文本
Label progressText = new Label(shell, SWT.CENTER);
GridData progressTextData = new GridData();
progressTextData.horizontalAlignment = GridData.FILL;
progressText.setLayoutData(progressTextData);
// 开始按钮
Button startButton = new Button(shell, SWT.PUSH);
startButton.setText("开始");
startButton.addListener(SWT.Selection, event -> {
Thread thread = new Thread(() -> {
for (int i = 0; i <= 100; i++) {
final int progress = i;
display.asyncExec(() -> {
normalBar.setSelection(progress);
progressText.setText("进度:" + progress + "%");
});
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
display.asyncExec(() -> {
progressText.setText("完成!");
});
});
thread.start();
});
// 不确定进度条
Label indeterminateLabel = new Label(shell, SWT.NONE);
indeterminateLabel.setText("不确定进度条:");
ProgressBar indeterminateBar = new ProgressBar(shell, SWT.INDETERMINATE);
GridData indeterminateData = new GridData();
indeterminateData.horizontalAlignment = GridData.FILL;
indeterminateBar.setLayoutData(indeterminateData);
// 停止按钮
Button stopButton = new Button(shell, SWT.PUSH);
stopButton.setText("停止不确定进度条");
stopButton.addListener(SWT.Selection, event -> {
indeterminateBar.dispose();
// 重新创建普通进度条
ProgressBar newBar = new ProgressBar(shell, SWT.SMOOTH);
newBar.setMinimum(0);
newBar.setMaximum(100);
GridData newData = new GridData();
newData.horizontalAlignment = GridData.FILL;
newBar.setLayoutData(newData);
shell.layout();
});
shell.setBounds(100, 100, 400, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
7.8 滑块(Slider)与刻度(Scale)
数值选择的可视化
// 滑块
Slider slider = new Slider(shell, SWT.HORIZONTAL);
slider.setMinimum(0);
slider.setMaximum(100);
slider.setSelection(50);
slider.setIncrement(1); // 每次移动 1
slider.setPageIncrement(10); // 每次移动 10(按 PageUp/PageDown)
// 刻度
Scale scale = new Scale(shell, SWT.HORIZONTAL);
scale.setMinimum(0);
scale.setMaximum(100);
scale.setSelection(50);
费曼解释:滑块与刻度的区别
滑块 = 音量旋钮
- 可以精确调节
- 可以上下调节
刻度 = 音量调节杆
- 可以精确调节
- 可以看到刻度
类比:
滑块 = 收音机的音量旋钮
- 旋转调节音量
- 可以很精确
刻度 = 空调的温度调节
- 拉动调节温度
- 可以看到刻度
事件监听与数据绑定
Slider slider = new Slider(shell, SWT.HORIZONTAL);
slider.setMinimum(0);
slider.setMaximum(100);
slider.setSelection(50);
Label valueLabel = new Label(shell, SWT.CENTER);
valueLabel.setText("值:50");
// 监听滑块变化
slider.addListener(SWT.Selection, event -> {
int value = slider.getSelection();
valueLabel.setText("值:" + value);
});
费曼解释:数据绑定的含义
数据绑定 = 连线
- 滑块 = 控制器
- 标签 = 显示器
- 连线 = 事件监听
- 控制器变化,显示器跟着变化
类比:
数据绑定 = 水龙头和水表
- 打开水龙头,水表转动
- 关闭水龙头,水表停止
- 水龙头的开度,决定水表的读数
代码示例:滑块与刻度
public class SliderScaleExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("Slider & Scale 示例");
shell.setLayout(new GridLayout(1, false));
// 滑块示例
Group sliderGroup = new Group(shell, SWT.NONE);
sliderGroup.setText("滑块(Slider)");
sliderGroup.setLayout(new GridLayout(2, false));
GridData sliderGroupData = new GridData();
sliderGroupData.horizontalAlignment = GridData.FILL;
sliderGroupData.grabExcessHorizontalSpace = true;
sliderGroup.setLayoutData(sliderGroupData);
Label sliderLabel = new Label(sliderGroup, SWT.NONE);
sliderLabel.setText("音量:");
Slider slider = new Slider(sliderGroup, SWT.HORIZONTAL);
slider.setMinimum(0);
slider.setMaximum(100);
slider.setSelection(50);
slider.setIncrement(1);
slider.setPageIncrement(10);
GridData sliderData = new GridData();
sliderData.horizontalAlignment = GridData.FILL;
sliderData.grabExcessHorizontalSpace = true;
slider.setLayoutData(sliderData);
Label sliderValue = new Label(sliderGroup, SWT.CENTER);
GridData sliderValueData = new GridData();
sliderValueData.horizontalSpan = 2;
sliderValueData.horizontalAlignment = GridData.FILL;
sliderValue.setLayoutData(sliderValueData);
slider.addListener(SWT.Selection, event -> {
int value = slider.getSelection();
sliderValue.setText("音量:" + value + "%");
});
sliderValue.setText("音量:" + slider.getSelection() + "%");
// 刻度示例
Group scaleGroup = new Group(shell, SWT.NONE);
scaleGroup.setText("刻度(Scale)");
scaleGroup.setLayout(new GridLayout(2, false));
GridData scaleGroupData = new GridData();
scaleGroupData.horizontalAlignment = GridData.FILL;
scaleGroupData.grabExcessHorizontalSpace = true;
scaleGroup.setLayoutData(scaleGroupData);
Label scaleLabel = new Label(scaleGroup, SWT.NONE);
scaleLabel.setText("亮度:");
Scale scale = new Scale(scaleGroup, SWT.HORIZONTAL);
scale.setMinimum(0);
scale.setMaximum(100);
scale.setSelection(70);
GridData scaleData = new GridData();
scaleData.horizontalAlignment = GridData.FILL;
scaleData.grabExcessHorizontalSpace = true;
scale.setLayoutData(scaleData);
Label scaleValue = new Label(scaleGroup, SWT.CENTER);
GridData scaleValueData = new GridData();
scaleValueData.horizontalSpan = 2;
scaleValueData.horizontalAlignment = GridData.FILL;
scaleValue.setLayoutData(scaleValueData);
scale.addListener(SWT.Selection, event -> {
int value = scale.getSelection();
scaleValue.setText("亮度:" + value + "%");
});
scaleValue.setText("亮度:" + scale.getSelection() + "%");
shell.setBounds(100, 100, 400, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
7.9 本章小结
高级控件总结
| 控件 | 用途 | 类比 |
|---|---|---|
| Table | 表格数据 | 电子表格 |
| Tree | 层级数据 | 家族树、文件树 |
| ScrollBar | 滚动浏览 | 电梯 |
| Menu | 菜单操作 | 餐厅菜单 |
| ToolBar | 工具栏 | 工具箱 |
| TabFolder | 多标签页 | 书签 |
| ProgressBar | 进度显示 | 加载条 |
| Slider | 数值选择 | 音量旋钮 |
| Scale | 刻度选择 | 温度调节 |
费曼测试:你能解释清楚吗?
- 普通表格和虚拟表格的区别?
- 普通:一次性加载所有数据,内存占用大 - 虚拟:按需加载数据,内存占用小
- 树(Tree)的递归创建思想?
- 俄罗斯套娃:一个套一个,层层嵌套
- 菜单的级联是如何实现的?
- 菜单项的菜单属性(setMenu)
- 普通进度条和不确定进度条的区别?
- 普通:知道总进度和当前进度,显示百分比 - 不确定:不知道总进度和当前进度,只显示动画
下一章预告
现在你已经掌握了高级控件,
可以创建丰富的交互界面,
但还缺少与用户沟通的"桥梁"。
下一章,我们将学习对话框与消息框,
让应用与用户友好对话,
提升用户体验。
练习题:
- 创建一个表格,包含姓名、年龄、邮箱列,支持排序和删除。
- 创建一个树,模拟文件系统,支持展开/折叠。
- 创建一个多标签页编辑器,每个标签页包含一个文本框。
(提示:表格排序使用 Arrays.sort(),树递归创建使用 createTreeItem(parent, text) 辅助方法)