使用 SWT 搭建跨平台桌面应用

前言

在我的计算机组成课程上,教授要求编写一个 MIPS 汇编器并提供图形界面。由于我的工作环境并非 Windows,同时希望在各个平台的界面中使用原生控件,并且我偏好使用静态类型语言,所以决定使用 SWT 进行图形界面的编写。

集成

鉴于 Maven 上的版本老旧,推荐直接前往 SWT 官方网站 下载稳定发行版本。我使用的开发环境是 IntelliJ Idea,因此在 Project Structure 中的 Libraries 和 Artifacts 内添加相应的设置即可。

架构

我是一名 Android 开发者,因此我在学习 SWT 时也会与 Android 框架进行对比。二者多有类似,也有不同。

在 SWT 中,Display 与 Android 中 ContextApplication 的设计有所相近,具有系统资源管理和应用层面操作的功能。而 Display 的一个实用的扩展是 Display.getCurrent()Display.getDefault(),即可以通过静态方法获取当前线程或默认的 Display

而与 Android 中的 Activity 相近的则是 ShellShell 代表一个平台无关的抽象窗口,由此可以进行窗口相关的操作,并建立视图结构。

与 Andorid 不同,SWT 中没有 XML 布局系统,所有视图的创建需要由应用程序的 Java 代码完成(但有自动将 XML 转换为 Java 代码的工具,如 SWTXML 等)。Control 与 Android 中的 View 类似,是所有控件的基类,但不同的是,所有的 Control 在构造时都需要一个 Composite 作为父对象,既是 Android 中的 Context,也是视图结构上的父容器。而之后如果需要更改控件的父容器,可以调用Control.setParent(),但是否成功则需要由平台支持决定。

一个窗口最初的 Composite 实例即是 Shell

使用

主循环

SWT 的主循环是由应用程序自己书写的。常见的范式如下:

1
2
3
4
5
6
7
8
9
10
11
12
onCreateDisplay();

onCreateShell();

shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}

display.dispose();

视图结构创建

在创建 Shell 实例之后,一般会添加界面元素和布局信息。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void onCreateShell() {

shell = new Shell(display, SWT.SHELL_TRIM);

// 设置图标
shell.setImages(icons);

// 设置根视图布局
shell.setLayout(new FillLayout());

// 创建一个 SashForm,作为根视图的子视图
sashForm = new SashForm(shell, SWT.VERTICAL);

// 创建编辑器文本框
editText = new StyledText(sashForm, SWT.H_SCROLL | SWT.V_SCROLL);
editText.setAlwaysShowScrollBars(false);

// 创建消息区文本框
messageText = new StyledText(sashForm, SWT.H_SCROLL | SWT.V_SCROLL);
messageText.setAlwaysShowScrollBars(false);
messageText.setEditable(false);

sashForm.setWeights(new int[]{8, 2});

// 本程序菜单实现依赖于 editText
onCreateMenu();
shell.setMenuBar(menu);

// 监听窗口关闭事件,提示保存
shell.addListener(SWT.Close, new Listener() {
@Override
public void handleEvent(Event event) {
event.doit = onClose();
}
});

// 更新窗口标题
updateTitle();
}

视图布局

SWT 中的布局方式分为两步:使用 Composite.setLayout() 设置父元素布局子元素时使用的布局方式,使用 Control.setLayoutData() 提供子元素的布局参数。这与 Android 中的布局方式有些许不同,即 SWT 中的 Layout 是父容器的参数而非父容器本身的类型。

SWT 中常用的 Layout 包括 FillLayoutRowLayoutGridLayoutFormLayoutSashForm 等。对于前三种基础布局,可以参考 Eclipse 的这篇文章 进行学习。

资源系统

与 Android 框架不同,SWT 只是一个图形界面库,因此不提供资源系统。

在写作一般规模的 SWT 应用时,可以使用 Java 自带的 ResourceBundle 进行资源管理与国际化。

常见的范式如下:

1
2
ResourceBundle resourceBundle = ResourceBundle.getBundle("res/string/mipside", new Utf8Control());
Display.setAppName(resourceBundle.getString("app_name"));

其中 Utf8Control 是为了使得 ResourceBundle 支持直接读取 UTF-8 编码的资源文件的类,可以从之后我的项目中获得我编写的版本;ResourceBundle 默认只支持 ASCII 编码。

设置存储

为了保存用户的设置,可以使用 Java 自带的 Preferences 进行键值对的存储。使用时可以通过 Preferences.userNodeForPackage() 获取一个节点的 Preferences 实例,而之后则与使用 Android 中的 SharedPreferences 类似。

部署

在 Linux 上,一个 Shell 脚本执行 java -jar 已经足够。

在 Windows 上,经过一些比较,我决定使用 launch4j,至今体验良好。

在 Mac OS X 上,JVM 需要特定参数才能正常启动应用,以及其他打包应用程序的细节,可以参考 这篇 Eclipse 的文档

评价

在使用 SWT 完成我的项目后,总体来说,感觉这是一个可用的图形库。

从接口方面看,SWT 遵循着一致的命名规范,但 Builder 模式的缺失使得构造控件的过程较为冗长,此外将样式、键码等大量常量放置在 SWT 类中也显得较为奇怪。

从功能方面看,SWT 跨平台调用原生控件的能力确实令人惊喜。众多部件在 Windows、Linux GTK+ 3 和 GTK+ 2、Mac OS X Cocoa 上不需要经过调试即表现良好,并且通过打包与原生应用几乎没有什么差异。美中不足的只是一些小的设计细节,例如标签中的文本不能使用光标选择复制、带有链接的多行标签需要使用另一个类却不能指定多行文本居中对齐。但总体来说,这些瑕疵要么可以绕过,要么相比于 SWT 本身带来的便捷来说,无伤大雅。

结语

菜单、字体、图像、对话框、StyledText,还有关于 SWT 的很多方面这篇文章没有覆盖到。但网络上相关的资源是十分丰富的,例如 Google、Eclipse 的 Javadoc 和 Java2s 上的代码示例都是有用的信息来源。

如果有兴趣,我的项目中也有一些按照我自己对 SWT 的理解写出的代码,可以 移步至 GitHub 浏览