第13章:SWT 与操作系统的深度集成

第13章:SWT 与操作系统的深度集成


作者:步子哥 (steper@foxmail.com)


13.1 调用系统浏览器

Program.launch()

// 在默认浏览器中打开 URL
try {
    Program.launch("https://www.eclipse.org");
} catch (Exception e) {
    e.printStackTrace();
}

// 在默认程序中打开文件
try {
    Program.launch("C:\\Users\\User\\Documents\\test.txt");
} catch (Exception e) {
    e.printStackTrace();
}

费曼解释:Program.launch() 的原理

Program.launch() = 电话转接
- 你(应用)拨打 10086(调用 launch)
- 话务员(SWT)帮你转接到浏览器
- 浏览器(系统默认程序)接听并执行

类比:
Program.launch() = 快递配送
- 你(应用)把包裹(URL/文件)给快递员(launch)
- 快递员把包裹送到收件人(系统默认程序)
- 收件人签收并处理(打开 URL/文件)

检测程序是否存在

// 检测程序是否可用
Program browserProgram = Program.findProgram("html");
if (browserProgram != null) {
    System.out.println("浏览器程序:" + browserProgram.getName());
    System.out.println("浏览器图标:" + browserProgram.getImageData());
} else {
    System.out.println("未找到浏览器程序");
}

// 获取所有可用程序
Program[] programs = Program.getPrograms();
for (Program program : programs) {
    System.out.println("程序:" + program.getName() + ",扩展名:" + Arrays.toString(program.getExtensions()));
}

费曼解释:findProgram() 的作用

findProgram() = 查找联系人
- 你想给浏览器打电话(调用 launch)
- 但你不知道浏览器的电话号码
- 你查通讯录(findProgram)
- 找到浏览器的电话号码(返回 Program 对象)

类比:
findProgram() = 查找快递员
- 你想寄快递(调用 launch)
- 但你不知道哪个快递员负责这个区域
- 你查快递员表(findProgram)
- 找到负责这个区域的快递员(返回 Program 对象)

代码示例:调用系统浏览器

public class SystemBrowserExample {
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("调用系统浏览器");
        shell.setLayout(new GridLayout(2, false));
        
        // URL 输入框
        Label urlLabel = new Label(shell, SWT.NONE);
        urlLabel.setText("URL:");
        
        Text urlText = new Text(shell, SWT.BORDER);
        urlText.setText("https://www.eclipse.org");
        GridData urlData = new GridData();
        urlData.horizontalAlignment = GridData.FILL;
        urlData.grabExcessHorizontalSpace = true;
        urlText.setLayoutData(urlData);
        
        // 文件路径输入框
        Label filePathLabel = new Label(shell, SWT.NONE);
        filePathLabel.setText("文件路径:");
        
        Text filePathText = new Text(shell, SWT.BORDER);
        filePathText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        
        // 按钮面板
        Composite buttonPanel = new Composite(shell, SWT.NONE);
        buttonPanel.setLayout(new FillLayout(SWT.HORIZONTAL));
        GridData buttonData = new GridData();
        buttonData.horizontalSpan = 2;
        buttonData.horizontalAlignment = GridData.FILL;
        buttonPanel.setLayoutData(buttonData);
        
        // 打开 URL 按钮
        Button openUrlButton = new Button(buttonPanel, SWT.PUSH);
        openUrlButton.setText("打开 URL");
        openUrlButton.addListener(SWT.Selection, event -> {
            String url = urlText.getText().trim();
            if (url.isEmpty()) {
                MessageBox messageBox = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
                messageBox.setText("警告");
                messageBox.setMessage("请输入 URL!");
                messageBox.open();
                return;
            }
            
            try {
                Program.launch(url);
                System.out.println("已打开 URL:" + url);
            } catch (Exception e) {
                MessageBox messageBox = new MessageBox(shell, SWT.ICON_ERROR | SWT.OK);
                messageBox.setText("错误");
                messageBox.setMessage("无法打开 URL:" + e.getMessage());
                messageBox.open();
            }
        });
        
