news 2026/10/26 9:19:52

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Swing车辆管理系统实战:Swing+Derby桌面应用开发

简介:这是一套基于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文件对照学习编译与反编译实践,快速掌握Swing+MySQL桌面应用开发全流程。

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()更安全——它强制整个表格重绘,避免因行索引错位导致的ArrayIndexOutOfBoundsException;setPreferredWidth()和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),比UNRELATED(0px)更安全,避免视觉粘连;所有JTextField初始化时传入15列宽,这是字符数而非像素,能随字体缩放自适应。


3. 数据库集成:嵌入式 Derby + JDBC 封装,零配置启动

本系统采用 Apache Derby 作为嵌入式数据库——它无需独立服务进程、不占额外端口、JAR 包仅 2.8MB,且完全兼容 JDBC 4.2 标准。所有 SQL 操作封装在VehicleDAO类中,避免在 GUI 代码里混杂 SQL 字符串。

3.1 Derby 初始化与建表脚本

首次运行时自动创建数据库目录和表结构。关键在于jdbc:derby:URL 中的create=true参数:

public class DatabaseManager { private static final String DB_URL = "jdbc:derby:vehicle_db;create=true"; 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,而是返回List<Vehicle>对象集合,所有 SQL 使用PreparedStatement防注入:

public class VehicleDAO { public List<Vehicle> findAll() throws SQLException { String sql = "SELECT * FROM vehicles ORDER BY last_update DESC"; List<Vehicle> 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 自带jpackage(JDK 14+)生成 Windows 原生安装包,彻底解决 JRE 依赖问题。

4.1 构建可执行 JAR:MANIFEST.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 14+)

jpackage是 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 和依赖的目录;
  • --icon:Windows 程序图标,必须是.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;create=true";

提示: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.uiScale=1.0 -Dswing.aatext=true -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-ooxml(10MB+):

// 右键菜单初始化 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]); } // 写入数据 List<Vehicle> 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启用自动补全(无需第三方库):

JComboBox<String> 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 Handler

Swing 的 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模型与视图行号未转换。没有这个日志,你只能靠猜。

希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 9:19:24

酒店管理系统设计与实现:Spring Boot后端开发全流程指南

简介&#xff1a;这是一份完整的酒店管理系统毕业设计项目资源&#xff0c;面向 Java 初学者、高校毕设及课程设计人群&#xff0c;内容整合了 JAVA 源码、SQL 数据库脚本、论文文档和辅导视频&#xff0c;覆盖客房预订、住店管理、餐饮等典型业务模块。压缩包共 12 个文件&…

作者头像 李华
网站建设 2026/10/26 9:19:23

Unity Shader Graph中BranchOnInputConnection节点原理与应用

1. BranchOnInputConnection节点核心原理剖析 在Unity Shader Graph中&#xff0c;BranchOnInputConnection是一个常被忽视却极为强大的逻辑控制节点。这个节点的独特之处在于它能根据输入端口是否连接来自动切换执行路径&#xff0c;相当于Shader中的条件分支语句&#xff0c;…

作者头像 李华
网站建设 2026/10/26 9:19:22

PAIR诊断法:用反事实延续定位上下文压缩问题

1. 从“无线诊断”误报说起&#xff1a;为什么我们总在长程任务里丢掉关键上下文你有没有遇到过这种场景&#xff1a;智能体正在处理一个需要跨十几轮对话、调用三次外部API、查阅两份历史文档的复杂任务&#xff0c;突然在第8轮毫无征兆地把用户三天前明确拒绝的方案又提了出来…

作者头像 李华
网站建设 2026/10/26 9:19:13

从脚本到AI Agent:自动化运维的技术演进与实践

1. 从脚本到AI Agent的技术演进 十年前我刚入行时&#xff0c;每天的工作就是写各种Bash和Python脚本。从简单的日志分析到复杂的CI/CD流程&#xff0c;脚本确实帮我解决了90%的自动化需求。但最近两年&#xff0c;随着AI Agent技术的成熟&#xff0c;我的工具箱发生了根本性变…

作者头像 李华
网站建设 2026/10/26 9:17:43

WebGL端AVProVideo播放海康监控实时画面可行性Demo

简介&#xff1a;一份面向 Unity 开发者与流媒体监控集成场景的 WebGL 播放示例源码&#xff0c;围绕 AVProVideo&#xff08;无水印版本&#xff09;在浏览器端呈现海康设备实时画面展开。工程重写了 Http 请求模块&#xff0c;并将发布 WebGL 所需的设置整理为可参照的配置文…

作者头像 李华
网站建设 2026/10/26 9:17:29

中文语音识别完整工程:PyTorch实现ASR训练-推理-评估闭环

简介&#xff1a;这是一套面向Python开发者与语音技术学习者的中文语音识别实战源码&#xff0c;聚焦深度学习在ASR领域的落地实现&#xff0c;适用于智能助手、语音控制系统等中文语音交互场景。资源共47个文件&#xff0c;含24个核心Python脚本&#xff08;涵盖数据加载、声学…

作者头像 李华