一、概述在 LocalChatRoom 局域网聊天室项目中我负责客户端界面与交互层的开发。这一层是用户直接面对的前端承担着登录引导、消息展示、交互操作和状态反馈等全部 UI 职责。我负责的三个核心文件分别是文件职责LoginDialog.java登录对话框服务器地址/端口/昵称输入与校验ChatFrame.java主窗口框架标签页管理、用户列表、私聊路由、状态栏ChatPanel.java聊天面板组件消息渲染、输入发送、表情选择器这三个文件合计约 753 行代码构成了客户端的完整 UI 层。它们通过调用组长提供的 ChatClient.send(Message) 发送消息并通过实现 ChatFrame.handleMessage(Message) 接收并渲染消息以 Message 类为唯一耦合接口实现了前后端分离。二、LoginDialog —— 登录对话框LoginDialog 是用户启动客户端后看到的第一个窗口负责收集连接参数并通过校验后才放行。2.1 核心设计模态对话框继承 JDialog构造时传入 modal true阻塞父窗口直到用户完成或取消登录。布局结构BorderLayout 整体布局NORTH 为标题CENTER 为表单面板GridLayout(3, 2)SOUTH 为操作按钮。默认焦点窗口打开时通过 WindowListener.windowOpened 自动将焦点定位到昵称输入框减少用户鼠标操作。2.2 局域网 IP 自动检测private String detectLocalIP() { EnumerationNetworkInterface nets NetworkInterface.getNetworkInterfaces(); while (nets.hasMoreElements()) { NetworkInterface net nets.nextElement(); if (net.isLoopback() || !net.isUp()) continue; EnumerationInetAddress addrs net.getInetAddresses(); while (addrs.hasMoreElements()) { InetAddress addr addrs.nextElement(); String ip addr.getHostAddress(); // 过滤 IPv6、回环地址、APIPA 自动配置地址 if (ip.contains(:) || ip.startsWith(127.) || ip.startsWith(169.254.)) continue; return ip; } } return localhost; // 兜底 }遍历本机所有网络接口跳过回环接口、已禁用的接口以及 IPv6 和 APIPA 地址169.254.x.x返回第一个有效的局域网 IPv4 地址。检测失败时回退到 localhost兼顾单机测试场景。2.3 昵称校验空值校验昵称为空时弹出警告弹窗并重新聚焦输入框。逗号校验昵称不能包含逗号因为服务端使用逗号分隔在线用户列表昵称含逗号会破坏协议解析。2.4 对外接口提供四个 Getter 方法供 ChatClient 读取用户输入isConfirmed()、getHost()、getPort()、getNickname()。三、ChatFrame —— 主窗口框架ChatFrame 是整个客户端的中枢承担消息路由、标签管理、用户列表和状态反馈等核心职责。3.1 整体布局JSplitPane 左右分割左侧占比约 78%setResizeWeight(1.0)窗口缩放时优先保证聊天区域的空间。右侧用户列表固定首选宽度 185px。3.2 标题栏渐变背景重写 JPanel.paintComponent()使用 GradientPaint 从左到右皇家蓝 → 矢车菊蓝渐变填充配合白色字体。3.3 JTabbedPane 多标签管理标签类型标题可关闭创建方式大厅群聊 大厅否构造时创建始终存在私聊标签对方昵称是双击用户列表或收到私聊消息时自动创建私聊标签关闭按钮通过 buildClosableTabHeader() 为每个私聊标签构建自定义标签头组件关闭按钮鼠标悬停时变红。重复打开保护openPrivateChat(target) 先检查 privatePanels Map 是否已存在该用户的私聊面板存在则直接切换到该标签而非重复创建。3.4 用户列表与双击私聊使用 DefaultListModelString JList自定义 UserCellRenderer每个用户项显示为 ● 昵称 (我)自己的昵称加粗且设为蓝色。双击列表项触发 openPrivateChat(target)含防呆设计不能和自己私聊。3.5 未读角标逻辑大厅未读新群聊消息到达但大厅不是当前活动标签时标题显示 大厅 ●切换回大厅后自动清除。私聊未读通过 ChatPanel 的未读计数回调机制在标签标题中显示数字角标如 Alice [3]且标题文字变红。3.6 断线状态切换setConnected(boolean) 统一管理状态栏从绿色 ● 已连接 切换为红色 ● 已断开所有面板输入框全部禁用。3.7 消息路由handleMessageswitch (msg.getType()) { case TEXT: → hallPanel.receiveMessage(msg); // 群聊 → 大厅 case PRIVATE: → handlePrivateMessage(msg); // 私聊 → 路由到对应面板 case JOIN: → hallPanel.appendStatus(...); // 上线通知 case LEAVE: → hallPanel.appendStatus(...); // 下线通知 case USER_LIST: → updateUserList(msg.getContent()); // 刷新在线列表 case SYSTEM: → hallPanel.appendSystem(...); // 系统消息 }这是消息从网络层进入 UI 层的唯一入口由 ChatClient 的后台接收线程在 EDT 中回调。四、ChatPanel —— 聊天面板ChatPanel 是群聊大厅和私聊共用的核心 UI 组件通过 chatTarget 字段区分模式null 为群聊非 null 为私聊。私聊模式下顶部显示浅蓝色提示条 与 XXX 的私聊 — 消息仅你们可见。4.1 JTextPane 富文本渲染选择 JTextPane StyledDocument实现每条消息的颜色和样式独立控制消息角色发送者颜色内容颜色说明自己发送蓝色30100220蓝色加粗发送他人发送红色2006060黑色——系统消息——灰色斜体前缀 [系统]状态消息绿色——斜体前缀 ►时间戳灰色——小字号11px每次追加消息动态创建新的 Style 对象并通过 doc.insertString() 插入文档末尾最后执行 scrollDown() 将插入符滚动到最新消息处。4.2 输入与发送回车发送inputField.addActionListener(e - doSend())。发送按钮根据 chatTarget 判断创建 TEXT群聊或 PRIVATE私聊消息调用 client.send(msg) 发出随后清空输入框并重新聚焦。发送按钮悬停效果鼠标进入时变亮离开时恢复。4.3 表情选择器非模态 JDialog内含30 个 Emoji以 GridLayout(3, 10) 网格排列。每个按钮使用 Segoe UI Emoji 字体渲染点击后将对应字符追加到输入框末尾并自动关闭选择器。4.4 未读计数回调机制private int unreadCount 0; private Runnable onUnreadChange; // 回调接口 public void receiveMessage(Message msg) { appendChat(msg); if (chatTarget ! null onUnreadChange ! null) { unreadCount; onUnreadChange.run(); // 通知 ChatFrame 刷新标签标题 } }通过 Runnable 回调通知父组件而非直接持有引用降低了耦合度。五、技术难点与亮点5.1 Swing EDT 线程安全Swing 是单线程模型所有 UI 操作必须在 EDT 中执行。消息从网络线程到达后通过 SwingUtilities.invokeLater() 投递到 EDT 才能安全更新 UI本项目通过 ChatClient 在后台接收线程中包装 invokeLater 调用 handleMessage()。5.2 JTextPane 样式控制相比 JTextArea 只能设置全局字体和颜色JTextPane StyledDocument 允许在同一文档内对不同段落的文字设置独立的颜色、字号、粗体和斜体。每追加一条消息需动态创建 Style 对象并管理插入符位置。5.3 标签页动态管理JTabbedPane 默认不提供标签关闭按钮需自定义标签头组件。同时维护 privatePanels HashMap 跟踪已创建的私聊面板关闭标签时既要移除 Swing 组件也要清除 Map 引用防止内存泄漏。5.4 未读角标刷新需在两个维度正确更新消息到达时通过回调通知 ChatFrame切换标签时通过 ChangeListener 清零。大厅标签和私聊标签使用两套不同的角标策略。5.5 局域网 IP 自动检测正确过滤回环地址、IPv6 和 APIPA 地址让大多数用户无需手动输入服务器 IP。5.6 渐变色标题栏与自定义渲染GradientPaint 渐变背景 UserCellRenderer 自定义用户列表外观让纯 Java Swing 应用摆脱原生控件的廉价感。六、总结本次 LocalChatRoom 项目的客户端 UI 层开发让我对 Java Swing 有了系统性的实践理解。最大收获在于组件化思维——将 ChatPanel 设计为群聊和私聊共用的通用组件通过一个 chatTarget 字段区分行为模式避免了两套代码的重复维护以及回调解耦——ChatPanel 通过 Runnable 回调通知父组件而非直接持有引用让组件边界更加清晰。本项目为两人协作完成我负责的界面与交互层代码共 753 行完整实现了登录、群聊、私聊、表情、未读提醒等全部 UI 功能。