        // 打开文件按钮
        Button openFileButton = new Button(buttonPanel, SWT.PUSH);
        openFileButton.setText("打开文件");
        openFileButton.addListener(SWT.Selection, event -> {
            FileDialog fileDialog = new FileDialog(shell, SWT.OPEN);
            fileDialog.setText("选择文件");
            String filePath = fileDialog.open();
            
            if (filePath != null) {
                filePathText.setText(filePath);
                
                try {
                    Program.launch(filePath);
                    System.out.println("已打开文件:" + filePath);
                } catch (Exception e) {
                    MessageBox messageBox = new MessageBox(shell, SWT.ICON_ERROR | SWT.OK);
                    messageBox.setText("错误");
                    messageBox.setMessage("无法打开文件:" + e.getMessage());
                    messageBox.open();
                }
            }
        });
        
        // 查找浏览器按钮
        Button findBrowserButton = new Button(buttonPanel, SWT.PUSH);
        findBrowserButton.setText("查找浏览器");
        findBrowserButton.addListener(SWT.Selection, event -> {
            Program browserProgram = Program.findProgram("html");
            if (browserProgram != null) {
                MessageBox messageBox = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
                messageBox.setText("浏览器信息");
                messageBox.setMessage("浏览器程序:" + browserProgram.getName());
                messageBox.open();
            } else {
                MessageBox messageBox = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
                messageBox.setText("警告");
                messageBox.setMessage("未找到浏览器程序");
                messageBox.open();
            }
        });
        
        // 查看所有程序按钮
        Button listProgramsButton = new Button(buttonPanel, SWT.PUSH);
        listProgramsButton.setText("查看所有程序");
        listProgramsButton.addListener(SWT.Selection, event -> {
            Program[] programs = Program.getPrograms();
            StringBuilder sb = new StringBuilder();
            sb.append("可用程序:\n");
            for (Program program : programs) {
                sb.append(program.getName()).append(":");
                sb.append(Arrays.toString(program.getExtensions())).append("\n");
            }
            
            MessageBox messageBox = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
            messageBox.setText("所有程序");
            messageBox.setMessage(sb.toString());
            messageBox.open();
        });
        
        shell.setBounds(100, 100, 500, 200);
        shell.open();
        
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        
        display.dispose();
    }
}

13.2 系统托盘(Tray)

托盘图标

// 创建系统托盘
Tray tray = display.getSystemTray();

// 创建托盘图标
TrayItem trayItem = new TrayItem(tray, SWT.NONE);

// 设置图标
Image trayIcon = new Image(display, "path/to/icon.png");
trayItem.setImage(trayIcon);

// 设置提示文本
trayItem.setToolTipText("我的应用");

// 双击托盘图标显示窗口
trayItem.addSelectionListener(new SelectionAdapter() {
    @Override
    public void widgetSelected(SelectionEvent e) {
        shell.setVisible(true);
        shell.setMinimized(false);
        shell.setActive();
    }
});

费曼解释:系统托盘的原理

系统托盘 = 通知栏
- 你的应用在后台运行
- 托盘图标 = 通知
- 双击图标 = 打开应用

类比:
系统托盘 = 手机通知栏
- App 在后台运行
- 通知栏有 App 的图标
- 点击图标 = 打开 App

悬停提示

// 设置悬停提示文本
trayItem.setToolTipText("我的应用\n双击打开");

费曼解释:悬停提示的作用

悬停提示 = 名字牌
- 你把鼠标悬停在托盘图标上
- 显示名字牌(提示文本)
- 你知道这个图标是什么

类比:
悬停提示 = 快递单
- 你看到快递员
- 看快递单(提示文本)
- 你知道快递员送什么

弹出菜单

// 创建托盘图标
TrayItem trayItem = new TrayItem(tray, SWT.NONE);
trayItem.setImage(trayIcon);

// 创建弹出菜单
Menu menu = new Menu(shell, SWT.POP_UP);

// 显示菜单项
MenuItem showItem = new MenuItem(menu, SWT.PUSH);
showItem.setText("显示窗口");
showItem.addListener(SWT.Selection, event -> {
    shell.setVisible(true);
    shell.setMinimized(false);
    shell.setActive();
});

// 隐藏菜单项
MenuItem hideItem = new MenuItem(menu, SWT.PUSH);
hideItem.setText("隐藏窗口");
hideItem.addListener(SWT.Selection, event -> {
    shell.setVisible(false);
});

