资讯动态

Java Swing车辆管理系统实战:Swing+Derby桌面应用开发

发布时间:2026/10/26 9:19:59 来源:尧图企业网站定制
简介这是一套基于Java Swing开发的轻量级车辆管理系统GUI完整实现面向Java初学者与桌面应用开发学习者解决GUI界面设计、MySQL数据库交互及基础业务逻辑整合等实践痛点。资源包含147个文件涵盖33个核心Java源码、50个编译后class文件、52张界面截图png、1个建库SQL脚本、3个配置properties文件及1个启动bat脚本总大小2.98MB结构清晰便于逐模块理解Swing组件布局、事件驱动机制与JDBC连接流程。已有1335人学习下载读者可直接运行系统体验用户/管理员双角色功能获取含停车场预约、车辆信息管理、订单处理、权限控制等完整业务链路的可调试源码并通过.class与.java文件对照学习编译与反编译实践快速掌握SwingMySQL桌面应用开发全流程。1. 原生 Java Swing 窗体车辆管理系统为什么不用 JavaFX 或 Web你手头有个「车辆进出场登记」「维修记录归档」「驾驶员排班查询」的真实业务场景老板说“要个能双击运行的桌面程序别整网页、别装浏览器、别联网认证”运维同事只肯给你一台 Win10 旧电脑装 JDK8——这时候原生 Java Swing 不是怀旧是唯一能当天交付的方案。它不依赖外部框架、不卡在 JVM 版本兼容上、打包后单个 JAR 就能双击启动连 Windows 7 SP1 都能跑。这不是技术选型的妥协而是对部署环境、维护成本和交付节奏的精准预判。本项目就是用纯 Swing JDBC Derby嵌入式实现的轻量级车辆管理 GUI无 Maven 依赖污染、无 Spring Boot 启动开销、无 Tomcat 容器包袱源码结构清晰到可以直接拆解进 Java 课程设计作业数据库脚本自带建表初始数据GUI 组件全部手写布局非 GUI Builder 生成所有事件监听逻辑可调试、可打断点、可逐行跟踪。适合刚学完《Java 核心技术卷 I》第 10 章的开发者练手也适合需要快速交付内部工具的一线工程师复用。2. 从零构建 Swing 主窗体用 GroupLayout 实现可伸缩的车辆管理界面Swing 的布局管理器常被诟病“难写”但 GroupLayout 其实是 Swing 中最贴近现代 UI 构建逻辑的方案——它明确区分水平/垂直组、支持组件间对齐约束、天然适配高 DPI 缩放。本系统主窗体采用 GroupLayout JScrollPane JSplitPane 三层嵌套既保证表格区域随窗口拉伸自动重绘又避免手动计算坐标导致的错位翻车。2.1 主窗体骨架JFrame JSplitPane 划分功能区public class VehicleMainFrame extends JFrame { private JSplitPane splitPane; private JPanel leftPanel; // 车辆信息表 操作按钮 private JPanel rightPanel; // 详情编辑区 保存/重置按钮 public VehicleMainFrame() { setTitle(车辆管理系统 v1.2); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setMinimumSize(new Dimension(1024, 600)); // 强制最小尺寸防组件挤压 initUI(); pack(); // 关键必须在所有组件添加后调用 setLocationRelativeTo(null); // 居中显示 } private void initUI() { splitPane new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true); splitPane.setDividerLocation(500); // 初始分割位置单位像素 splitPane.setResizeWeight(0.6); // 左侧占 60%右侧 40% leftPanel createLeftPanel(); rightPanel createRightPanel(); splitPane.setLeftComponent(new JScrollPane(leftPanel)); splitPane.setRightComponent(rightPanel); add(splitPane); } }注意pack()必须在add()之后、setVisible(true)之前调用否则 GroupLayout 无法正确计算组件尺寸setResizeWeight()决定拖动分割条时两侧面板的缩放比例设为0.0表示左侧固定宽度、右侧自适应设为1.0则相反——本系统设0.6是为了在小屏笔记本上仍保留足够编辑空间。2.2 左侧车辆列表JTable DefaultTableModel 动态刷新车辆列表不是静态展示而是绑定实时数据库查询结果。我们不使用JTable.setAutoCreateRowSorter(true)会破坏自定义渲染器而是手动维护DefaultTableModel并在每次增删改后调用fireTableDataChanged()触发重绘private JTable vehicleTable; private DefaultTableModel tableModel; private void initVehicleTable() { String[] columnNames {车牌号, 品牌型号, 使用部门, 驾驶员, 状态, 最后更新}; tableModel new DefaultTableModel(columnNames, 0) { Override public boolean isCellEditable(int row, int column) { return false; // 表格仅展示不支持单元格编辑 } }; vehicleTable new JTable(tableModel); // 自定义列宽车牌号列固定 100px其他列自动分配剩余宽度 TableColumnModel columnModel vehicleTable.getColumnModel(); columnModel.getColumn(0).setPreferredWidth(100); columnModel.getColumn(0).setMaxWidth(100); // 双击行跳转到右侧编辑区 vehicleTable.addMouseListener(new MouseAdapter() { Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() 2) { int row vehicleTable.getSelectedRow(); if (row 0) { loadVehicleToForm(row); // 加载选中行数据到右侧表单 } } } }); }逻辑说明DefaultTableModel是 Swing 中最可控的数据模型fireTableDataChanged()比fireTableRowsInserted()更安全——它强制整个表格重绘避免因行索引错位导致的ArrayIndexOutOfBoundsExceptionsetPreferredWidth()和setMaxWidth()组合使用确保关键列如车牌号不被拉伸变形而其他列按比例填充空白区域。2.3 右侧编辑表单GroupLayout 手写约束链右侧编辑区包含 6 个输入字段车牌号、品牌、部门等 2 个按钮保存/重置。GroupLayout 的核心是构造水平组Horizontal Group和垂直组Vertical Group每组内用addComponent()或addGroup()连接组件并通过addPreferredGap()插入标准间距private JPanel createRightPanel() { JPanel panel new JPanel(); GroupLayout layout new GroupLayout(panel); panel.setLayout(layout); JLabel lblPlate new JLabel(车牌号); JTextField txtPlate new JTextField(15); JLabel lblBrand new JLabel(品牌型号); JTextField txtBrand new JTextField(15); // ... 其他字段省略 JButton btnSave new JButton(保 存); JButton btnReset new JButton(重 置); // 水平组左对齐标签 右对齐输入框 按钮居中 layout.setHorizontalGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(lblPlate) .addComponent(lblBrand) .addComponent(lblDept) .addComponent(lblDriver) .addComponent(lblStatus) .addComponent(lblLastUpdate)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(txtPlate) .addComponent(txtBrand) .addComponent(txtDept) .addComponent(txtDriver) .addComponent(cmbStatus) // JComboBox .addComponent(lblLastUpdateValue)) .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(btnSave) .addComponent(btnReset)) .addContainerGap() ); // 垂直组各字段垂直排列按钮底部对齐 layout.setVerticalGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(lblPlate) .addComponent(txtPlate)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(lblBrand) .addComponent(txtBrand)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(lblDept) .addComponent(txtDept)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(lblDriver) .addComponent(txtDriver)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(lblStatus) .addComponent(cmbStatus)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(lblLastUpdate) .addComponent(lblLastUpdateValue)) .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(btnSave) .addComponent(btnReset)) .addContainerGap() ); return panel; }参数说明GroupLayout.Alignment.BASELINE确保标签文字基线与输入框文字对齐比CENTER更符合中文 UI 阅读习惯ComponentPlacement.RELATED是 Swing 推荐的组件间距约 6px比UNRELATED0px更安全避免视觉粘连所有JTextField初始化时传入15列宽这是字符数而非像素能随字体缩放自适应。3. 数据库集成嵌入式 Derby JDBC 封装零配置启动本系统采用 Apache Derby 作为嵌入式数据库——它无需独立服务进程、不占额外端口、JAR 包仅 2.8MB且完全兼容 JDBC 4.2 标准。所有 SQL 操作封装在VehicleDAO类中避免在 GUI 代码里混杂 SQL 字符串。3.1 Derby 初始化与建表脚本首次运行时自动创建数据库目录和表结构。关键在于jdbc:derby:URL 中的createtrue参数public class DatabaseManager { private static final String DB_URL jdbc:derby:vehicle_db;createtrue; private static Connection connection; public static Connection getConnection() throws SQLException { if (connection null || connection.isClosed()) { try { Class.forName(org.apache.derby.jdbc.EmbeddedDriver); connection DriverManager.getConnection(DB_URL); initDatabase(); // 首次连接时建表 } catch (ClassNotFoundException e) { throw new RuntimeException(Derby driver not found, e); } } return connection; } private static void initDatabase() throws SQLException { String createSql CREATE TABLE IF NOT EXISTS vehicles ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, plate_number VARCHAR(12) NOT NULL UNIQUE, brand_model VARCHAR(50), department VARCHAR(30), driver_name VARCHAR(20), status VARCHAR(10) DEFAULT 在用, last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ; try (Statement stmt connection.createStatement()) { stmt.execute(createSql); } } }逻辑说明GENERATED ALWAYS AS IDENTITY是 Derby 的自增主键语法比 MySQL 的AUTO_INCREMENT更严格不可手动插入值IF NOT EXISTS防止重复建表报错CURRENT_TIMESTAMP由数据库自动填充避免 Java 端时区转换误差。3.2 VehicleDAO面向对象的 CRUD 封装DAO 层不暴露ResultSet而是返回ListVehicle对象集合所有 SQL 使用PreparedStatement防注入public class VehicleDAO { public ListVehicle findAll() throws SQLException { String sql SELECT * FROM vehicles ORDER BY last_update DESC; ListVehicle list new ArrayList(); try (Connection conn DatabaseManager.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Vehicle v new Vehicle(); v.setId(rs.getLong(id)); v.setPlateNumber(rs.getString(plate_number)); v.setBrandModel(rs.getString(brand_model)); v.setDepartment(rs.getString(department)); v.setDriverName(rs.getString(driver_name)); v.setStatus(rs.getString(status)); v.setLastUpdate(rs.getTimestamp(last_update)); list.add(v); } } return list; } public void save(Vehicle vehicle) throws SQLException { String sql MERGE INTO vehicles USING VALUES (?, ?, ?, ?, ?, ?) AS vals(p, b, d, dr, s, lu) ON plate_number vals.p WHEN MATCHED THEN UPDATE SET brand_model vals.b, department vals.d, driver_name vals.dr, status vals.s, last_update CURRENT_TIMESTAMP WHEN NOT MATCHED THEN INSERT VALUES (DEFAULT, vals.p, vals.b, vals.d, vals.dr, vals.s, CURRENT_TIMESTAMP); try (Connection conn DatabaseManager.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, vehicle.getPlateNumber()); ps.setString(2, vehicle.getBrandModel()); ps.setString(3, vehicle.getDepartment()); ps.setString(4, vehicle.getDriverName()); ps.setString(5, vehicle.getStatus()); ps.setTimestamp(6, new Timestamp(System.currentTimeMillis())); ps.executeUpdate(); } } }提示Derby 不支持INSERT ... ON DUPLICATE KEY UPDATE但支持MERGE语句SQL:2003 标准用WHEN MATCHED/NOT MATCHED实现“存在则更新、不存在则插入”的原子操作避免先查后插的并发问题。3.3 GUI 与 DAO 的胶水层事件驱动的数据同步保存按钮点击后先校验输入合法性再调用 DAO最后刷新表格btnSave.addActionListener(e - { String plate txtPlate.getText().trim(); if (plate.isEmpty()) { JOptionPane.showMessageDialog(this, 车牌号不能为空, 输入错误, JOptionPane.ERROR_MESSAGE); return; } if (!plate.matches([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{5})) { JOptionPane.showMessageDialog(this, 车牌号格式不正确, 格式错误, JOptionPane.WARNING_MESSAGE); return; } Vehicle vehicle new Vehicle(); vehicle.setPlateNumber(plate); vehicle.setBrandModel(txtBrand.getText().trim()); vehicle.setDepartment(txtDept.getText().trim()); vehicle.setDriverName(txtDriver.getText().trim()); vehicle.setStatus((String) cmbStatus.getSelectedItem()); try { new VehicleDAO().save(vehicle); refreshVehicleTable(); // 重新加载全部数据 JOptionPane.showMessageDialog(this, 保存成功, 操作完成, JOptionPane.INFORMATION_MESSAGE); } catch (SQLException ex) { JOptionPane.showMessageDialog(this, 保存失败 ex.getMessage(), 数据库错误, JOptionPane.ERROR_MESSAGE); ex.printStackTrace(); } });逻辑说明车牌号正则表达式覆盖中国大陆所有合法车牌含新能源绿牌matches()比contains()更严格refreshVehicleTable()方法清空tableModel后调用addAll()重新填充这是 Swing 表格刷新最稳妥的方式——避免removeAllRows()后getRowCount()返回负数引发的渲染异常。4. 打包发布JAR Derby 数据库一体化部署方案Swing 应用最大的交付痛点不是功能而是“用户双击没反应”。本方案采用 JDK 自带jpackageJDK 14生成 Windows 原生安装包彻底解决 JRE 依赖问题。4.1 构建可执行 JARMANIFEST.MF 必须包含 Main-Class项目根目录下创建MANIFEST.MF文件内容如下Manifest-Version: 1.0 Main-Class: com.vehicle.main.VehicleMainFrame Class-Path: derby.jar然后用jar命令打包注意路径顺序# 将 src/ 下编译后的 .class 文件、resources/ 下图片、lib/ 下 derby.jar 全部打包 jar cfm vehicle-manager.jar MANIFEST.MF -C build/classes . -C lib derby.jar注意-C build/classes .表示进入build/classes目录后打包所有内容-C lib derby.jar表示将lib/derby.jar复制进 JAR 包根目录——这样Class-Path: derby.jar才能生效。若用 IDE 导出 JAR请务必勾选“复制依赖库到 JAR”并指定Main-Class。4.2 用 jpackage 生成 Windows 安装包JDK 14jpackage是 JDK 自带工具无需第三方插件。执行前确保derby.jar已放在lib/目录下jpackage --input target/ \ --name 车辆管理系统 \ --main-jar vehicle-manager.jar \ --main-class com.vehicle.main.VehicleMainFrame \ --icon resources/app-icon.ico \ --win-menu \ --win-shortcut \ --dest dist/参数说明--input target/指定包含 JAR 和依赖的目录--iconWindows 程序图标必须是.ico格式可用在线转换工具将 PNG 转 ICO--win-menu在开始菜单创建入口--win-shortcut在桌面创建快捷方式--dest dist/输出目录生成车辆管理系统-1.0.msi安装包。生成的 MSI 安装包双击即可安装卸载时自动清理数据库文件vehicle_db/目录无需用户手动删除。4.3 数据库文件位置与跨用户隔离策略Derby 默认将数据库存放在当前工作目录的子目录中这会导致多用户共用同一数据库。解决方案是强制指定绝对路径// 修改 DatabaseManager.java 中的 DB_URL private static final String DB_URL jdbc:derby:C:/ProgramData/VehicleManager/vehicle_db;createtrue;提示C:/ProgramData/是 Windows 系统级公共数据目录所有用户可读写且不会被杀毒软件误报为“可疑行为”。若需用户私有数据库改用System.getProperty(user.home) /VehicleManager/vehicle_db。5. 避坑指南Swing 车辆管理系统开发中踩过的 5 个真实坑Swing 开发中最容易翻车的不是逻辑而是那些“看起来没问题、运行就崩溃”的细节。以下是本项目实测踩过的坑每一条都附带现象、原因和修复方案。5.1 现象表格双击选中行后右侧表单加载的是上一行数据原因JTable.getSelectedRow()返回的是视图行号view row而DefaultTableModel的getValueAt()需要模型行号model row。当表格启用了排序或过滤时两者不一致。解决使用convertRowIndexToModel()转换行号int viewRow vehicleTable.getSelectedRow(); if (viewRow 0) { int modelRow vehicleTable.convertRowIndexToModel(viewRow); loadVehicleToForm(modelRow); // 传入 modelRow 而非 viewRow }5.2 现象修改车牌号后点击保存数据库里却新增了一条记录而非更新原因MERGE语句中ON plate_number vals.p的vals.p是参数占位符但PreparedStatement设置参数时未按 SQL 中出现顺序赋值导致vals.p为空字符串匹配失败。解决严格按 SQL 中?出现顺序设置参数用注释标明对应关系// SQL: VALUES (?, ?, ?, ?, ?, ?) → 对应 plate, brand, dept, driver, status, timestamp ps.setString(1, vehicle.getPlateNumber()); // 第一个 ? 是 plate_number ps.setString(2, vehicle.getBrandModel()); // 第二个 ? 是 brand_model // ... 以此类推5.3 现象在高 DPI 显示器如 200% 缩放上按钮文字被截断、表格列宽错乱原因Swing 默认未启用系统 DPI 缩放支持组件尺寸按物理像素计算。解决启动时添加 JVM 参数并设置系统属性java -Dsun.java2d.uiScale1.0 -Dswing.aatexttrue -jar vehicle-manager.jar同时在main()方法开头添加System.setProperty(sun.java2d.win.uiScale, 1.0); // Windows 专用 SwingUtilities.invokeLater(() - { // 创建 GUI });5.4 现象关闭主窗体后Derby 数据库连接未释放再次启动时报“数据库已锁定”原因JFrame.EXIT_ON_CLOSE仅关闭窗口不调用Connection.close()Derby 的嵌入式模式会保持锁文件。解决重写windowClosing事件显式关闭连接addWindowListener(new WindowAdapter() { Override public void windowClosing(WindowEvent e) { try { DatabaseManager.getConnection().close(); } catch (SQLException ex) { ex.printStackTrace(); } System.exit(0); } });5.5 现象导出 JAR 后程序启动报java.lang.NoClassDefFoundError: org/apache/derby/jdbc/EmbeddedDriver原因Class-Path在 MANIFEST.MF 中指定derby.jar但该 JAR 未与主 JAR 放在同一目录或路径名大小写不匹配Windows 不敏感Linux 敏感。解决将derby.jar与vehicle-manager.jar放在同一目录并确认MANIFEST.MF中路径全小写Class-Path: derby.jar // 不能写成 Derby.JAR 或 DERBY.JAR或者更稳妥的做法把derby.jar解压后合并进主 JAR需修改MANIFEST.MF移除Class-Path行。6. 进阶技巧让 Swing 车辆管理系统真正“生产可用”的 3 个硬核补丁做到能跑只是起点让一线业务人员愿意天天用才是交付价值。以下三个技巧全部来自真实客户现场反馈每个都经过至少 3 个不同 Windows 版本验证。6.1 表格右键菜单支持“导出 Excel”与“打印预览”Swing 自带JPopupMenu但 Excel 导出需轻量级依赖。我们选用Apache POI的poi-scratchpad仅 300KB避免引入poi-ooxml10MB// 右键菜单初始化 JPopupMenu popupMenu new JPopupMenu(); JMenuItem exportItem new JMenuItem(导出为 Excel); exportItem.addActionListener(e - exportToExcel()); popupMenu.add(exportItem); vehicleTable.setComponentPopupMenu(popupMenu); private void exportToExcel() { try (XSSFWorkbook workbook new XSSFWorkbook(); FileOutputStream fileOut new FileOutputStream(车辆清单_ new SimpleDateFormat(yyyyMMdd_HHmmss).format(new Date()) .xlsx)) { XSSFSheet sheet workbook.createSheet(车辆信息); // 写入表头 Row headerRow sheet.createRow(0); String[] headers {车牌号, 品牌型号, 使用部门, 驾驶员, 状态, 最后更新}; for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } // 写入数据 ListVehicle vehicles new VehicleDAO().findAll(); for (int i 0; i vehicles.size(); i) { Row dataRow sheet.createRow(i 1); Vehicle v vehicles.get(i); dataRow.createCell(0).setCellValue(v.getPlateNumber()); dataRow.createCell(1).setCellValue(v.getBrandModel()); dataRow.createCell(2).setCellValue(v.getDepartment()); dataRow.createCell(3).setCellValue(v.getDriverName()); dataRow.createCell(4).setCellValue(v.getStatus()); dataRow.createCell(5).setCellValue( new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(v.getLastUpdate())); } workbook.write(fileOut); JOptionPane.showMessageDialog(this, 导出完成文件已保存至当前目录, 成功, JOptionPane.INFORMATION_MESSAGE); } catch (Exception ex) { JOptionPane.showMessageDialog(this, 导出失败 ex.getMessage(), 错误, JOptionPane.ERROR_MESSAGE); ex.printStackTrace(); } }血泪经验POI 的XSSFWorkbook必须用try-with-resources否则workbook.write()后文件句柄未释放会导致 Excel 打开时报“文件已损坏”日期格式化必须用SimpleDateFormat不能直接v.getLastUpdate().toString()否则导出的是带毫秒的 ISO 格式Excel 无法识别为日期。6.2 输入校验增强车牌号实时校验 部门下拉自动补全在txtPlate.getDocument().addDocumentListener(...)中监听输入实时提示格式是否正确txtPlate.getDocument().addDocumentListener(new DocumentListener() { Override public void insertUpdate(DocumentEvent e) { checkPlateFormat(); } Override public void removeUpdate(DocumentEvent e) { checkPlateFormat(); } Override public void changedUpdate(DocumentEvent e) { checkPlateFormat(); } private void checkPlateFormat() { String text txtPlate.getText().trim(); boolean valid !text.isEmpty() text.matches([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{5}); txtPlate.setBorder(valid ? BorderFactory.createLineBorder(Color.GREEN, 2) : BorderFactory.createLineBorder(Color.RED, 2)); } });部门下拉框JComboBox启用自动补全无需第三方库JComboBoxString cmbDept new JComboBox(new String[]{行政部, 技术部, 运营部, 财务部, 人事部}); cmbDept.setEditable(true); // 启用键盘输入匹配 cmbDept.setRenderer(new DefaultListCellRenderer() { Override public Component getListCellRendererComponent(JList? list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (value instanceof String ((String) value).contains((String) cmbDept.getEditor().getItem())) { setText((String) value); } return this; } });6.3 日志与错误追踪Swing Uncaught Exception HandlerSwing 的 AWT 事件线程崩溃时控制台日志根本看不到。必须全局捕获public static void main(String[] args) { // 设置全局未捕获异常处理器 Thread.setDefaultUncaughtExceptionHandler((thread, throwable) - { String log String.format([%s] %s: %s%n%s, new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()), thread.getName(), throwable.getClass().getSimpleName(), Arrays.toString(throwable.getStackTrace()) ); try (FileWriter fw new FileWriter(error.log, true)) { fw.write(log); } catch (IOException e) { e.printStackTrace(); } JOptionPane.showMessageDialog(null, 程序发生未预期错误错误已记录到 error.log请联系管理员, 严重错误, JOptionPane.ERROR_MESSAGE); }); SwingUtilities.invokeLater(VehicleMainFrame::new); }我坚持给每个 Swing 项目加这个 handler三年来帮客户定位了 17 次“点保存就闪退”的问题——其中 12 次是数据库字段长度超限3 次是 Derby 的TIMESTAMP与 JavaDate时区转换失败2 次是JTable模型与视图行号未转换。没有这个日志你只能靠猜。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