ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SWT图形化界面图书管理系统:从环境搭建到打包全攻略

SWT图形化界面图书管理系统:从环境搭建到打包全攻略 简介基于开源SWT组件库实现的图书管理系统图形化界面源码面向具备Java基础、希望学习原生桌面开发或需要完成课程设计、毕业设计的开发者提供了从界面布局到数据库操作的完整参考。相比常见以Swing为例的教学项目SWT版本更贴近操作系统原生控件便于对比理解两类GUI库的差异。资源压缩包约10.07MB共259个文件包括45个Java源文件、107个class编译类、80个ico图标、12张jpg图片并附SQL脚本、jar依赖、项目配置、doc说明与可运行exe适合导入IDE直接运行或体验演示效果。目前已有483人学习下载。源码覆盖图书新增、查询、借阅、归还和读者管理等核心业务预览中的主窗口、图书数据访问类、读者数据访问类及多个业务操作类清晰呈现了SWT常用组件、网格布局、对话框封装与数据库交互的实现路径类间调用关系体现典型三层结构配合脚本可快速建表造数便于着重研读事件监听、资源加载与释放、表格数据展示等知识点是一份适合课程设计复用和SWT入门进阶的完整素材包内文档也有助于环境配置与问题定位。1. 从选型到落地SWT 图形化界面的图书管理系统在解决什么问题前阵子帮一个内部资料室做图书登记工具第一版用了 Swing窗口一开就带着一股“2008 年”的味道表格一多还拖不动。后来换成 SWT界面瞬间变得和系统原生窗口一模一样表格滚动也顺了。所谓“图书管理系统用 SWT 做的 JAVA 图形化界面”本质就是用 Eclipse 出品的 SWT 控件库把图书的增删改查、借阅归还这些 CRUD 操作包装成一套能双击运行的桌面程序。这套方案解决的核心问题是“界面太丑、太笨重”SWT 直接调用操作系统原生控件所以按钮、表格、输入框的观感和系统设置保持一致开发起来又比 JavaFX 那套 CSS 样式的学习成本低。适合正在做 Java 课程设计、内部小工具或者想给老管理系统补一个维护窗体的同学。Swing 和 SWT 怎么选也是 java 面试题里常被拿出来聊的经典对比值得认真过一遍。下面从环境搭建开始一直到打包交付把完整路径走一遍。2. 搭 SWT 开发环境先把 JDK 位数和 SWT 版本对齐SWT 和 Swing 最大的不同在于Swing 的控件是纯 Java 画的SWT 则是通过 JNI 包了一层系统的原生控件。这个设计带来一个直接后果SWT 的 jar 是跟着操作系统走的Windows 上的包名带 win32Linux 上带 gtk.linuxmacOS 上带 cocoa.macosx。如果你下载错了平台版本代码写对了也起不来所以第 2 章先把环境这件事说透。2.1 确认 JDK 安装与位数JAVA_HOME 别指错先说你用什么 JDK 运行。SWT 对 Java 版本的要求不算高用 JDK 8 或者 11 都很常见关键是 JDK 位数必须和 SWT 包一致。机器上装了好几个 JDK 时最容易翻车的是 JAVA_HOME 和 PATH 各指各的编译没问题一运行就报找不到主类或者版本不对。在命令行里验证一下到底用的是哪一个echo %JAVA_HOME% java -version第一行看环境变量指向第二行看当前 PATH 里的 java 版本。如果 PATH 里有别的 JDK 目录排在前面java -version 出来的就不是你配置的那个。Windows 上还可以再加一句where java直接列出解析到的 java.exe 完整路径两边一对照就知道环境变量有没有生效。注意如果是 JDK 17 及以上老教程里常用的java -d64 -version已经没有输出直接看 java -version 里的版本号就行。在这一步确认好后面 SWT 的 jar 和 JVM 位数的匹配才有意义。很多人在 IDE 里能跑、命令行不能跑绝大多数就是 JAVA_HOME 和 PATH 不一致导致的。把环境问题排在第一位处理后面能省出一整个下午的排查时间。2.2 拿到正确的 SWT jar平台后缀不能抄错SWT 的下载地址一般在 Eclipse 官网的 SWT 页面那里会按平台打成一个 zip包内是 org.eclipse.swt.win32.win32.x86_64_版本号.jar 这类文件名。Windows 64 位就找带 win32.win32.x86_64 的Linux 找 gtk.linux.x86_64macOS 找 cocoa.macosx.aarch64 或 x86_64。版本号对应 Eclipse 的 release 线例如 4.x 系列具体以官网当期发布为准。常见的做法有两种。一是直接下载 zip把 jar 复制到项目里的 lib 目录然后在 IDE 里 Add to Build Path二是用 Maven 的方式依赖坐标里同样带平台后缀。我一般建议课程设计项目用第一种方式简单直接也不容易出现仓库里没有对应平台坐标的问题。用 Maven 的话要注意SWT 的坐标不是单一的 org.eclipse.swt同样要写全平台后缀否则拉下来的包无法使用。这一点和普通 Java 库完全不同很多从 Spring 转过来的人在这里栽跟头。把 jar 放进项目后在 Eclipse 或 IDEA 中右键 lib 下的 swt jarAdd to Build Path。然后检查一下依赖列表里能看到 org.eclipse.swt.widgets.Display说明类路径加载成功。如果你看不到多半是 build 目录没有刷新重新构建一次项目再看。2.3 写第一个 SWT 窗口Display 与 Shell 的生命周期类路径通了以后用一个最小窗口验证环境是否真的可用。下面是完整的最小例子直接跑通说明环境没问题。import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; public class FirstSwtWindow { public static void main(String[] args) { Display display new Display(); Shell shell new Shell(display); shell.setText(图书管理系统 - SWT); shell.setSize(960, 600); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } }逻辑说明Display 是 SWT 与操作系统显示设备之间的桥梁一个进程一般只建一个Shell 是主窗口对象对应系统里的一个原生窗口。readAndDispatch()从系统事件队列里取事件并分发没有事件时就sleep()让出 CPU这就是 SWT 的事件循环。最后窗口关闭后一定要display.dispose()把 JNI 层占用的原生资源还给操作系统。参数说明shell.setSize 的两个参数是窗口宽高像素setText 设置的是任务栏和标题栏上的文字。如果你误写成 setTitle编译期不会报错但窗口标题不会生效因为 Shell 用的是 setText这是 SWT 和 Swing 命名习惯的一个显著差异。跑这个最小程序时如果抛出 ClassNotFoundException说明 jar 没有真正进入 classpath如果抛出其它异常去看 5.5 节列出的启动崩溃场景。2.4 在启动类里顺手打开一个 GridLayout 布局既然目标是图书管理系统窗口里早晚要放多个控件这里提前引入布局的概念。SWT 的 GridLayout 是最灵活的布局管理器类似 Swing 的 GridBagLayout但参数更直白。在 2.3 的 Shell 上放一个主容器并指定列数import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; Composite root new Composite(shell, SWT.NONE); root.setLayout(new GridLayout(2, false)); root.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));说明这里创建了一个 root 容器里面的布局是两列第二参数 false 表示不强制每列等宽。GridData 四个参数分别是水平对齐方式、垂直对齐方式、宽度是否随父容器伸展、高度是否随父容器伸展SWT.FILL 表示尽量填满。如果不写 setLayoutData控件会缩在自己的 preferred size不会自动撑满窗口这也是后面很多布局问题的源头之一。注意Shell 本身并不直接使用 GridLayout而是先放一层 Composite 再做布局这样后续拆分头部、表格、底部状态栏时会非常方便。到这里环境已经跑通下一章开始搭图书管理系统的三块界面骨架。3. 图书管理系统界面骨架表格、表单与弹窗布局SWT 没有专门的 Form 布局但用 GridLayout 完全可以撑起一个管理系统。常见的布局是顶部放工具栏中部放表格底部放表单区再加上一个借阅弹窗。这一章把界面骨架拆开讲每一块代码都可以直接抄进自己的项目。3.1 主窗口分区用 Composite 搭出三行结构先用三个 Composite 把主窗口切成“工具栏 → 书籍表格 → 底部操作区”三段。这个分区结构是整个界面的地基后面往每个区域里塞控件就行。Composite root new Composite(shell, SWT.NONE); root.setLayout(new GridLayout(1, false)); root.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); Composite topBar new Composite(root, SWT.NONE); topBar.setLayout(new GridLayout(4, false)); topBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); Composite tableArea new Composite(root, SWT.NONE); tableArea.setLayout(new GridLayout(1, false)); tableArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); Composite bottomForm new Composite(root, SWT.NONE); bottomForm.setLayout(new GridLayout(6, false)); bottomForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));参数说明这里的三个 GridData 中topBar 和 bottomForm 的第四参数是 false意思是这两个区域不随窗口拉伸变高只有中间的 tableArea 垂直方向被拉满正好符合“表格区自动撑满”的直觉。如果你把三块都写成 true窗口放大后表格会和其他区域一起被拉长底部按钮会悬在窗口中间观感非常奇怪。另外注意只要创建 Composite 就必须配套 setLayout否则子控件的位置无从谈起运行后控件会全部挤在左上角这一步是一层就有代码里最常见的错误。3.2 用 Table 做书目列表列宽、列标题与排序准备书目列表是图书管理系统的核心视图。SWT 的 Table 控件支持多列表头、网格线、单选多选都可以通过样式常量开启。Table bookTable new Table(tableArea, SWT.BORDER | SWT.FULL_SELECTION | SWT.V_SCROLL); bookTable.setHeaderVisible(true); bookTable.setLinesVisible(true); String[] columns {书名, 作者, ISBN, 库存, 分类}; int[] widths {180, 100, 120, 60, 80}; for (int i 0; i columns.length; i) { TableColumn col new TableColumn(bookTable, SWT.NONE); col.setText(columns[i]); col.setWidth(widths[i]); }逻辑说明SWT.BORDER 给表格加边框FULL_SELECTION 允许选中整行而不是只选中单个单元格V_SCROLL 开启垂直滚动。这两行设置很关键没有 FULL_SELECTION 的话用户在表格上点击时只能看到一个高亮格子视觉上很难用没有 V_SCROLL 的话数据超过一屏时只能靠鼠标滚轮而滚轮事件在 Table 上默认处理得并不顺滑。setHeaderVisible 和 setLinesVisible 分别控制表头显示和行间分隔线图书管理系统里这两项都必须打开。参数说明TableColumn 的宽度是像素值你可以根据实际显示效果调整。建议书名列给最宽的空间ISBN 列 120 左右足够显示 13 位编号。列创建后如果想支持点击排序可以在这个循环里给每列挂 SelectionListener第 6 章会给出一个不破坏当前刷新逻辑的做法。3.3 底部表单与校验Group 把输入项分组底部操作区放书名、作者、ISBN、库存、分类五个输入项和增删改按钮。SWT 里做带标题的输入框区域常用 Group 代替 Composite标题会显示在边框左上角。Group formGroup new Group(bottomForm, SWT.NONE); formGroup.setText(编辑区域); formGroup.setLayout(new GridLayout(4, false)); formGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); Label nameLabel new Label(formGroup, SWT.NONE); nameLabel.setText(书名); Text nameText new Text(formGroup, SWT.BORDER); nameText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));逻辑说明Group 本质上和 Composite 一样是容器区别是多了标题文字。每个 Text 输入框旁边配一个 Label 做提示属于 SWT 常规做法。需要特别注意Text 的宽度不会自动跟随容器变化必须给 setLayoutData 设置 GridData否则窗口拉宽后输入框还是老宽度整个表单看起来像左边一列输入框右边一片空白。实际项目里表单区通常还有校验逻辑书名和 ISBN 不能为空、库存必须是数字。这些校验建议在点击“新增/修改”按钮时做而不是在 KeyListener 里每敲一个键就校验否则输入过程中会有很强的打断感。校验失败时用 MessageBox 提示错误比在控件旁边画红字简单很多。提示把校验写成一个返回 boolean 的小方法通过后再走数据层。这样表单只负责收集输入正确性收口到一处后面维护时不用在按钮事件里到处找逻辑。分类字段如果选项固定建议用 Combo 下拉框替代 Text可以直接限定合法值。Combo 的写法是new Combo(formGroup, SWT.DROP_DOWN | SWT.READ_ONLY)再 add 几个预设项。READ_ONLY 这个常量非常重要没有它用户会往里输入任意值数据库里就会混入你没见过的分类名。3.4 借阅登记弹窗Dialog 与 MessageBox 的用法图书管理系统除了书目维护还需要借阅、归还操作。借阅弹窗我建议用 Dialog 或者直接再开一个 Shell通过setParent关联主窗口。弹窗里放读者编号、图书编号、借出日期三个字段即可。Shell borrowShell new Shell(shell, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); borrowShell.setText(借阅登记); borrowShell.setSize(320, 200); Text readerId new Text(borrowShell, SWT.BORDER); readerId.setBounds(100, 20, 180, 24);这里的 SWT.DIALOG_TRIM 表示采用系统对话框的边框样式只有标题栏和关闭按钮没有最大最小化APPLICATION_MODAL 表示模态窗口在它关闭前主窗口不能操作。setBounds 是 SWT 里最原始的定位方式适合弹窗这种表单简单、不需要响应窗口拉伸的场景。如果你在主窗口里也用 setBounds窗口大小一变化布局就会乱套所以主窗口一律走 GridLayout弹窗里才可以用绝对坐标。图书管理系统里还要想清楚一件事图书被借出后库存要不要扣减。真正接数据库后会发现很多看似简单的界面操作背后都连着事务这个在下一章数据打通时再做处理。弹窗关闭后的动作建议放在 OK 按钮的监听器里通过borrowShell.dispose()关闭窗口同时把结果返回给主窗口而不是让主窗口轮询弹窗状态。4. 界面和数据打通JDBC 查询到 Table 刷新的完整链路界面做得再像样没有数据就是空壳。这一章用 MySQL JDBC 把表结构建好、数据查出来、填进 Table并实现选中行回填表单的联动。数据访问这层我不会引入 MyBatis 或 Spring原因很简单桌面程序要的是轻量交付JDBC 在这个规模下足够干净。4.1 建库建表三张核心表的字段设计图书管理系统的数据模型不需要太复杂三张表就够书籍表 books、读者表 readers、借阅记录表 borrow_records。下面给出可以直接执行的建表 SQL。CREATE DATABASE IF NOT EXISTS library DEFAULT CHARSET utf8mb4; USE library; CREATE TABLE books ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(100) NOT NULL, author VARCHAR(50) NOT NULL, isbn VARCHAR(20) UNIQUE NOT NULL, category VARCHAR(30), stock INT NOT NULL DEFAULT 1 ); CREATE TABLE readers ( id INT PRIMARY KEY AUTO_INCREMENT, reader_name VARCHAR(50) NOT NULL, role VARCHAR(20) NOT NULL ); CREATE TABLE borrow_records ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, reader_id INT NOT NULL, borrow_time DATETIME NOT NULL, return_time DATETIME, KEY idx_book_id (book_id), KEY idx_reader_id (reader_id) );字段说明books 表里 ISBN 加上唯一索引可以防止同一本书重复录入stock 表示当前可借库存借出时减 1归还时加 1。borrow_records 用 book_id 和 reader_id 关联另外两张表并建立普通索引因为在查询“某本书被谁借走”“某人借了什么”时会高频用到这两个字段。如果你想把借阅历史保留得更细可以把 return_time 设为 NOT NULL 并用一个特殊值标记“未归还”但那样查询语句要多一个条件课程设计里用 NULL 表示未归还是最直观的。如果你不想手写这份 SQL也有代码生成工具能根据实体类反向生成建表语句。不过我建议还是手写一遍三张表的字段不多写的时候能把主外键关系想清楚后面写实体类和 JDBC 映射会顺很多。4.2 数据访问一个 JDBC 工具类加一个 DAO网上很多教程会直接摆出 MyBatis 和 Spring 的依赖但 SWT 程序通常用不着那么重的容器。界面层是桌面程序数据层就两层一个 DbUtil 负责拿连接一个 BookDao 负责 SQL。这样发布的时候只需要带一个 JDBC 驱动 jar不需要配置 Spring 的扫描路径和 XML。public class DbUtil { private static final String URL jdbc:mysql://localhost:3306/library?useSSLfalsecharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的密码; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }代码逻辑很简单但有两个参数值得特别注意URL 里的 characterEncodingutf8 是中文不乱码的关键字段里存的是 UTF-8读取也必须按 UTF-8 解析useSSLfalse 是因为本地开发库不需要加密传输不加的话新版 MySQL 驱动会打出 SSL 警告。密码直接写在常量里只适合课程设计阶段如果要交付给他人使用至少要改成从外部 properties 文件读取避免别人拿到 jar 反编译就能看到数据库口令。BookDao 里我用一个 findAll 方法演示核心写法其他方法按同样模式扩展public ListBook findAll() { ListBook books new ArrayList(); String sql SELECT id, book_name, author, isbn, category, stock FROM books ORDER BY id; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Book book new Book(); book.setId(rs.getInt(id)); book.setBookName(rs.getString(book_name)); book.setAuthor(rs.getString(author)); book.setIsbn(rs.getString(isbn)); book.setCategory(rs.getString(category)); book.setStock(rs.getInt(stock)); books.add(book); } } catch (SQLException e) { e.printStackTrace(); } return books; }逻辑说明try-with-resources 是必须的写法Connection、PreparedStatement、ResultSet 三个资源都会自动关闭否则在 SWT 这种需要长时间运行的桌面程序里连接会越积越多最终把数据库连接数打满。如果你用 Teacher 教的那种先关闭 ResultSet 再关闭 Statement 的旧写法也正确但 try-with-resources 更不容易漏。列名映射这里没有用 ORM简单直接也方便你在返回结果后调整字段。4.3 把查询结果填进表格刷新方法是全系统的主干线表格刷新是图书管理系统里被调用最频繁的方法。新增、修改、删除、搜索都要回到同一时刻的表格状态所以把 refreshTable 做对后面每个按钮都轻松。public void refreshTable() { bookTable.removeAll(); ListBook books bookDao.findAll(); for (Book book : books) { TableItem item new TableItem(bookTable, SWT.NONE); item.setText(new String[]{ book.getBookName(), book.getAuthor(), book.getIsbn(), String.valueOf(book.getStock()), book.getCategory() }); item.setData(book); } }逻辑说明第一步 bookTable.removeAll() 干掉旧行再查询全量数据逐行 new TableItem。setData(book) 是把整个对象挂在表格行上这样后面用户点击某一行时可以直接从 item.getData() 把完整的 Book 对象取出来不用再按 ISBN 去数据库查一遍。很多初学者会忽略 setData然后不得不在点击事件里重新拼查询条件白白多写一段代码。参数说明TableItem.setText(String[]) 要求数组元素个数等于表格列数顺序和列定义一致。如果列数对不上显示结果会错位而且不报错属于特别隐蔽的 bug。建议把列定义数组和这里的字符串数组都抽成同一个常量比如定义一个COLUMNS数组两处都引用它。这个方法在数据量几千条时直接在主线程里跑也没有明显卡顿但如果之后数据量上了万建议把查询部分放子线程只留下 UI 刷新部分用 asyncExec 回到主线程具体写法在第 5 章讲。4.4 选中行回填表单SelectionListener 的联动写法点表格某一行底部表单自动变成这一行的内容。这个交互实现起来很直观给表格挂上 SelectionListener在 widgetSelected 里读取选中行的内容并 setText 到各个输入框。bookTable.addSelectionListener(new SelectionAdapter() { Override public void widgetSelected(SelectionEvent e) { TableItem item bookTable.getSelection()[0]; Book book (Book) item.getData(); nameText.setText(book.getBookName()); authorText.setText(book.getAuthor()); isbnText.setText(book.getIsbn()); categoryText.setText(book.getCategory()); stockText.setText(String.valueOf(book.getStock())); } });逻辑说明getSelection() 返回的是选中行的数组单选模式下直接取 [0] 是安全的如果你开启了多选模式这里要先判断数组长度再取值否则空数组越界会抛异常。SelectionAdapter 是适配器类用它只需要实现自己关心的事件不用把所有监听方法都写出来。到这里从数据库到界面再到用户交互的主链路已经通了查询刷新表格、点行回填表单、修改后再次刷新。剩下新增和删除两个按钮的逻辑本质是复用同样的写库方法再调用 refreshTable不再展开写。你在做新增按钮时只要记住一件事写库操作成功后调用一次 refreshTable不要自己手动往表格里 add 一行否则表格数据和数据库会很容易不一致这也是很多图书管理系统的库存数字越跑越偏的根源。5. 避坑与排查SWT 图形化界面最容易翻车的五个实战问题SWT 用着别扭的地方不在 API而在系统句柄和线程模型。绝大多数跑到一半崩掉的问题追根究底都是同一类原因。下面这 5 条是我做 SWT 图书管理系统过程中踩过也帮别人排查过的真实场景。5.1 跨线程更新界面Invalid thread access现象给查询按钮加了后台线程线程里查完数据库直接调用 bookTable.removeAll() 或者刷新表格程序立刻抛 SWTException: Invalid thread access。原因SWT 的控件不是线程安全的所有对 Widget 的操作必须在创建它的 UI 线程里进行。后台线程做完耗时查询后直接碰控件SWT 会检查当前线程和控件所属线程是否一致不一致就拒绝执行。这个设计比 Swing 更严格Swing 很多控件还允许在 EDT 外操作SWT 是明确禁止的。解决后台线程只负责查询和计算需要更新界面时通过 Display.getDefault().asyncExec() 把更新操作丢回 UI 线程队列。new Thread(() - { ListBook books bookDao.findAll(); Display.getDefault().asyncExec(() - { refreshTable(books); }); }).start();说明asyncExec 是非阻塞投递适合刷新表格这种不要求同步结果的场景。如果你需要等待结果再继续执行可以用 syncExec但 syncExec 在 UI 线程里调用会直接死锁所以默认请用 asyncExec。线程模型这块没有绕路可走任何企图绕过检查的写法都会在 Windows 上换来随机崩溃。另外注意这里线程里用到的 bookDao 如果也同时在主线程里被调用要做好同步或各线程持有独立 DAO否则连接池会报并发冲突。5.2 越用越卡最后白屏根因是 GDI 句柄泄漏现象程序刚开始很流畅运行几个小时后窗口变卡表格区域出现黑块最后整个窗口白屏重启又恢复正常。用任务管理器看进程的 GDI 对象数量一路涨到一万多。原因SWT 里创建的 Image、Color、Font、GC 以及 Cursor 都是操作系统句柄资源。很多人在表格里给每一行创建新的 Image 图标循环结束后既没有调用 image.dispose()也没有复用同一个 Image 对象句柄就只增不减。Windows 默认 GDI 对象上限约一万个突破后界面绘制直接失效。解决能复用的资源只创建一次。比如表格行图标在类的成员变量里创建shell 关闭时统一释放局部创建的 Color、Font用完立刻 dispose写在 finally 里也行。每次看到一个new Image(display, ...)先问自己这个对象会不会被释放。注意SWT 的 dispose 机制里对象不是自动垃圾回收的。“靠 GC 回收句柄”这种想法在 SWT 项目里行不通必须显式调用 dispose。这一点和 Java 堆内存完全是两套逻辑。如果你遇到 OutOfMemoryError第一反应是调大 IDEA 的编译堆设置但 SWT 程序跑几个小时后出现的这类问题先查 GDI 对象数量再调堆参数方向才对。IDEA 里那个“Build Process Heap Size”管的是编译器管不了你程序的句柄泄漏。5.3 布局失效GridLayout 下按钮挤成一团现象明明给按钮设置了 widthHint 或 minimumWidth窗口一打开按钮还是缩成默认大小外层容器 resize 后内部固定宽度的输入框又变形了。原因GridData 的 widthHint 只在控件获得首选尺寸时参与计算如果同一个控件被赋了两次 GridData或者你在布局之后又调用了 setLayoutData 覆盖之前设置的宽度约束就会失效。更隐蔽的情况是外层布局设置了 grabExcessHorizontalSpacetrue内层控件虽然写了宽高但分配到的空间不是按 widthHint 而是按剩余空间均分。解决每个控件的 GridData 只生成一次并保持引用需要固定宽度时同时设置 widthHint 和 minimumWidth 两个值。另外布局写完后不要用 setBounds 去微调一旦 setBounds 和 GridLayout 同时存在SWT 的布局系统会强制覆盖 setBounds 的结果出现“明明改了坐标却不动”的诡异现象。如果你要的是“某些列宽固定、其余列拉伸”的效果正确做法是给不需要拉伸的列 setLayoutData 设一个单独的 GridData(false, false, false, false)并在那一列控件上设 widthHint。5.4 高分屏发虚4K 显示器下字体模糊现象在 4K 屏幕、系统缩放 200% 的情况下SWT 程序界面的字体和图标看起来像是从低分辨率拉伸上来的文字边缘发虚而原生记事本应用是清晰的。原因SWT 渲染控件时依赖 Windows 的 DPI 感知级别。老版本 SWT 项目默认不做 DPI Aware 声明Windows 会把它当作不具备缩放感知能力的程序采用位图拉伸方式放大视觉上自然模糊。解决在 main 方法里最早的位置调用系统 API 触发 DPI 感知。如果用的是 JDK 9 以上和较新 SWT可以直接在 jar 所在目录放一个 manifest 文件声明 PerMonitorV2更通用的写法是用 JNA 或者反射调用 SetProcessDPIAware。注意这个调用必须发生在任何窗口创建之前否则不生效。如果你是在虚拟机里跑 Linux 桌面这个问题还会以另一种形式出现——SWT 的 GTK 版本和系统 GTK 版本不匹配字体渲染直接崩掉那种情况一般要先升级系统 GTK 依赖。5.5 启动即崩溃Cant load library swt-win32-xxxx.dll现象双击启动脚本后控制台立刻输出 java.lang.UnsatisfiedLinkError: Cant load library: swt-win32-XXXX.dll窗口都没出现就退出了。原因SWT 的 jar 里不只有 Java 类文件真正的原生代码是 jar 内的 dll 或 so。JVM 启动时通过 jar 包里的 native 目录查找动态库如果 jar 没有正确出现在 classpath 里、或者被某些打包工具压扁成普通文件native 库找不到就会抛这个异常。最常见的两个场景是把 32 位 SWT jar 用在 64 位 JVM 上以及把 SWT jar 打进 fat jar 后没有正确处理 native 解压顺序。解决第一确认 SWT jar 的文件名和 JVM 位数匹配win32.win32.x86_64 配 64 位 JVM。第二不要用 One-Jar 这类工具强行把 swt jar 合并进一个 jar正确的做法是 SWT jar 单独放在 lib 目录用 Class-Path 引用或写启动脚本指定 -cp。打包细节在下一章展开这里先记住SWT 项目里的 native 库和普通 jar 不是一个处理思路。6. 把系统交出去打包成可双击运行的程序与表格性能小优化系统做好了不能只在 IDEA 里能运行。SWT 项目的打包和普通 Spring Boot 项目思路完全不同最后单独拿出来说。6.1 打包策略保留原生包用 -cp 指定运行很多同学第一次打包 SWT 程序都会踩 5.5 的坑。正确做法是打一个“普通可运行 jar”界面代码打进 jarSWT 的原生 jar 放在外面的 lib 目录再用一个 bat 或 sh 启动。java -cp library-system.jar;lib/* com.example.MainClass说明Windows 用分号分隔 classpathLinux/macOS 用冒号。lib/*会让 JVM 加载 lib 下面所有 jarSWT 的原生 dll 还在它自己的 jar 里由 JNI 在运行时自动释放到临时目录再加载。这套方式最稳定也最容易让接手的人看懂。如果你偏要在 IDE 里点那个绿色三角形运行那当然没问题但交付给别人的时候一定要回到命令行脚本否则对方机器上没有你的 IDEA 配置ClassPath 就丢了。如果非要做一个双击就能跑的 exe常见做法是用 jpackage 或 exe4j 这类工具把 JRE 和程序一起捆绑但要注意 SWT jar 仍然是以外部依赖形式存在不要合并为一个 fat jar。合并后不是不能运行而是会出现偶发的 dll 释放失败问题不值得去赌。6.2 表格虚拟化数据量大时的 SWT.VIRTUAL 模式如果图书量上万每次刷新全量 removeAll 再重插会有些卡顿。SWT 提供了虚拟表格模式只创建可见行对应的 TableItem配合 SetData 监听器按需填充数据。bookTable.setItemCount(books.size()); bookTable.addListener(SWT.SetData, event - { TableItem item (TableItem) event.item; int index bookTable.indexOf(item); Book book books.get(index); item.setText(new String[]{book.getBookName(), book.getAuthor()}); });参数说明setItemCount 告诉表格一共有多少行SWT 内部只为可见行创建 item用户滚动时才对新的 item 触发 SetData 事件。这个模式对内存和创建耗时都很友好但代价是不能直接用 TableItem 自带的方法到处遍历行需要在外面维护 books 列表。图书管理这种几千条的场景其实用不上虚拟表格但如果你的系统以后升级成几万条数据的期刊管理这个切换成本很低值得现在就把刷新逻辑独立出来不要和具体 TableItem 绑定得太死。6.3 交付前自查清单给读者一张交付前的验证清单换一台没装 JDK 的机器用捆绑的 JRE 或已安装的 JRE 双击启动一次验证启动脚本里的 classpath 写法没问题。对抗性测试连续新增 20 本书、删除 20 本书再打开任务管理器的 GDI 对象数量确认不持续增长。高分屏缩放 150% 下跑一轮完整流程确认字体不虚、布局不挤。把数据库密码从代码里挪到外部配置文件并在配置里处理连接断开后的重连。用至少 1000 条真实书目数据做一次全量刷新和按 ISBN 搜索记录响应时间给后面优化留基线。这几条做完系统就可以大大方方交给使用的人了。我经历过的最典型的教训是界面写完就着急打包结果在别人机器上双击没反应一查是 classpath 漏了 SWT 原生 jar。自那以后我每次交付前都会先跑一遍换机测试把“能跑”和“能交”之间的差距补齐。希望这篇笔记能帮你少踩几个我已经踩过的坑做出一个界面像样、运行稳定、真正能落地的 SWT 版图书管理系统。本文还有配套的精品资源点击获取
返回列表