// 分隔线
new MenuItem(menu, SWT.SEPARATOR);

// 退出菜单项
MenuItem exitItem = new MenuItem(menu, SWT.PUSH);
exitItem.setText("退出");
exitItem.addListener(SWT.Selection, event -> {
    shell.dispose();
});

// 右键点击托盘图标显示菜单
trayItem.addMenuDetectListener(event -> {
    menu.setVisible(true);
});

费曼解释:托盘菜单的工作原理

托盘菜单 = 快捷菜单
- 右键点击托盘图标
- 显示快捷菜单
- 点击菜单项,执行操作

类比:
托盘菜单 = 手机长按 App 图标
- 长按 App 图标
- 显示快捷菜单
- 点击菜单项,执行操作(添加到主屏幕、卸载等)

代码示例:系统托盘

public class SystemTrayExample {
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("系统托盘示例");
        shell.setLayout(new GridLayout(1, false));
        
        // 检查是否支持系统托盘
        Tray tray = display.getSystemTray();
        if (tray == null) {
            MessageBox messageBox = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
            messageBox.setText("警告");
            messageBox.setMessage("此系统不支持系统托盘");
            messageBox.open();
            shell.dispose();
            display.dispose();
            return;
        }
        
        // 创建托盘图标
        TrayItem trayItem = new TrayItem(tray, SWT.NONE);
        
        // 注意:实际应用中应该从文件加载图标
        // Image trayIcon = new Image(display, "icon.png");
        // trayItem.setImage(trayIcon);
        
        // 设置提示文本
        trayItem.setToolTipText("系统托盘示例\n右键显示菜单\n双击显示窗口");
        
        // 创建弹出菜单
        Menu menu = new Menu(shell, SWT.POP_UP);
        
        MenuItem showItem = new MenuItem(menu, SWT.PUSH);
        showItem.setText("显示窗口");
        showItem.addListener(SWT.Selection, event -> {
            shell.setVisible(true);
            shell.setMinimized(false);
            shell.setActive();
        });
        
        MenuItem hideItem = new MenuItem(menu, SWT.PUSH);
        hideItem.setText("隐藏窗口");
        hideItem.addListener(SWT.Selection, event -> {
            shell.setVisible(false);
        });
        
        new MenuItem(menu, SWT.SEPARATOR);
        
        MenuItem minimizeItem = new MenuItem(menu, SWT.PUSH);
        minimizeItem.setText("最小化到托盘");
        minimizeItem.addListener(SWT.Selection, event -> {
            shell.setMinimized(true);
            shell.setVisible(false);
        });
        
        MenuItem maximizeItem = new MenuItem(menu, SWT.PUSH);
        maximizeItem.setText("最大化窗口");
        maximizeItem.addListener(SWT.Selection, event -> {
            shell.setMaximized(true);
        });
        
        new MenuItem(menu, SWT.SEPARATOR);
        
        MenuItem aboutItem = new MenuItem(menu, SWT.PUSH);
        aboutItem.setText("关于");
        aboutItem.addListener(SWT.Selection, event -> {
            MessageBox messageBox = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
            messageBox.setText("关于");
            messageBox.setMessage("系统托盘示例\n版本 1.0");
            messageBox.open();
        });
        
        MenuItem exitItem = new MenuItem(menu, SWT.PUSH);
        exitItem.setText("退出");
        exitItem.addListener(SWT.Selection, event -> {
            shell.dispose();
        });
        
        // 右键点击托盘图标显示菜单
        trayItem.addMenuDetectListener(event -> {
            menu.setVisible(true);
        });
        
