
简介一套基于SWT打造的Java图书管理系统图形化界面项目适合正在学习Java桌面应用开发、GUI设计或JDBC数据库操作的初学者作为练手与参考模板。系统围绕图书、借阅、读者等核心业务展开覆盖图书信息增删改查、借书还书、分类统计等场景界面采用SWT原生控件组合而成。资源压缩包共259个文件主要包含45个Java源代码与107个已编译class文件另有80个ico图标、12个jpg图片支撑界面美化并附带jar依赖库、sql建库脚本、doc说明文档及可执行入口文件整体大小约10.07MB。已有483人学习下载读者可在Eclipse中导入工程直接运行也可通过源码研习SWT的GridLayout布局、Table表格展示、事件监听与对话框交互方法同时结合SQL文件理清数据库表设计和JDBC访问流程为后续构建RCP或JFace高级应用打下基础。1. 图书管理系统用SWT做的JAVA图形化界面不是换皮而是另一种桌面取舍图书管理系统用SWT做的JAVA图形化界面听起来像课程设计里最普通的一类题目但真正动手会发现它与Swing的差别远不止换了个皮肤。SWT在Windows上走的是原生控件接口按钮、表格、输入框的外观由操作系统绘制不像Swing那样全部自绘因此启动更快、观感和日常桌面软件更接近。如果把项目定位为本地运行的简易图书管理工具或想为Eclipse RCP插件开发打基础SWT都是一个值得认真走一遍的方向。接下来按实际开发顺序把环境、布局、事件、数据库联动和打包的要点都过一遍。2. SWT核心生命周期从Display到Shell到资源释放少一步程序就会退出2.1 为什么选SWT而不是Swing或JavaFX技术选型的实际理由先回答一个绕不开的问题同样写Java图形化界面SWT到底赢在哪又输在哪。最核心的区别是底层实现方式。Swing是纯Java自绘控件界面由Java2D绘制所以跨平台一致但总带一点“Java味”SWT通过JNI调用操作系统原生控件接口Windows下按钮、表格、输入框都是系统绘制观感上最接近C# WinForms。JavaFX则走自绘加硬件加速路线样式现代但学习曲线也更陡。做课程设计或小型桌面工具时选型通常不是“哪个更好”而是“哪个更不容易在演示时翻车”。SWT在Windows上基本不会出现“字体渲染发虚”“控件挤在一起”这种观感问题数据表格、树形列表这些控件的交互逻辑也更接近桌面软件。下面是三者的粗略对比方便快速定位维度SWTSwingJavaFX控件实现调用操作系统原生接口纯Java自绘自绘并引入硬件加速界面观感跟随系统主题跨平台统一但偏老旧现代化样式可写CSS学习成本中等需理解资源释放和线程切换低教程多较高概念多典型场景Eclipse插件、本地管理工具课程设计、小型工具数据可视化、富客户端中文资料一般集中在Eclipse周边非常多中等如果目标是“做出一个看起来不太像学生作业的桌面程序”SWT是值得投入的。代价是它在Maven坐标、动态库、打包方面比Swing麻烦这些坑会在后面逐一讲。如果只是应付课设且不想折腾环境Swing仍然是省事的选择但如果想走Eclipse RCP插件开发方向SWT几乎是必修课。2.2 环境准备SWT依赖与最简程序骨架初学者最容易卡住的地方是环境JDK自带的类库里没有SWTIDE里直接写import org.eclipse.swt.widgets.Display;会报ClassNotFoundError。常见做法是去Eclipse官网下载对应平台的SWT二进制包把swt.jar加进项目的LibrariesMaven使用者可以在中央仓库找org.eclipse.swt相关的平台坐标但版本号需要与当前Eclipse发布版本匹配且要让jar包与JDK位数一致。注意不要同时引入多个平台的swt.jar否则运行时动态库加载顺序会乱。很多人用IDEA或Eclipse打开项目后第一件事是写主窗体但SWT程序有个固定骨架缺一块都不行。下面把这个最小骨架拆开讲import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; public class MainWindow { public static void main(String[] args) { Display display new Display(); Shell shell new Shell(display); shell.setText(图书管理系统); shell.setSize(900, 600); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } }这段代码的逻辑很简单new Display()建立Java程序与操作系统的连接new Shell(display)创建主窗口setText和setSize分别设置标题和初始尺寸。真正容易忽略的是后面的while循环——SWT程序必须不停地把系统事件分发给对应控件readAndDispatch()负责取事件sleep()在无事件时让出CPU否则窗口会一直白屏无响应。程序退出时display.dispose()释放底层资源。如果只是拿来做课程设计把主界面控件都写在shell.open()之前再跑这个循环就够了。但有一个细节需要注意如果程序里存在多个Shell窗口比如登录窗和主窗体主窗口关闭时不一定会让整个进程退出这时把循环条件改成while (!display.isDisposed())更稳妥。2.3 生命周期与资源释放为什么dispose不能省SWT的Widget和Swing的JComponent在内存模型上有本质区别。Swing控件只是堆里的Java对象GC能回收SWT的每一个控件都会在操作系统里创建对应的窗口句柄Handle这个句柄不归JVM的GC管。如果只创建控件但不调dispose()句柄会一直留在系统里程序运行时看起来不占多少内存但句柄数持续上涨最后表现为打开窗口越来越慢、界面偶发卡死。这也是SWT最常被吐槽的点。做图书管理系统时登录窗口、新增图书窗口、关于弹窗都会频繁开关。如果只用setVisible(false)隐藏而不是dispose()每开一次就多一组控件句柄。正确做法是窗口用完就dispose()如果还需要复用数据把数据保存到全局变量或对象里不要靠隐藏窗口来“留状态”。另一个生命周期问题是子窗口的父Shell传递。创建对话框时如果误把登录窗口当成父Shell传入登录窗口关闭后子窗口也会跟着销毁。父Shell的作用是维护窗口层级关系不是让控件“挂靠”在父窗口对象里。SWT的子控件生命周期由父Composite管理但独立Shell不是。这一点在第五章排查部分还会再展开。提示SWT依赖要与JDK位数一致。64位JDK对应x86_64平台包32位JDK对应x86包。版本不匹配通常会在创建Display时抛UnsatisfiedLinkError。3. 图书列表主界面把Table和GridLayout用到顺手解决“怎么摆放”的所有问题3.1 先定布局GridLayout的核心参数比Swing的布局更直白图书管理系统一般需要左侧一个图书分类树右侧一个大表格和若干操作按钮。这种结构如果用Swing的BorderLayout也可以做但SWT里更常见的是GridLayout配合GridData。GridLayout按行列排列控件但每个控件具体怎么拉伸由GridData决定。先看主界面骨架的搭建方式Composite mainComposite new Composite(shell, SWT.NONE); GridLayout rootLayout new GridLayout(2, false); rootLayout.horizontalSpacing 8; rootLayout.verticalSpacing 8; rootLayout.marginWidth 8; rootLayout.marginHeight 8; mainComposite.setLayout(rootLayout); mainComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); Tree categoryTree new Tree(mainComposite, SWT.BORDER); categoryTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); Composite rightComposite new Composite(mainComposite, SWT.NONE); rightComposite.setLayout(new GridLayout(1, false)); rightComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));这里new GridLayout(2, false)第一个参数是列数第二个参数表示“是否需要所有列等宽”。false表示列宽按控件内容伸缩分配左侧树窄、右侧内容区宽这正是管理界面想要的。GridData(SWT.FILL, SWT.FILL, true, true)四个参数依次是水平对齐方式、垂直对齐方式、是否抢占水平多余空间、是否抢占垂直多余空间。左侧树给的是false, true表示竖直占满但水平不抢空间右侧Composite给true, true把剩余水平宽度全部接收。SWT布局里最常见的“玄学”是控件不显示或挤在一个小角落里问题往往出在忘了给控件设置setLayoutData。GridLayout下如果某个控件没有GridData它会按自身的preferredSize放到所在格子不拉伸也不扩展看起来就像没放对位置。所以写SWT界面时每new一个控件下一步就应该先考虑它的GridData再写其他业务逻辑。3.2 表格列与TableItem把数据库记录填到界面上图书列表最核心的组件是Table。Table本身不带表头表头由TableColumn负责。创建表格的惯用写法如下Table bookTable new Table(rightComposite, SWT.FULL_SELECTION | SWT.BORDER | SWT.V_SCROLL); bookTable.setHeaderVisible(true); bookTable.setLinesVisible(true); String[] columns {编号, 书名, 作者, ISBN, 价格}; int[] columnWidths {60, 220, 100, 160, 80}; for (int i 0; i columns.length; i) { TableColumn column new TableColumn(bookTable, SWT.NONE); column.setText(columns[i]); column.setWidth(columnWidths[i]); } GridData tableGridData new GridData(SWT.FILL, SWT.FILL, true, true); tableGridData.heightHint 320; bookTable.setLayoutData(tableGridData);SWT.FULL_SELECTION表示点击行时整行高亮这是表格类界面必须的样式否则默认只选中点中的单元格。setHeaderVisible(true)和setLinesVisible(true)分别控制表头和行分隔线。TableColumn创建后默认追加到表格列集合末尾setWidth设置的是像素宽具体多少取决于数据长度如果列总宽超过Table可视区会出现水平滚动条。往表格里填数据用的是TableItem每条记录对应一行for (Book book : bookList) { TableItem item new TableItem(bookTable, SWT.NONE); item.setText(new String[] { String.valueOf(book.getId()), book.getTitle(), book.getAuthor(), book.getIsbn(), book.getPrice() }); item.setData(book, book); }setText(String[])把字符串数组按顺序写到各列。这里有个容易被忽略的技巧item.setData(book, book)可以把整个Book对象挂到TableItem上。后面做删除或编辑时从选中行里取回对象不需要再用索引去列表里查这能省掉很多边界判断。图书量少时直接填TableItem没问题但超过几千条后一次性new几万个TableItem会让界面卡顿。SWT提供了虚拟表格方案给Table加SWT.VIRTUAL样式用bookTable.setItemCount(totalCount)设置总行数再通过addListener(SWT.SetData, ...)在需要显示某一行时才填充数据。多数图书管理系统到不了这个量级但知道这条路存在以后做数据量更大的桌面工具时能少走弯路。3.3 数据更新与线程切换表格刷新的最小套路图书管理系统的数据库查询不能放到UI线程里否则查询期间窗口无法拖动鼠标点击也没反应。常见做法是开一个后台线程做JDBC查询查完再切回UI线程更新表格Thread queryThread new Thread(() - { ListString[] data queryBooksFromDb(); Display.getDefault().asyncExec(() - { if (bookTable.isDisposed()) { return; } bookTable.setRedraw(false); bookTable.removeAll(); for (String[] row : data) { TableItem item new TableItem(bookTable, SWT.NONE); item.setText(row); } bookTable.setRedraw(true); }); }); queryThread.start();queryBooksFromDb()是示意方法实际替换成JDBC查询即可。后台线程里不能直接操作控件否则会抛Invalid thread accessSWT要求所有控件操作必须在创建该控件的UI线程里完成。Display.getDefault().asyncExec做的事情是把Runnable提交到Display事件队列由UI线程稍后执行。这很像Swing里的EventQueue.invokeLater。setRedraw(false)是批量更新表格时的惯用优化。在关闭重绘期间所有TableItem的增删只发生在内存里全部改完再调setRedraw(true)一次性重绘避免几百条数据一行一行刷新导致闪烁。如果查询失败让后台线程捕获异常后通过asyncExec弹MessageBox提示不要再直接操作控件。4. 登录与图书表单SWT事件监听与控件组合写一个能用的增删改查4.1 登录窗口Text、Button与密码输入参数图书管理系统几乎都从登录窗口开始。登录界面控件不多关键是布局和样式参数。一个常见布局是把标签放左列、输入框放右列按钮横跨两列Display display new Display(); Shell loginShell new Shell(display, SWT.TITLE | SWT.CLOSE); loginShell.setText(图书管理 - 登录); loginShell.setLayout(new GridLayout(2, false)); loginShell.setSize(360, 180); Label userLabel new Label(loginShell, SWT.NONE); userLabel.setText(用户名); Text userText new Text(loginShell, SWT.BORDER); userText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); Label passLabel new Label(loginShell, SWT.NONE); passLabel.setText(密码); Text passText new Text(loginShell, SWT.BORDER | SWT.PASSWORD); passText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); Button loginBtn new Button(loginShell, SWT.PUSH); loginBtn.setText(登录); loginBtn.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 2, 1));SWT.PASSWORD让输入框以圆点掩码显示密码SWT.BORDER给控件加边框。GridData(SWT.FILL, SWT.CENTER, true, false)让输入框在水平方向填满列宽垂直方向居中但不抢占额外高度。最后一个登录按钮使用了六参数GridDataSWT.CENTER, SWT.CENTER, false, false, 2, 1其中2, 1表示水平跨两列、垂直跨一行这样按钮就能占满整行的中间位置。登录窗口创建完成后同样需要loginShell.open()和事件循环。为了让主窗口代码结构清晰登录逻辑可以单独放进一个LoginDialog类里验证成功后返回用户名主程序再根据返回值决定是否进入主界面。4.2 事件监听器组合登录验证、搜索过滤、保存新增SWT的事件监听器接口比较多实际使用中很少直接implements Listener因为大部分场景只需要处理一个事件。Button按钮点击用SelectionAdapter最方便它把不需要的方法空实现掉了只留下widgetSelectedloginBtn.addSelectionListener(new SelectionAdapter() { Override public void widgetSelected(SelectionEvent e) { String username userText.getText().trim(); String password passText.getText().trim(); if (admin.equals(username) 123456.equals(password)) { openMainWindow(display); loginShell.dispose(); } else { MessageBox box new MessageBox(loginShell, SWT.ICON_WARNING | SWT.OK); box.setText(登录失败); box.setMessage(用户名或密码错误); box.open(); } } });登录成功后的顺序很重要先打开主窗口再dispose()登录Shell。如果反过来登录Shell已经销毁事件回调里再访问loginShell就会抛Widget is disposed异常。MessageBox是SWT自带对话框SWT.ICON_WARNING | SWT.OK表示警告图标加确定按钮不需要自己new一个窗口来提示。搜索框实时过滤是图书列表很常用的交互。给Text加ModifyListener每次输入变化都触发一次过滤searchText.addModifyListener(e - { String keyword searchText.getText().trim().toLowerCase(); bookTable.setRedraw(false); bookTable.removeAll(); for (Book book : allBooks) { if (book.getTitle().toLowerCase().contains(keyword)) { TableItem item new TableItem(bookTable, SWT.NONE); item.setText(new String[] { String.valueOf(book.getId()), book.getTitle(), book.getAuthor(), book.getIsbn(), book.getPrice() }); item.setData(book, book); } } bookTable.setRedraw(true); });这个过滤操作在内存列表里循环几千条数据没问题。如果数据量大可以在ModifyListener里加一个防抖逻辑用一个标志位或时间戳记录最后一次输入超过200毫秒没变化再执行过滤避免每敲一个字符都全表重扫。保存新增图书的按钮事件同理先从Text控件取值、校验空值再入库并刷新表格saveBtn.addSelectionListener(new SelectionAdapter() { Override public void widgetSelected(SelectionEvent e) { String title titleText.getText().trim(); String author authorText.getText().trim(); if (title.isEmpty()) { MessageBox box new MessageBox(shell, SWT.ICON_WARNING | SWT.OK); box.setMessage(书名不能为空); box.open(); return; } insertBook(title, author, isbnText.getText().trim(), priceText.getText().trim()); refreshTable(); clearForm(); } });trim()必须在取值阶段就做避免把空格当作有效输入。校验失败要return这样后面的数据库操作不会执行。把insertBook、refreshTable、clearForm拆成独立方法监听器会保持很短排查问题时也更容易定位。4.3 对话框与子窗口给新增和编辑窗口加模态新增和编辑图书功能共用同一个表单窗口区别只在标题和初始数据。SWT中这类窗口应该用带模态的Shell实现Shell editShell new Shell(shell, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); editShell.setText(book null ? 新增图书 : 编辑图书); editShell.setLayout(new GridLayout(2, false)); Label titleLabel new Label(editShell, SWT.NONE); titleLabel.setText(书名); Text titleText new Text(editShell, SWT.BORDER); titleText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); // ISBN、作者、价格字段省略写法与书名一致 Button okBtn new Button(editShell, SWT.PUSH); okBtn.setText(保存); okBtn.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false)); Button cancelBtn new Button(editShell, SWT.PUSH); cancelBtn.setText(取消); cancelBtn.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); cancelBtn.addSelectionListener(e - editShell.dispose()); okBtn.addSelectionListener(e - { saveBook(); editShell.dispose(); }); editShell.open();SWT.APPLICATION_MODAL会阻止用户操作父窗口直到这个Shell关闭。注意这里editShell.open()之后代码实际上会继续往下走而不是卡住模态性通过事件循环内部机制实现。取消按钮只需要dispose不需要额外返回值保存按钮先从文本框取值做校验成功后再dispose。这个Shell的dispose要放在主Shell仍然存活的前提下。如果用户直接关闭了主窗口后台还开着新增窗口程序会残留无主窗口进程。因此主窗口关闭时最好把程序里所有子窗口都统一dispose或者直接让主窗口关闭事件触发display.dispose()。5. SWT图形化界面常见问题与排查5个几乎人人都遇到的细节坑5.1 登录窗关闭后主窗体也消失了现象点击登录按钮主窗口打开后立刻闪烁一下整个程序直接退出控制台也没有异常。这类问题在登录型SWT项目里最常出现越查越觉得像鬼打墙。原因主窗体的Shell创建时误把登录Shell传成了父Shell例如new Shell(loginShell, SWT.SHELL_TRIM)。登录Shell执行dispose()后整个窗口层级被拆掉Display事件循环因为登录Shell的销毁而退出主窗体即使已经open也没机会继续处理事件。另一种更隐蔽的情况是登录成功后主窗体open了但事件循环的退出条件仍然写着while (!loginShell.isDisposed())loginShell一dispose循环立即结束。解决创建主窗体时必须用Display作为父级new Shell(display, SWT.SHELL_TRIM)。事件循环条件统一写成while (!display.isDisposed())不要绑定到任何一个具体Shell。登录成功后的操作顺序固定是先mainShell.open()再loginShell.dispose()两步顺序不能反。5.2 表格选中删除时偶发“Widget is disposed”异常现象表格里选中一行点击删除按钮。第一次删除正常第二次或连续快速点击时界面卡住控制台抛出SWTException: Widget is disposed。原因删除操作触发后TableItem已经被dispose但监听器里的局部变量仍然持有这个Item的引用。再次点击时SWT发现旧Item的句柄已失效就抛异常。这本质上是SWT的“使用后必检查dispose”纪律问题。解决删除前先取索引然后从Table重新获取Item再处理数据。正确代码如下int index bookTable.getSelectionIndex(); if (index -1) { return; } TableItem selectedItem bookTable.getItem(index); Book selectedBook (Book) selectedItem.getData(book); bookTable.remove(index); deleteBookFromDb(selectedBook);这里先通过getData(book)把业务对象取出来再删除行最后执行数据库操作。如果业务对象在删除后还需要使用就必须放在remove(index)之前取好。这条规则同样适用于行编辑、批量删除等场景。5.3 高分屏下界面字体和控件比例失衡现象普通分辨率下窗口一切正常换到4K屏幕或Windows缩放设置改成150%之后SWT窗口变得很小文字挤成一团部分按钮显示不全。原因SWT控件尺寸默认按像素计算而Windows开启显示缩放后Java进程默认被系统按虚拟DPI拉伸SWT自身没有参与DPI感知于是出现比例失衡。这个问题在Windows 10/11的高分屏笔记本上非常常见属于SWT跨版本的老问题。解决优先使用GridLayout和GridData的拉伸规则避免写死控件宽度。如果确实需要固定宽度可以使用Display的DPI信息做换算Display display new Display(); int dpi display.getDPI().x; float scale dpi / 96.0f; int realWidth Math.round(180 * scale);部分较新SWT版本支持System.setProperty(swt.enable.autoScale, true)自动缩放但这个属性在不同版本表现不稳定不能把它当作唯一方案。最保险的做法是界面整体用布局和GridData撑开字体大小用相对倍数计算而不是写死像素。程序启动后如果做了DPI调整建议在合适时机调用shell.layout(true)重新计算布局。5.4 从数据库读出来的中文显示成问号现象数据库里的书名、作者在控制台查询输出正常但在SWT表格和Text里变成???或乱码。原因数据库连接URL里没有指定字符编码。常见的MySQL连接串只写了localhost:3306/libraryMySQL驱动默认按系统编码传输读写不一致就把中文弄丢了。解决JDBC连接URL加上编码参数String url jdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8; String user root; String password yourpassword; Connection conn DriverManager.getConnection(url, user, password);如果改动URL后仍然乱码检查数据库建库语句是否用了utf8mb4比如CREATE DATABASE library CHARACTER SET utf8mb4。还有一层隐藏问题程序源码文件本身的编码如果不是UTF-8编译出来的字节码里中文字面量就已经是乱码需要在IDE里把工程编码统一设置为UTF-8。5.5 导出成可执行jar后提示找不到SWT库现象IDE里运行一切正常导出jar后双击运行要么提示找不到主类要么抛UnsatisfiedLinkError或Native code library failed to load。原因直接把swt.jar打进fat jar还不够SWT的native动态库不会自动被Java加载。IDE运行时能加载到swt的动态库是因为IDE的classpath配置里带了native路径脱离IDE后动态库路径丢失程序自然起不来。解决简单演示场景最省事的就是不打包直接在IDE里运行。如果一定要发布常见做法是用jpackage输出应用镜像把SWT的动态库和jar一起打包成原生程序也可以把SWT的dll从swt.jar里提取出来放到程序的lib目录并在启动脚本里通过java -Djava.library.pathlib -jar app.jar指定。不要只把jar发给别人那不是可复现的发布方式。6. 进阶SWT图书管理项目的启动初始化与打包收尾技巧6.1 首次启动自动建库建表演示时最怕“打开程序发现没有数据库必须先手动导入SQL”。常见做法是把建表和预置数据写进程序首次启动自动执行。用JDBC的CREATE TABLE IF NOT EXISTS保证幂等Statement st conn.createStatement(); st.execute(CREATE TABLE IF NOT EXISTS book ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, author VARCHAR(100), isbn VARCHAR(50), price DECIMAL(10,2)));这段SQL可以放在启动类里统一调用也可以做成一个“初始化数据库”按钮让用户在演示前手动触发。自动化建库的好处是别人拿到项目不需要额外配表结构坏处是SQL写得不够兼容时排查问题会更麻烦。建议建表语句保持简单字段名和Java实体类严格对应。6.2 用WindowBuilder降低手写布局成本SWT的手写布局并不难但反复调整GridData参数确实费时间。Eclipse的WindowBuilder支持SWT可视化设计可以拖拽控件、拖动边框调整GridData、切换布局方式适合快速搭界面原型。用的时候注意两点第一WindowBuilder生成的代码有时会包很多层Composite嵌套运行没毛病但阅读负担大定稿后可以手动精简第二生成代码里的监听器是空壳事件逻辑仍然要自己写。我的习惯是先拖出静态界面再手工补事件监听和数据库操作。6.3 把关键操作记入日志文件演示时控制台输出并不可靠窗口一旦全屏就看不到日志。更实用的做法是把登录、新增、删除等关键操作写入本地日志文件用PrintWriter包一层就能用try (FileWriter fw new FileWriter(app.log, true); PrintWriter pw new PrintWriter(fw)) { pw.println([ LocalDateTime.now() ] 用户admin登录系统); pw.println([ LocalDateTime.now() ] 新增图书《活着》作者余华); } catch (IOException ex) { ex.printStackTrace(); }这个习惯帮我避免了很多“演示中途数据没更新难以及时定位问题”的情况。我做第一个SWT图书管理系统时因为登录窗口和主窗口的父Shell传错卡了整整一个下午后来才明白问题出在Shell的创建者是谁。从那以后每写一个新窗口我都会先确认三件事父Shell是谁、事件循环怎么退出、什么时候调dispose。希望这几个排查思路能帮你省下同样的踩坑时间希望帮到你。本文还有配套的精品资源点击获取