JavaSwingGUI从小白到大神-5
背景
首先本文是一个系列的文章,起名为《Java Swing GUi从小白到大神》,主要介绍Java Swing相关概念和实践应用。目前JavaSwingGUI很少作为界面的开发框架,但是对于学习Java体系,以及新手将来学习后端之后,能快速从前后端整体去理解一套系统是非常有帮助的,所以是很有必要学习和掌握的。本篇是系列博文的第五篇
,若想详细学习请点击首篇博文开始,让我们开始吧。
文章概览
- Swing与并发
- Swing模型架构
- Swing的气体特性
15. Swing与并发
先了解一下核心类SwingUtilities
是一个实用类,包含与 Swing GUI 相关的静态方法,以下是其三个常用方法及说明:
SwingUtilities
类的 3 个常用方法
-
invokeLater(Runnable doRun)
-
作用:将一个任务(
Runnable
)调度到 事件分派线程(EDT) 上执行。 -
用途:确保所有的 GUI 操作在 EDT 上运行,避免线程安全问题。
-
示例:
-
-
invokeAndWait(Runnable doRun)
-
作用:将一个任务调度到 EDT 上执行,并等待该任务完成。
-
用途:用于需要在后台线程中执行某些 GUI 操作并确保同步完成。
-
注意:如果当前线程已经是 EDT,调用此方法会导致
IllegalStateException
。 -
示例:
-
-
isEventDispatchThread()
-
作用:检查当前线程是否是 EDT。
-
用途:在执行线程相关逻辑前验证线程类型,避免非线程安全操作。
-
示例:
-
SwingWorker
类的方法及说明
SwingWorker
是一个抽象类,用于在后台线程中执行耗时任务,同时支持在事件分派线程中更新 GUI。
常用方法:
-
doInBackground()
-
作用:定义后台任务的主逻辑。
-
运行线程:后台线程。
-
返回值:任务完成后返回的结果(
SwingWorker
的泛型参数V
)。 -
示例:
-
-
publish(V... chunks)
-
作用:在后台线程中发布中间结果。
-
运行线程:后台线程。
-
用途:将中间结果发送给
process()
方法。 -
示例:
1
publish(currentProgress);
-
-
process(List<V> chunks)
-
作用:接收由
publish()
方法发送的中间结果。 -
运行线程:事件分派线程(EDT)。
-
用途:用于更新 GUI。
-
示例:
-
-
done()
-
作用:后台任务完成后调用。
-
运行线程:事件分派线程(EDT)。
-
用途:完成任务后的清理或结果处理。
-
示例:
-
-
setProgress(int progress)
-
作用:设置任务进度(范围 0-100)。
-
运行线程:后台线程。
-
用途:用于更新
progress
属性,并通过监听器通知。 -
示例:
1
setProgress(50);
-
-
get()
-
作用:获取
doInBackground()
返回的结果。 -
运行线程:任何线程。
-
用途:阻塞线程直到任务完成,适用于获取计算结果。
-
示例:
-
-
execute()
-
作用:启动后台任务。
-
运行线程:调用线程。
-
用途:创建任务后,调用此方法开始执行。
-
示例:
1
myWorker.execute();
-
-
cancel(boolean mayInterruptIfRunning)
-
作用:取消任务。
-
运行线程:任何线程。
-
用途:设置任务为取消状态,可选择是否中断正在运行的任务。
-
示例:
1
myWorker.cancel(true);
-
-
isCancelled()
-
作用:检查任务是否已被取消。
-
运行线程:任何线程。
-
用途:在后台任务中动态检测取消状态。
-
示例:
1
if (isCancelled()) break;
-
-
addPropertyChangeListener(PropertyChangeListener listener)
-
作用:添加属性变化监听器,监听
progress
或state
的变化。 -
用途:实现动态界面更新。
-
示例:
-
事件分派线程
(Event Dispatch Thread, 简称 EDT)
是 Swing 的核心线程之一,它专门负责处理事件队列中的任务,例如用户交互事件(鼠标点击、键盘输入等)、界面更新、绘制组件等。
事件分派线程的特点:
- 唯一性:在运行时,应用程序中只有一个事件分派线程,它是单一的线程模型。
- Swing 操作必须在 EDT 上执行:所有涉及到 Swing 组件的操作(例如更新界面、添加组件)都需要在 EDT 上执行。这是因为 Swing 不是线程安全的,多线程访问可能导致 UI 渲染异常或线程竞争问题。
- 自动启动:当 Swing 应用程序启动时(例如通过
SwingUtilities.invokeLater
或显示JFrame
),EDT 会自动创建。
事件分派线程的工作流程:
- 任务分派:EDT 维护一个任务队列,任务可以通过
SwingUtilities.invokeLater()
或SwingUtilities.invokeAndWait()
提交到队列中。 - 任务处理:EDT 按顺序处理队列中的任务。
- 用户事件处理:当用户触发事件(例如鼠标点击、按键),这些事件会被放入任务队列,由 EDT 依次处理。
如何检测当前线程是否是事件分派线程:
可以通过 SwingUtilities.isEventDispatchThread()
方法检查当前线程是否为事件分派线程。
示例:
如何确保代码运行在事件分派线程上:
如果代码需要操作 Swing 组件但可能运行在非 EDT 的线程中,应该使用以下方法将任务调度到 EDT:
-
SwingUtilities.invokeLater(Runnable)
-
将任务提交给 EDT 异步执行,不阻塞调用线程。
-
示例:
-
-
SwingUtilities.invokeAndWait(Runnable)
-
将任务提交给 EDT 并等待其完成(阻塞调用线程)。
-
示例:
-
代码swing与多线程
|
|
16. Swing模型架构
Swing模型架构描述:
Swing 是基于 Model-View-Controller (MVC) 架构设计的轻量级 GUI 框架,它将数据模型、显示和用户交互逻辑分离,使组件更加灵活和可扩展。以下是 Swing 模型架构的主要概念和重点内容:
1. Swing 的 MVC 模型
Swing 中的大多数组件都基于一个简化的 MVC 模型,称为 “可分离的模型架构”。以下是其核心组成部分:
-
Model(数据模型)
数据模型用于存储组件的数据和状态,并提供修改和访问数据的方法。- 负责数据管理。
- 通过监听器模式通知视图和控制器数据的变化。
- 组件的数据模型可以是默认实现(如
DefaultTableModel
),也可以通过接口自定义。
-
View(视图)
视图负责呈现组件的数据和外观。- 负责绘制组件的外观。
- Swing 组件通常共享一个默认的视图。
-
Controller(控制器)
控制器处理用户的输入并将其映射到模型和视图。- Swing 的事件处理机制(事件监听器)充当控制器。
- 常用的控制器接口包括
ActionListener
,MouseListener
等。
2. Swing 的 MVC 特点
-
轻量级组件
Swing 组件通过 Java 代码实现,几乎不依赖原生平台,具备跨平台一致性。 -
可分离模型
- 数据模型(Model)独立于视图(View)和事件处理(Controller)。
- 某些组件(如
JTable
和JList
)允许用户使用自定义模型。
-
单线程模型
Swing 的 UI 更新需要在 事件分派线程 (EDT) 上完成,确保线程安全。 -
灵活可定制
通过模型和渲染器,开发者可以轻松自定义组件的外观和行为。
3. Swing 核心组件的模型架构
组件 | 数据模型 | 默认实现类 | 用途 |
---|---|---|---|
JButton |
ButtonModel |
DefaultButtonModel |
管理按钮状态(启用、选中等)。 |
JLabel |
无数据模型 | 无 | 仅显示文本或图像,无需数据模型。 |
JTextField |
Document |
PlainDocument |
管理输入框的文本内容。 |
JTable |
TableModel |
DefaultTableModel |
管理表格数据。 |
JList |
ListModel |
DefaultListModel |
管理列表项数据。 |
JTree |
TreeModel |
DefaultTreeModel |
管理树形结构数据。 |
JComboBox |
ComboBoxModel |
DefaultComboBoxModel |
管理组合框的选项。 |
4. 重点内容学习
- 数据模型
- 学习数据模型接口的定义(如
TableModel
,ListModel
)。 - 掌握如何使用默认实现类(如
DefaultTableModel
)进行简单开发。 - 了解如何创建自定义数据模型以满足特定需求。
- 渲染器和编辑器
- 渲染器用于定制 Swing 组件的显示样式(如单元格颜色、字体等)。
- 通过实现
TableCellRenderer
或扩展DefaultTableCellRenderer
。
- 通过实现
- 编辑器用于处理用户输入(如文本框、组合框等)。
- 通过实现
TableCellEditor
或扩展DefaultCellEditor
。
- 通过实现
- 事件处理
- 掌握事件监听器接口(如
ActionListener
,MouseListener
)。 - 理解事件传播机制,以及如何在事件分派线程中处理事件。
- 线程安全
- 了解 Swing 的单线程规则。
- 使用
SwingUtilities.invokeLater()
确保 UI 操作在事件分派线程上执行。
- 自定义组件
- 学习如何结合模型、渲染器和编辑器创建复杂的自定义组件。
- 性能优化
- 对于动态数据量较大的组件(如
JTable
),通过虚拟化模型和延迟加载优化性能。
代码MVC 使用 JTable
|
|
17. Swing的其他特性
- 如何边框、外边距、内边距
-
边框:
并非所有组件都有默认边框。外边框通过 setBorder(Border border) 方法设置,所有继承自 JComponent 的组件均支持此方法。- 有默认边框的组件:JTextField、JTextArea 通常有一个内置的边框。JScrollPane 也有默认的边框。
- 没有默认边框的组件:JButton 和 JLabel 通常没有明显的边框。
-
外边距:
外边距是指组件与组件之间的距离,它通常用于调整组件之间的间距。- 外部间距(边框): 通过 setBorder 设置边框内的间距。
- 内边距:
对于某些组件(如按钮),内边距直接控制组件内容与其边框之间的距离,不同组件提供了不同的方法。- 可以通过 setMargin 方法设置文字或内容与边界的距离。
- 支持内边距的组件:
组件类型 | 方法 | 说明 |
---|---|---|
JButton |
setMargin(Insets margin) |
设置按钮内容与边框的距离 |
JTextField |
setMargin(Insets margin) |
设置输入文本与边框的距离 |
JTextArea |
setMargin(Insets margin) |
设置多行文本与边框的距离 |
JEditorPane |
setMargin(Insets margin) |
设置编辑器内容与边框的距离 |
JPasswordField |
setMargin(Insets margin) |
设置密码输入框内容与边框的距离 |
JTabbedPane |
setTabComponentInsets(Insets insets) |
设置选项卡内容与边框的距离(需扩展方法) |
-
布局管理器:
-
FlowLayout:通过构造函数或 setHgap 和 setVgap 方法设置组件之间的水平和垂直间距。
|
|
- GridBagLayout:通过 Insets 指定组件的外边距。
- BorderLayout:在每个区域使用 EmptyBorder 设置间距。
|
|
代码边框边距综合示例
|
|
- 如何在组件上使用HTML
直接使用HTML语句来代替文本字符串即可。
代码使用HTML语句
|
|
- 如何使用图标 通过类ImageIcon来加载图片,并通过setIcon方法来设置按钮的图标。
代码ImageIcon示例
|
|
- 获取焦点
拿按钮举例,让按钮组件在窗口被激活时,理解获得动作焦点。
|
|
- 如何使用键绑定
在Java Swing中,可以为按钮绑定快捷键,通过设置键盘助记符(Mnemonic)或键绑定(Key Bindings)来实现。以下是两种常见的方法:
-
- 设置键盘助记符(Mnemonic)
键盘助记符允许用户按下
Alt + 字母
来激活按钮。
- 设置键盘助记符(Mnemonic)
键盘助记符允许用户按下
代码键盘助记符示例
|
|
-
- 设置键绑定(Key Bindings)
键绑定可以绑定任意键盘组合到按钮,不局限于
Alt + 字母
。按下Ctrl + Enter
,按钮被触发,并打印消息或执行其他逻辑。
- 设置键绑定(Key Bindings)
键绑定可以绑定任意键盘组合到按钮,不局限于
代码键绑定
|
|
-
- 两种方法的区别
方法 适用场景 示例 键盘助记符 简单快捷,通常用于菜单或按钮,激活键为 Alt + 字母
button.setMnemonic('S');
键绑定 更灵活,可以绑定任意键盘组合到组件,例如 Ctrl + Enter
inputMap.put(KeyStroke.getKeyStroke("ctrl ENTER"), "action");
- 两种方法的区别
- 如何使用Modality
在 Java Swing 中,Modality 是用来控制对话框(JDialog
)的模式行为。它定义了对话框在显示时是否会阻塞父窗口或应用程序的其他部分。常见的 Modality 类型包括:
- 模态对话框:阻塞父窗口。
- 非模态对话框:不阻塞父窗口。
- 应用程序模态:阻塞整个应用程序。
- 工具模态:只阻塞当前窗口。
使用 JDialog
的 Modality
-
- 创建一个模态对话框
可以通过JDialog
构造函数或setModalityType
方法来设置对话框的模式。
- 创建一个模态对话框
代码基本模态对话框
|
|
-
- Modality 类型详解:
Dialog.ModalityType
提供了以下几种模式:
-
APPLICATION_MODAL
(默认)
阻塞整个应用程序的其他窗口,直到对话框关闭。 -
DOCUMENT_MODAL
阻塞对话框所属的窗口及其子窗口,不阻塞其他窗口。 -
TOOLKIT_MODAL
阻塞所有同一个Toolkit
的窗口(通常是同一 JVM 的窗口)。 -
MODELESS
非模态对话框,不阻塞任何窗口。
- Modality 类型详解:
-
- 切换不同的 Modality 类型
|
|
-
- 非模态对话框示例
将 ModalityType
设置为 MODELESS
,使对话框非模态。效果,对话框显示时,用户仍然可以与主窗口交互。
|
|
- 如何创建Splash Screen
在 Java 中,Splash Screen 是指在应用程序启动时显示的一个初始屏幕,用于展示启动画面或加载信息。Java 提供了两种实现方式:
-
- 通过
java.awt.SplashScreen
类
这是 Java 内置的方式,通常用于在应用程序启动之前显示一个启动画面。在应用程序启动时,会显示splash.png
,并绘制文字 “正在启动应用程序…",之后切换到主窗口。
步骤:
- 在应用程序的 JAR 文件中添加一个启动图片(例如
splash.png
)。 - 在
MANIFEST.MF
文件中指定SplashScreen-Image
属性。
示例代码:
- 添加
MANIFEST.MF
文件
- 手动或自动将该文件打jar文件,
splash.png
图片、MANIFEST.MF
放入项目正确目录中。
- 通过
代码通过java.awt.SplashScreen类
|
|
-
- 通过自定义的
JWindow
或JFrame
这种方式可以完全自定义启动画面,并添加更多的交互或动画。
使用自定义
JWindow
如果需要更复杂的启动画面,比如动画或进度条,可以使用JWindow
来实现。一个定制的窗口显示欢迎信息和进度条,3秒后切换到主窗口。 - 通过自定义的
示例代码:
代码使用自定义JWindow
|
|
-
- 使用
JWindow
加载图片
如果需要展示一个图片作为启动画面,可以直接加载图片到
JWindow
。 - 使用
代码使用JWindow加载图片
|
|
方法 | 优点 | 缺点 |
---|---|---|
java.awt.SplashScreen |
简单易用,不需要额外代码或组件 | 样式受限,不能交互或自定义内容 |
自定义 JWindow |
完全可定制,支持交互、动画或进度条 | 需要更多代码 |
选择合适的方法根据你的需求。如果需要简单的静态画面,用 SplashScreen
;如果需要交互或动画,用自定义的 JWindow
。
- 如何使用System Tray
- 检查系统是否支持托盘: SystemTray.isSupported()
- 获取 SystemTray 实例: SystemTray systemTray = SystemTray.getSystemTray()
- 创建托盘图标: Image image = Toolkit.getDefaultToolkit().getImage(“icon.png”)
- 将菜单绑定到托盘图标: TrayIcon trayIcon = new TrayIcon(image, “System Tray Example”, popupMenu)
- 将托盘图标添加到系统托盘: systemTray.add(trayIcon)
- 显示气泡通知: trayIcon.displayMessage(“标题”, “内容”, TrayIcon.MessageType.INFO)
- 动态更改图标: trayIcon.setImage(newImage)
代码SystemTray示例
|
|
总结
本章重点,Swing与并发、Swing的其他特性,知识点比较杂,结合例子多练习。
文章作者 江南小哥
上次更新 2025-01-17
许可协议 MIT