        // 双击托盘图标显示窗口
        trayItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                shell.setVisible(true);
                shell.setMinimized(false);
                shell.setActive();
            }
        });
        
        // 主窗口内容
        Label label = new Label(shell, SWT.WRAP | SWT.CENTER);
        label.setText("这是一个系统托盘示例。\n\n最小化窗口后,托盘图标仍然存在。\n双击托盘图标可以显示窗口。\n右键托盘图标可以显示菜单。");
        GridData labelData = new GridData();
        labelData.horizontalAlignment = GridData.FILL;
        labelData.grabExcessHorizontalSpace = true;
        label.setLayoutData(labelData);
        
        // 按钮:最小化到托盘
        Button minimizeButton = new Button(shell, SWT.PUSH);
        minimizeButton.setText("最小化到托盘");
        minimizeButton.addListener(SWT.Selection, event -> {
            shell.setMinimized(true);
            shell.setVisible(false);
        });
        
        // 按钮:隐藏到托盘
        Button hideButton = new Button(shell, SWT.PUSH);
        hideButton.setText("隐藏到托盘");
        hideButton.addListener(SWT.Selection, event -> {
            shell.setVisible(false);
        });
        
        shell.setBounds(100, 100, 400, 200);
        shell.open();
        
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        
        // 释放托盘图标
        trayItem.dispose();
        // trayIcon.dispose();
        display.dispose();
    }
}

13.3 获取系统信息

屏幕分辨率

// 获取所有显示器
Monitor[] monitors = display.getMonitors();
System.out.println("显示器数量:" + monitors.length);

for (Monitor monitor : monitors) {
    Rectangle bounds = monitor.getBounds();
    System.out.println("显示器边界:" + bounds);
    System.out.println("宽度:" + bounds.width + ",高度:" + bounds.height);
    
    Rectangle clientArea = monitor.getClientArea();
    System.out.println("可用区域:" + clientArea);
    System.out.println("可用宽度:" + clientArea.width + ",可用高度:" + clientArea.height);
}

费曼解释:显示器边界 vs 可用区域

显示器边界 = 电视边框
- 边框包括边框(显示器边界)
- 边框不包括任务栏(可用区域)

类比:
显示器边界 = 纸张大小
- 纸张大小:A4 纸(显示器边界)
- 可用大小:去掉页眉页脚(可用区域)

可用工作区

// 获取主显示器
Monitor primaryMonitor = display.getPrimaryMonitor();
Rectangle clientArea = primaryMonitor.getClientArea();

System.out.println("主显示器可用区域:" + clientArea);
System.out.println("宽度:" + clientArea.width + ",高度:" + clientArea.height);

// 计算窗口居中位置
int shellWidth = 400;
int shellHeight = 300;
int x = clientArea.x + (clientArea.width - shellWidth) / 2;
int y = clientArea.y + (clientArea.height - shellHeight) / 2;

shell.setBounds(x, y, shellWidth, shellHeight);

费曼解释:窗口居中的计算

窗口居中 = 放相册
- 墙壁(显示器)
- 相册(窗口)
- 相册居中:相册左边 = 墙壁左边 + (墙壁宽度 - 相册宽度) / 2

类比:
窗口居中 = 放海报
- 墙壁(显示器)
- 海报(窗口)
- 海报居中:海报左边 = 墙壁左边 + (墙壁宽度 - 海报宽度) / 2

系统字体

// 获取系统字体列表
FontData[] fontDatas = display.getFontList(null, true);
System.out.println("系统字体数量:" + fontDatas.length);

// 显示前 10 个字体
for (int i = 0; i < Math.min(10, fontDatas.length); i++) {
    FontData fontData = fontDatas[i];
    System.out.println("字体:" + fontData.getName() + ",大小:" + fontData.getHeight());
}

// 获取特定字体
FontData[] arialFonts = display.getFontList("Arial", true);
if (arialFonts.length > 0) {
    System.out.println("Arial 字体数量:" + arialFonts.length);
}

费曼解释:系统字体的含义

系统字体 = 字库
- 系统安装了很多字体(字库)
- 你可以从字库里选择字体

类比:
系统字体 = 笔袋
- 笔袋里有很多笔(字体)
- 你可以从笔袋里选择笔(字体)

系统颜色

// 获取系统颜色
Color systemColor = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
System.out.println("系统背景色:" + systemColor.getRGB());

