第2章:环境搭建
作者:步子哥 (steper@foxmail.com)
2.1 最简单的 Hello SWT
Maven 依赖配置
第一步:创建 Maven 项目
mkdir hello-swt
cd hello-swt
mvn archetype:generate -DgroupId=com.example -DartifactId=hello-swt -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
cd hello-swt
第二步:修改 pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>hello-swt</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- SWT 核心库 -->
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.swt</artifactId>
<version>3.124.200</version>
</dependency>
</dependencies>
<!-- 根据你的操作系统选择对应的原生库 -->
<profiles>
<!-- Windows -->
<profile>
<id>windows</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<dependencies>
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.swt.win32.win32.x86_64</artifactId>
<version>3.124.200</version>
</dependency>
</dependencies>
</profile>
<!-- Mac -->
<profile>
<id>mac</id>
<activation>
<os>
<family>mac</family>
</os>
</activation>
<dependencies>
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.swt.cocoa.macosx.x86_64</artifactId>
<version>3.124.200</version>
</dependency>
</dependencies>
</profile>
<!-- Linux -->
<profile>
<id>linux</id>
<activation>
<os>
<family>linux</family>
</os>
</activation>
<dependencies>
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.swt.gtk.linux.x86_64</artifactId>
<version>3.124.200</version>
</dependency>
</dependencies>
</profile>
</profiles>
</project>
费曼解释:为什么需要平台特定的依赖?
SWT 就像一套语言包:
- org.eclipse.swt 是"语法书"(通用 API)
- org.eclipse.swt.win32.win32.x86_64 是"Windows 词典"
- org.eclipse.swt.cocoa.macosx.x86_64 是"Mac 词典"
- org.eclipse.swt.gtk.linux.x86_64 是"Linux 词典"
你有了语法书,还需要对应平台的词典,
才能说出当地语言(调用本地 API)。
下载依赖:
mvn clean install
Gradle 依赖配置
第一步:创建 Gradle 项目
mkdir hello-swt
cd hello-swt
gradle init --type java-application
第二步:修改 build.gradle
plugins {
id 'java'
id 'application'
}
java {
sourceCompatibility = 11
targetCompatibility = 11
}
repositories {
mavenCentral()
}
dependencies {
// SWT 核心库
implementation 'org.eclipse.platform:org.eclipse.swt:3.124.200'
// 根据操作系统选择原生库
if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
implementation 'org.eclipse.platform:org.eclipse.swt.win32.win32.x86_64:3.124.200'
} else if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) {
implementation 'org.eclipse.platform:org.eclipse.swt.cocoa.macosx.x86_64:3.124.200'
} else {
implementation 'org.eclipse.platform:org.eclipse.swt.gtk.linux.x86_64:3.124.200'
}
}
application {
mainClass = 'com.example.App'
}
下载依赖:
gradle build
手动添加 jar 包
适用于:不想用 Maven/Gradle,或者网络受限的情况
步骤:
- 下载 SWT jar 包
- 访问:https://download.eclipse.org/eclipse/downloads/
- 选择最新版本
- 下载 SWT Binary
- 解压后得到:
``
swt.jar // 核心库
swt-win32.dll // Windows 原生库
swt-cocoa.dylib // Mac 原生库
libswt-gtk-3.so // Linux 原生库
``
- 创建项目结构
``
hello-swt/
├── src/
│ └── main/
│ └── java/
│ └── com/
│ └── example/
│ └── App.java
└── lib/
├── swt.jar
├── swt-win32.dll
├── swt-cocoa.dylib
└── libswt-gtk-3.so
``
- 设置 CLASSPATH
```bash # Windows set CLASSPATH=lib\swt.jar;.
# Mac/Linux export CLASSPATH=lib/swt.jar:. ```
- 设置 java.library.path
```bash # Windows java -Djava.library.path=lib App
# Mac/Linux java -Djava.library.path=lib App ```
费曼解释:什么是 java.library.path?
java.library.path 就像"搜索路径":
- JVM 运行 Java 代码(.class 文件)
- SWT 需要加载原生库(.dll, .so, .dylib)
- JVM 在 java.library.path 指定的目录中查找原生库
类比:
- CLASSPATH:告诉 JVM 在哪里找 .class 文件
- java.library.path:告诉 JVM 在哪里找 .dll 文件
2.2 第一个 SWT 程序
Hello World 程序
package com.example;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
public class HelloWorld {
public static void main(String[] args) {
// 1. 创建 Display(连接操作系统的桥梁)
Display display = new Display();
// 2. 创建 Shell(窗口)
Shell shell = new Shell(display);
shell.setText("Hello SWT"); // 设置窗口标题
shell.setSize(300, 200); // 设置窗口大小
// 3. 创建 Label(标签)
Label label = new Label(shell, SWT.NONE);
label.setText("Hello, SWT!");
label.setBounds(100, 80, 100, 30); // 设置位置和大小
// 4. 打开窗口
shell.open();
// 5. 事件循环(让程序持续运行)
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
// 6. 释放资源
display.dispose();
}
}
运行程序:
# Maven
mvn exec:java -Dexec.mainClass="com.example.HelloWorld"
# Gradle
gradle run
# 手动编译
javac -cp lib/swt.jar src/main/java/com/example/HelloWorld.java
java -cp lib/swt.jar;. -Djava.library.path=lib com.example.HelloWorld
代码详细解释
1. 创建 Display
Display display = new Display();
费曼解释:Display 是什么?
Display 就像"电话机":
- 你的 Java 代码在一个房间(Java 虚拟机)
- 操作系统在另一个房间(操作系统)
- Display 是连接两个房间的电话线
没有 Display:
- 你的代码无法联系操作系统
- 就像没有电话,无法联系外界
有了 Display:
- 你可以"拨号"(调用 Display 的方法)
- 操作系统会"接听"(处理你的请求)
- 例如:创建窗口、显示控件、处理事件
类比:
Display 的作用 = 浏览器的网络连接
- 没有 Display = 离线模式,什么都做不了
- 有了 Display = 在线模式,可以访问操作系统资源
Display 的生命周期:
创建 Display → 连接操作系统
使用 Display → 创建窗口、控件
销毁 Display → 断开连接
2. 创建 Shell
Shell shell = new Shell(display);
shell.setText("Hello SWT"); // 设置窗口标题
shell.setSize(300, 200); // 设置窗口大小(宽x高)
费曼解释:Shell 是什么?
Shell 就像"相框":
- 你可以在相框里放照片(控件)
- 相框本身可以摆放在不同位置
- 相框可以关闭、最小化、最大化
类比:
- Display 是桌子
- Shell 是桌子上的相框
- Label 是相框里的照片
没有 Shell:
- 控件(照片)无处摆放
- Display(桌子)是空的
有了 Shell:
- 控件(照片)有家了
- 用户看到的是一个窗口
Shell 的属性:
setText(): 设置窗口标题setSize(): 设置窗口大小(宽 × 高)setLocation(): 设置窗口位置(x, y)setBounds(): 同时设置位置和大小
3. 创建 Label
Label label = new Label(shell, SWT.NONE);
label.setText("Hello, SWT!");
label.setBounds(100, 80, 100, 30);
费曼解释:Label 是什么?
Label 就像"便利贴":
- 你可以写字在上面
- 粘在相框(Shell)的某个位置
- 用户可以看到,但不能修改
类比:
- Label = 便利贴(只读文本)
- Button = 开关(可以点击)
- Text = 笔记本(可以写)
Label 的构造函数:
Label(Composite parent, int style)
parent: 父容器(这里是 Shell)style: 样式(SWT.NONE表示默认样式)
为什么需要指定父容器?
UI 是一个树形结构:
Display
└── Shell(窗口)
└── Label(标签)
└── Button(按钮)
└── ...
每个控件都需要一个"家"(父容器):
- Label 的家是 Shell
- Button 的家是 Label(但通常 Button 的家也是 Shell)
- 控件的"家"决定了它显示在哪里
4. 打开窗口
shell.open();
费曼解释:open() 做了什么?
open() 就像"掀开红布":
- 创建 Shell 时,窗口还在"后台"(看不见)
- 调用 open() 后,窗口"前台"显示(可见)
类比:
- 在商店里,衣服穿在模特身上(shell 创建)
- 但模特被布盖着(不可见)
- 掀开布(shell.open()),顾客才能看到衣服
5. 事件循环
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
费曼解释:事件循环是什么?
事件循环就像"门卫":
- 站在门口(Display)守着
- 有人来了(事件发生),门卫去处理
- 没人来了,门卫休息一下
事件的例子:
- 用户点击按钮
- 用户关闭窗口
- 用户移动鼠标
- 用户输入文字
readAndDispatch() 的作用:
- 检查有没有事件
- 有事件:处理事件(dispatch)
- 没事件:返回 false
sleep() 的作用:
- 没事件时,让 CPU 休息一下
- 避免空转浪费资源
类比:
事件循环 = 餐厅服务员
while (餐厅没关门) {
if (有顾客点单) {
去点单(readAndDispatch)
} else {
在旁边休息(sleep)
}
}
为什么需要事件循环?
没有事件循环:
- 程序创建窗口后就退出了
- 窗口闪一下就消失了
有了事件循环:
- 程序持续运行
- 窗口一直显示
- 可以响应用户操作
<code>shell.isDisposed()</code> 的含义:
Disposed = 已销毁
- 用户点击窗口的关闭按钮
- 窗口被标记为"已销毁"
- isDisposed() 返回 true
- 事件循环退出
- 程序结束
6. 释放资源
display.dispose();
费曼解释:dispose() 是什么?
dispose() 就像"打扫卫生":
- 关门前,把所有东西清理干净
- 释放占用的内存
- 释放占用的系统资源
类比:
- 你开了一间店(Display 创建)
- 营业了一天(事件循环)
- 关门前要打扫卫生(dispose)
- 否则垃圾会越堆越多
为什么必须释放资源?
SWT 资源的特殊性:
- SWT 控件映射到操作系统的原生对象
- 这些对象由操作系统管理
- Java 的 GC(垃圾回收)不会自动回收它们
如果不 dispose:
- 操作系统资源泄漏
- 内存占用增加
- 最终系统变慢或崩溃
类比:
Java 对象 = 你在笔记本上写的字
- GC 像橡皮擦,擦掉没用的字
SWT 资源 = 你借了图书馆的书
- GC 不会帮你还书
- 你必须自己还(dispose)
- 否则书越来越多,图书馆崩溃
完整的注释版本
package com.example;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
public class HelloWorld {
public static void main(String[] args) {
// ===== 第一阶段:准备环境 =====
// 创建 Display(连接操作系统的桥梁)
// 每个 SWT 程序必须有且只有一个 Display
Display display = new Display();
// 创建 Shell(窗口的容器)
// Shell 需要 Display 作为参数,因为窗口需要与操作系统通信
Shell shell = new Shell(display);
shell.setText("Hello SWT"); // 设置窗口标题(显示在标题栏)
shell.setSize(300, 200); // 设置窗口大小(宽度 × 高度)
// ===== 第二阶段:添加控件 =====
// 创建 Label(标签)
// 第一个参数:父容器(这里把 Label 放在 Shell 里面)
// 第二个参数:样式(SWT.NONE 表示默认样式)
Label label = new Label(shell, SWT.NONE);
label.setText("Hello, SWT!"); // 设置标签文本
label.setBounds(100, 80, 100, 30); // 设置位置和大小(x, y, 宽度, 高度)
// ===== 第三阶段:显示窗口 =====
// 打开窗口(从不可见变为可见)
shell.open();
// ===== 第四阶段:事件循环 =====
// 事件循环:持续运行,处理事件
while (!shell.isDisposed()) { // 只要窗口没有被关闭
if (!display.readAndDispatch()) { // 如果没有事件需要处理
display.sleep(); // 就让 CPU 休息一下
}
}
// ===== 第五阶段:清理资源 =====
// 释放 Display 及其管理的所有资源
// 这会自动释放所有控件(Label、Shell 等)
display.dispose();
}
}
2.3 常见问题解决
问题 1:找不到原生库(.dll, .so, .jnilib)
错误信息:
java.lang.UnsatisfiedLinkError: Could not load SWT library. Reasons:
no swt-win32-**** in java.library.path
no swt-win32 in java.library.path
Can't load library: ...\swt-win32-****.dll
原因分析:
SWT 原生库就像"翻译词典":
- Java 代码需要"翻译"成操作系统能理解的指令
- 原生库就是"翻译词典"
- 找不到词典 = 无法翻译 = 程序崩溃
解决方案(按优先级):
方案 1:确保依赖正确(Maven/Gradle)
<!-- 检查 pom.xml 是否包含了对应平台的依赖 -->
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.swt.win32.win32.x86_64</artifactId>
<version>3.124.200</version>
</dependency>
方案 2:手动设置 java.library.path
# 编译
javac -cp lib/swt.jar src/main/java/com/example/HelloWorld.java
# 运行(指定原生库位置)
java -cp lib/swt.jar;. -Djava.library.path=lib com.example.HelloWorld
方案 3:提取原生库到运行目录
// 在代码中提取原生库(适合打包应用)
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class NativeLibraryExtractor {
public static void extractNativeLibrary(String resourceName, String targetPath) throws Exception {
try (InputStream in = NativeLibraryExtractor.class.getResourceAsStream(resourceName);
FileOutputStream out = new FileOutputStream(targetPath)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
}
}
// 使用示例
public static void main(String[] args) throws Exception {
// 提取原生库
String osName = System.getProperty("os.name").toLowerCase();
String nativeLib;
if (osName.contains("win")) {
nativeLib = "/lib/swt-win32.dll";
} else if (osName.contains("mac")) {
nativeLib = "/lib/swt-cocoa.dylib";
} else {
nativeLib = "/lib/libswt-gtk-3.so";
}
String targetDir = System.getProperty("java.io.tmpdir");
String targetPath = targetDir + nativeLib.substring(nativeLib.lastIndexOf('/'));
// 确保目录存在
Files.createDirectories(Path.of(targetDir));
// 提取
extractNativeLibrary(nativeLib, targetPath);
// 设置库路径
System.setProperty("java.library.path", targetDir);
// 继续运行 SWT 程序...
}
问题 2:平台相关的坑
Windows 平台
问题:高分屏(HiDPI)模糊
原因:
- Windows 的 DPI 缩放导致 SWT 控件模糊
解决方案:
// 在 main 方法最开始设置
System.setProperty("swt.autoScale", "true");
问题:菜单栏显示在窗口顶部(Mac 风格)
Windows 默认菜单栏在窗口顶部,这是正常的。
如果想让菜单栏在窗口顶部(而不是屏幕顶部),默认就是这样的。
Mac 平台
问题:程序没有 Dock 图标
原因:
- SWT 程序默认没有 Dock 图标
- 用户看不到程序在运行
解决方案:
// 设置应用名称(会在 Dock 显示)
System.setProperty("apple.laf.useScreenMenuBar", "true");
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "我的应用");
问题:菜单栏显示在窗口顶部,但应该在屏幕顶部
原因:
- Mac 的菜单栏应该在屏幕顶部,不是窗口顶部
解决方案:
// 设置菜单栏在屏幕顶部
System.setProperty("apple.laf.useScreenMenuBar", "true");
问题:无法打开多个窗口
原因:
- Mac 应用默认是单例模式
解决方案:
// 在 Info.plist 中添加(打包应用时)
<key>LSMultipleInstancesProhibited</key>
<false/>
Linux 平台
问题:找不到 GTK 库
错误信息:
java.lang.UnsatisfiedLinkError: Could not load SWT library. ... libgtk-3.so: cannot open shared object file
原因:
- Linux 需要 GTK 库,但系统没有安装
解决方案(Ubuntu/Debian):
sudo apt-get install libgtk-3-dev
解决方案(Fedora/RHEL):
sudo dnf install gtk3-devel
问题:主题不统一
原因:
- Linux 有多个桌面环境(GNOME、KDE、XFCE)
- 每个桌面环境的主题不同
解决方案:
- SWT 会自动使用系统默认主题
- 如果需要统一主题,使用 CSS(需要额外库)
问题 3:调试技巧
启用 SWT 调试模式
// 在 main 方法最开始设置
System.setProperty("org.eclipse.swt.debug", "true");
// 启用特定调试选项
System.setProperty("org.eclipse.swt.debug.widgets", "true"); // 打印控件创建信息
System.setProperty("org.eclipse.swt.debug.events", "true"); // 打印事件信息
System.setProperty("org.eclipse.swt.debug.graphics", "true"); // 打印图形调试信息
使用 Display.getAppName()
Display display = new Display();
System.out.println("应用名称: " + display.getAppName());
System.out.println("应用版本: " + display.getAppVersion());
System.out.println("SWT 版本: " + SWT.getVersion());
检查控件树
public static void printControlTree(Control control, int indent) {
String indentation = " ".repeat(indent);
System.out.println(indentation + control.getClass().getSimpleName());
if (control instanceof Composite) {
Composite composite = (Composite) control;
Control[] children = composite.getChildren();
for (Control child : children) {
printControlTree(child, indent + 1);
}
}
}
// 使用示例
printControlTree(shell, 0);
监听所有事件
public class EventTracer {
public static void addAllListeners(Control control) {
// Selection 事件
control.addListener(SWT.Selection, event -> {
System.out.println("Selection: " + control);
});
// Modify 事件
control.addListener(SWT.Modify, event -> {
System.out.println("Modify: " + control);
});
// Focus 事件
control.addListener(SWT.FocusIn, event -> {
System.out.println("FocusIn: " + control);
});
control.addListener(SWT.FocusOut, event -> {
System.out.println("FocusOut: " + control);
});
// Mouse 事件
control.addListener(SWT.MouseDown, event -> {
System.out.println("MouseDown: " + control);
});
control.addListener(SWT.MouseUp, event -> {
System.out.println("MouseUp: " + control);
});
// 递归添加到子控件
if (control instanceof Composite) {
Composite composite = (Composite) control;
for (Control child : composite.getChildren()) {
addAllListeners(child);
}
}
}
}
// 使用示例
addAllListeners(shell);
2.4 本章小结
关键步骤回顾
环境搭建 → 编写程序 → 运行调试
↓ ↓ ↓
Maven/Gradle Display → 事件循环
原生库 Shell 资源释放
Label
费曼测试:你能解释清楚吗?
- Display 是什么?
- 连接操作系统的桥梁 - 像电话机,让 Java 代码联系操作系统
- Shell 是什么?
- 窗口的容器 - 像相框,承载控件
- 事件循环是什么?
- 持续处理事件 - 像门卫,等待并处理事件
- 为什么要 dispose?
- SWT 资源由操作系统管理 - Java GC 不会自动回收 - 必须手动释放,否则泄漏
下一章预告
现在你已经能写出最简单的 SWT 程序了,
但你还不知道:
- 如何让窗口更漂亮?(布局管理器)
- 如何让控件响应用户操作?(事件处理)
- 如何创建复杂的界面?(组合控件)
下一章,我们将深入学习 SWT 的五大组件,
从 Display 到布局管理器,
让你真正掌握 SWT 的基础能力。
练习题:
- 修改 Hello World 程序,添加一个按钮(Button),点击按钮后改变 Label 的文本。
- 尝试在 Mac 上运行程序,设置菜单栏在屏幕顶部。
- 使用调试技巧,打印所有控件树和事件。
(提示:Button 的样式是 SWT.PUSH,事件监听使用 addListener(SWT.Selection, event -> ...))