第7章:高级控件 - 丰富的交互体验

第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刻度选择温度调节

费曼测试:你能解释清楚吗?

  1. 普通表格和虚拟表格的区别?

- 普通:一次性加载所有数据,内存占用大 - 虚拟:按需加载数据,内存占用小

  1. 树(Tree)的递归创建思想?

- 俄罗斯套娃:一个套一个,层层嵌套

  1. 菜单的级联是如何实现的?

- 菜单项的菜单属性(setMenu)

  1. 普通进度条和不确定进度条的区别?

- 普通:知道总进度和当前进度,显示百分比 - 不确定:不知道总进度和当前进度,只显示动画

下一章预告

现在你已经掌握了高级控件,
可以创建丰富的交互界面,
但还缺少与用户沟通的"桥梁"。

下一章,我们将学习对话框与消息框,
让应用与用户友好对话,
提升用户体验。

练习题:

  1. 创建一个表格,包含姓名、年龄、邮箱列,支持排序和删除。
  2. 创建一个树,模拟文件系统,支持展开/折叠。
  3. 创建一个多标签页编辑器,每个标签页包含一个文本框。

(提示:表格排序使用 Arrays.sort(),树递归创建使用 createTreeItem(parent, text) 辅助方法)

← 返回目录