// 获取常用的系统颜色
Color[] colors = {
    display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND),
    display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND),
    display.getSystemColor(SWT.COLOR_WIDGET_BORDER),
    display.getSystemColor(SWT.COLOR_LIST_BACKGROUND),
    display.getSystemColor(SWT.COLOR_LIST_FOREGROUND),
    display.getSystemColor(SWT.COLOR_LIST_SELECTION),
    display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT),
    display.getSystemColor(SWT.COLOR_INFO_BACKGROUND),
    display.getSystemColor(SWT.COLOR_INFO_FOREGROUND),
    display.getSystemColor(SWT.COLOR_TITLE_BACKGROUND),
    display.getSystemColor(SWT.COLOR_TITLE_FOREGROUND),
    display.getSystemColor(SWT.COLOR_TITLE_BACKGROUND_GRADIENT),
    display.getSystemColor(SWT.COLOR_TITLE_FOREGROUND_GRADIENT),
    display.getSystemColor(SWT.COLOR_WHITE),
    display.getSystemColor(SWT.COLOR_BLACK),
    display.getSystemColor(SWT.COLOR_RED),
    display.getSystemColor(SWT.COLOR_DARK_RED),
    display.getSystemColor(SWT.COLOR_GREEN),
    display.getSystemColor(SWT.COLOR_DARK_GREEN),
    display.getSystemColor(SWT.COLOR_YELLOW),
    display.getSystemColor(SWT.COLOR_DARK_YELLOW),
    display.getSystemColor(SWT.COLOR_BLUE),
    display.getSystemColor(SWT.COLOR_DARK_BLUE),
    display.getSystemColor(SWT.COLOR_MAGENTA),
    display.getSystemColor(SWT.COLOR_CYAN),
    display.getSystemColor(SWT.COLOR_GRAY)
};

// 显示所有颜色
for (int i = 0; i < colors.length; i++) {
    System.out.println("颜色 " + i + ":" + colors[i].getRGB());
}

费曼解释:系统颜色的用途

系统颜色 = 系统调色板
- 系统预定义了很多颜色(调色板)
- 你可以从调色板选择颜色
- 使用系统颜色,应用风格与系统一致

类比:
系统颜色 = 餐厅调料
- 餐厅准备了很多调料(系统颜色)
- 你可以从调料里选择(使用系统颜色)
- 使用餐厅调料,味道与餐厅一致

代码示例:获取系统信息

public class SystemInfoExample {
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("系统信息示例");
        shell.setLayout(new GridLayout(1, false));
        
        // 文本框:显示系统信息
        Text infoText = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
        GridData textData = new GridData();
        textData.horizontalAlignment = GridData.FILL;
        textData.verticalAlignment = GridData.FILL;
        textData.grabExcessHorizontalSpace = true;
        textData.grabExcessVerticalSpace = true;
        textData.heightHint = 300;
        infoText.setLayoutData(textData);
        
        // 按钮:获取显示器信息
        Button monitorButton = new Button(shell, SWT.PUSH);
        monitorButton.setText("获取显示器信息");
        monitorButton.addListener(SWT.Selection, event -> {
            StringBuilder sb = new StringBuilder();
            sb.append("显示器信息:\n\n");
            
            Monitor[] monitors = display.getMonitors();
            sb.append("显示器数量:").append(monitors.length).append("\n\n");
            
            for (int i = 0; i < monitors.length; i++) {
                Monitor monitor = monitors[i];
                Rectangle bounds = monitor.getBounds();
                Rectangle clientArea = monitor.getClientArea();
                
                sb.append("显示器 ").append(i + 1).append(":\n");
                sb.append("  边界:").append(bounds).append("\n");
                sb.append("  可用区域:").append(clientArea).append("\n");
                sb.append("  是否是主显示器:").append(monitor.equals(display.getPrimaryMonitor())).append("\n\n");
            }
            
            infoText.setText(sb.toString());
        });
        
        // 按钮:获取系统字体
        Button fontButton = new Button(shell, SWT.PUSH);
        fontButton.setText("获取系统字体");
        fontButton.addListener(SWT.Selection, event -> {
            StringBuilder sb = new StringBuilder();
            sb.append("系统字体:\n\n");
            
            FontData[] fontDatas = display.getFontList(null, true);
            sb.append("字体总数:").append(fontDatas.length).append("\n\n");
            
            sb.append("前 20 个字体:\n");
            for (int i = 0; i < Math.min(20, fontDatas.length); i++) {
                FontData fontData = fontDatas[i];
                sb.append(i + 1).append(". ")
                  .append(fontData.getName())
                  .append(" (大小: ").append(fontData.getHeight()).append(")\n");
            }
            
            infoText.setText(sb.toString());
        });
        
        // 按钮:获取系统颜色
        Button colorButton = new Button(shell, SWT.PUSH);
        colorButton.setText("获取系统颜色");
        colorButton.addListener(SWT.Selection, event -> {
            StringBuilder sb = new StringBuilder();
            sb.append("系统颜色:\n\n");
            
            Color[] colors = {
                display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND),
                display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND),
                display.getSystemColor(SWT.COLOR_WIDGET_BORDER),
                display.getSystemColor(SWT.COLOR_LIST_BACKGROUND),
                display.getSystemColor(SWT.COLOR_LIST_FOREGROUND),
                display.getSystemColor(SWT.COLOR_LIST_SELECTION),
                display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT),
                display.getSystemColor(SWT.COLOR_INFO_BACKGROUND),
                display.getSystemColor(SWT.COLOR_INFO_FOREGROUND),
                display.getSystemColor(SWT.COLOR_TITLE_BACKGROUND),
                display.getSystemColor(SWT.COLOR_TITLE_FOREGROUND),
                display.getSystemColor(SWT.COLOR_TITLE_BACKGROUND_GRADIENT),
                display.getSystemColor(SWT.COLOR_TITLE_FOREGROUND_GRADIENT),
                display.getSystemColor(SWT.COLOR_WHITE),
                display.getSystemColor(SWT.COLOR_BLACK),
                display.getSystemColor(SWT.COLOR_RED),
                display.getSystemColor(SWT.COLOR_DARK_RED),
                display.getSystemColor(SWT.COLOR_GREEN),
                display.getSystemColor(SWT.COLOR_DARK_GREEN),
                display.getSystemColor(SWT.COLOR_YELLOW),
                display.getSystemColor(SWT.COLOR_DARK_YELLOW),
                display.getSystemColor(SWT.COLOR_BLUE),
                display.getSystemColor(SWT.COLOR_DARK_BLUE),
                display.getSystemColor(SWT.COLOR_MAGENTA),
                display.getSystemColor(SWT.COLOR_CYAN),
                display.getSystemColor(SWT.COLOR_GRAY)
            };
            
            String[] colorNames = {
                "WIDGET_BACKGROUND", "WIDGET_FOREGROUND", "WIDGET_BORDER",
                "LIST_BACKGROUND", "LIST_FOREGROUND", "LIST_SELECTION", "LIST_SELECTION_TEXT",
                "INFO_BACKGROUND", "INFO_FOREGROUND",
                "TITLE_BACKGROUND", "TITLE_FOREGROUND", "TITLE_BACKGROUND_GRADIENT", "TITLE_FOREGROUND_GRADIENT",
                "WHITE", "BLACK", "RED", "DARK_RED", "GREEN", "DARK_GREEN",
                "YELLOW", "DARK_YELLOW", "BLUE", "DARK_BLUE", "MAGENTA", "CYAN", "GRAY"
            };
            
            for (int i = 0; i < colors.length; i++) {
                RGB rgb = colors[i].getRGB();
                sb.append(colorNames[i]).append(":")
                  .append("R=").append(rgb.red)
                  .append(", G=").append(rgb.green)
                  .append(", B=").append(rgb.blue)
                  .append("\n");
            }
            
            infoText.setText(sb.toString());
        });
        
        shell.setBounds(100, 100, 600, 400);
        shell.open();
        
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        
        display.dispose();
    }
}

13.4 平台特定的注意事项

Windows 的特殊处理

// 检测是否是 Windows
boolean isWindows = Platform.getWS() == Platform.WS_WIN32;

if (isWindows) {
    System.out.println("运行在 Windows 平台");
    
    // Windows 特定的处理
    System.setProperty("sun.java2d.noddraw", "true");  // 禁用 DDraw,提高性能
    
    // 设置窗口透明度(仅 Windows 10+)
    if (Platform.isWindows10OrLater()) {
        shell.setAlpha(200);  // 0-255,255 = 不透明
    }
}

费曼解释:平台特定的必要性

平台特定 = 个性化服务
- Windows 有 Windows 的特点
- Mac 有 Mac 的特点
- Linux 有 Linux 的特点
- 针对不同平台,提供不同的服务

类比:
平台特定 = 客户定制
- 客户 A(Windows)喜欢红色
- 客户 B(Mac)喜欢蓝色
- 客户 C(Linux)喜欢绿色
- 针对不同客户,提供不同的颜色

Mac 的菜单栏规则

// 检测是否是 Mac
boolean isMac = Platform.getWS() == Platform.WS_COCOA;

if (isMac) {
    System.out.println("运行在 Mac 平台");
    
    // Mac 特定的处理
    System.setProperty("apple.laf.useScreenMenuBar", "true");  // 菜单栏在屏幕顶部
    
    // 设置应用名称(会在 Dock 显示)
    System.setProperty("com.apple.mrj.application.apple.menu.about.name", "我的应用");
    
    // 设置 Dock 图标(注意:需要从文件加载)
    // Image dockIcon = new Image(display, "icon.png");
    // shell.setImage(dockIcon);
    
    // Mac 的 Cmd+Q 退出快捷键
    shell.addListener(SWT.Close, event -> {
        // Mac 点击窗口关闭按钮,不是退出应用
        shell.setVisible(false);
    });
}

费曼解释:Mac 菜单栏的规则

Mac 菜单栏 = 屋顶大招牌
- Mac 的菜单栏在屏幕顶部
- 所有应用共享菜单栏
- 菜单栏显示当前应用的菜单

类比:
Mac 菜单栏 = 商场导购图
- 所有店铺共享导购图(菜单栏)
- 当前店铺显示在导购图上(当前应用的菜单)
- 切换店铺,导购图切换(切换应用,菜单栏切换)

Linux 的主题适配

// 检测是否是 Linux
boolean isLinux = Platform.getWS() == Platform.WS_GTK;

if (isLinux) {
    System.out.println("运行在 Linux 平台");
    
    // Linux 特定的处理
    // SWT 会自动使用 GTK 主题
    
    // 检测桌面环境
    String desktop = System.getenv("XDG_CURRENT_DESKTOP");
    if (desktop != null) {
        System.out.println("桌面环境:" + desktop);
        
        if (desktop.contains("GNOME")) {
            System.out.println("使用 GNOME 主题");
        } else if (desktop.contains("KDE")) {
            System.out.println("使用 KDE 主题");
        } else if (desktop.contains("XFCE")) {
            System.out.println("使用 XFCE 主题");
        }
    }
}

费曼解释:Linux 主题的适配

Linux 主题 = 装修风格
- GNOME 有 GNOME 的装修风格
- KDE 有 KDE 的装修风格
- XFCE 有 XFCE 的装修风格
- SWT 自动使用桌面的装修风格

类比:
Linux 主题 = 家具风格
- 现代风格(GNOME 主题)
- 古典风格(KDE 主题)
- 简约风格(XFCE 主题)
- 应用自动使用桌面的家具风格

代码示例:平台特定处理

public class PlatformSpecificExample {
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("平台特定处理");
        shell.setLayout(new GridLayout(1, false));
        
        // 平台特定处理
        String platform = "";
        if (Platform.getWS() == Platform.WS_WIN32) {
            platform = "Windows";
            System.setProperty("sun.java2d.noddraw", "true");
        } else if (Platform.getWS() == Platform.WS_COCOA) {
            platform = "Mac";
            System.setProperty("apple.laf.useScreenMenuBar", "true");
            System.setProperty("com.apple.mrj.application.apple.menu.about.name", "平台特定示例");
        } else if (Platform.getWS() == Platform.WS_GTK) {
            platform = "Linux";
        } else {
            platform = "未知";
        }
        
        // 显示平台信息
        Label platformLabel = new Label(shell, SWT.WRAP | SWT.CENTER);
        platformLabel.setText("当前平台:" + platform + "\nSWT 会自动适配平台的特性。");
        GridData labelData = new GridData();
        labelData.horizontalAlignment = GridData.FILL;
        labelData.grabExcessHorizontalSpace = true;
        platformLabel.setLayoutData(labelData);
        
        // 按钮面板
        Composite buttonPanel = new Composite(shell, SWT.NONE);
        buttonPanel.setLayout(new FillLayout(SWT.HORIZONTAL));
        
        // 显示平台信息按钮
        Button showButton = new Button(buttonPanel, SWT.PUSH);
        showButton.setText("显示平台信息");
        showButton.addListener(SWT.Selection, event -> {
            StringBuilder sb = new StringBuilder();
            sb.append("平台信息:\n\n");
            sb.append("WS:").append(Platform.getWS()).append("\n");
            sb.append("Java 版本:").append(System.getProperty("java.version")).append("\n");
            sb.append("OS 名称:").append(System.getProperty("os.name")).append("\n");
            sb.append("OS 版本:").append(System.getProperty("os.version")).append("\n");
            sb.append("OS 架构:").append(System.getProperty("os.arch")).append("\n");
            
            if (platform.equals("Linux")) {
                String desktop = System.getenv("XDG_CURRENT_DESKTOP");
                sb.append("桌面环境:").append(desktop != null ? desktop : "未知").append("\n");
            }
            
            MessageBox messageBox = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
            messageBox.setText("平台信息");
            messageBox.setMessage(sb.toString());
            messageBox.open();
        });
        
        // 显示系统颜色按钮
        Button colorButton = new Button(buttonPanel, SWT.PUSH);
        colorButton.setText("显示系统颜色");
        colorButton.addListener(SWT.Selection, event -> {
            Color backgroundColor = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
            Color foregroundColor = display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND);
            Color borderColor = display.getSystemColor(SWT.COLOR_WIDGET_BORDER);
            
            shell.setBackground(backgroundColor);
            platformLabel.setForeground(foregroundColor);
            
            MessageBox messageBox = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
            messageBox.setText("系统颜色");
            messageBox.setMessage("背景色:" + backgroundColor.getRGB() + "\n前景色:" + foregroundColor.getRGB() + "\n边框色:" + borderColor.getRGB());
            messageBox.open();
        });
        
        // 显示系统字体按钮
        Button fontButton = new Button(buttonPanel, SWT.PUSH);
        fontButton.setText("显示系统字体");
        fontButton.addListener(SWT.Selection, event -> {
            FontData[] fontDatas = display.getFontList(null, true);
            
            StringBuilder sb = new StringBuilder();
            sb.append("系统字体总数:").append(fontDatas.length).append("\n\n");
            sb.append("前 10 个字体:\n");
            for (int i = 0; i < Math.min(10, fontDatas.length); i++) {
                FontData fontData = fontDatas[i];
                sb.append(i + 1).append(". ").append(fontData.getName()).append("\n");
            }
            
            MessageBox messageBox = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
            messageBox.setText("系统字体");
            messageBox.setMessage(sb.toString());
            messageBox.open();
        });
        
        shell.setBounds(100, 100, 500, 200);
        shell.open();
        
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        
        display.dispose();
    }
}

13.5 本章小结

系统集成总结

功能作用类比
Program.launch()调用系统默认程序快递配送
TrayItem系统托盘图标通知栏
Monitor获取显示器信息电视边框
FontData获取系统字体字库
系统颜色获取系统颜色系统调色板

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

  1. Program.launch() 的作用?

- 调用系统默认程序打开 URL 或文件

  1. 系统托盘的用途?

- 在后台运行应用,托盘图标显示通知

  1. 如何获取主显示器的可用区域?

- display.getPrimaryMonitor().getClientArea()

  1. 为什么需要平台特定的处理?

- 不同平台有不同的特性和规则

下一章预告

现在你已经掌握了 SWT 与操作系统的深度集成,
可以调用系统浏览器、系统托盘等功能,
让应用更好地利用系统特性。

下一章,我们将学习国际化(i18n),
让应用支持多种语言,
服务全球用户。

练习题:

  1. 创建一个应用,包含一个文本框和一个按钮,点击按钮在默认浏览器中打开文本框中的 URL。
  2. 创建一个应用,包含系统托盘图标,双击显示窗口,右键显示菜单(显示/隐藏/退出)。
  3. 创建一个应用,获取并显示所有显示器的信息(边界、可用区域)。

(提示:使用 Program.launch() 打开 URL,使用 TrayItem 创建托盘图标,使用 display.getMonitors() 获取显示器)

← 返回目录