JavaSwingGUI从小白到大神-5
背景
首先本文是一个系列的文章,起名为《Java Swing GUi从小白到大神》,主要介绍Java Swing相关概念和实践应用。目前JavaSwingGUI很少作为界面的开发框架,但是对于学习Java体系,以及新手将来学习后端之后,能快速从前后端整体去理解一套系统是非常有帮助的,所以是很有必要学习和掌握的。本篇是系列博文的第五篇,若想详细学习请点击首篇博文开始,让我们开始吧。
文章概览
- Swing与并发
- Swing模型架构
- Swing的其他特性
15. Swing与并发
1. SwingUtilities类介绍
先了解一下核心类SwingUtilities 是一个实用类,包含与 Swing GUI 相关的静态方法,以下是其三个常用方法及说明:
SwingUtilities 类的 3 个常用方法
- invokeLater(Runnable doRun)
- 作用:将一个任务(Runnable)调度到 事件分派线程(EDT) 上执行。
- 用途:确保所有的 GUI 操作在 EDT 上运行,避免线程安全问题。
- 示例:
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Example");
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
- invokeAndWait(Runnable doRun)
- 作用:将一个任务调度到 EDT 上执行,并等待该任务完成。
- 用途:用于需要在后台线程中执行某些 GUI 操作并确保同步完成。
- 注意:如果当前线程已经是 EDT,调用此方法会导致 IllegalStateException。
- 示例:
try {
SwingUtilities.invokeAndWait(() -> System.out.println("Running on EDT"));
} catch (Exception e) {
e.printStackTrace();
}
- isEventDispatchThread()
- 作用:检查当前线程是否是 EDT。
- 用途:在执行线程相关逻辑前验证线程类型,避免非线程安全操作。
- 示例:
if (SwingUtilities.isEventDispatchThread()) {
System.out.println("Running on EDT");
} else {
SwingUtilities.invokeLater(() -> System.out.println("Switch to EDT"));
}
2. wingWorker类介绍
SwingWorker 是一个抽象类,用于在后台线程中执行耗时任务,同时支持在事件分派线程中更新 GUI。
常用方法:
- doInBackground()
- 作用:定义后台任务的主逻辑。
- 运行线程:后台线程。
- 返回值:任务完成后返回的结果(SwingWorker 的泛型参数 V)。
- 示例:
@Override
protected Void doInBackground() {
for (int i = 0; i < 100; i++) {
setProgress(i);
Thread.sleep(100);
}
return null;
}
- publish(V... chunks)
- 作用:在后台线程中发布中间结果。
- 运行线程:后台线程。
- 用途:将中间结果发送给 process() 方法。
- 示例:
publish(currentProgress);
- process(List
chunks)
- 作用:接收由 publish() 方法发送的中间结果。
- 运行线程:事件分派线程(EDT)。
- 用途:用于更新 GUI。
- 示例:
@Override
protected void process(List chunks) {
progressBar.setValue(chunks.get(chunks.size() - 1));
}
- done()
- 作用:后台任务完成后调用。
- 运行线程:事件分派线程(EDT)。
- 用途:完成任务后的清理或结果处理。
- 示例:
@Override
protected void done() {
System.out.println("Task Completed");
}
- setProgress(int progress)
- 作用:设置任务进度(范围 0-100)。
- 运行线程:后台线程。
- 用途:用于更新 progress 属性,并通过监听器通知。
- 示例:
setProgress(50);
- get()
- 作用:获取 doInBackground() 返回的结果。
- 运行线程:任何线程。
- 用途:阻塞线程直到任务完成,适用于获取计算结果。
- 示例:
try {
String result = myWorker.get();
} catch (Exception e) {
e.printStackTrace();
}
- execute()
- 作用:启动后台任务。
- 运行线程:调用线程。
- 用途:创建任务后,调用此方法开始执行。
- 示例:
myWorker.execute();
- cancel(boolean mayInterruptIfRunning)
- 作用:取消任务。
- 运行线程:任何线程。
- 用途:设置任务为取消状态,可选择是否中断正在运行的任务。
- 示例:
myWorker.cancel(true);
- isCancelled()
- 作用:检查任务是否已被取消。
- 运行线程:任何线程。
- 用途:在后台任务中动态检测取消状态。
- 示例:
if (isCancelled()) break;
- addPropertyChangeListener(PropertyChangeListener listener)
- 作用:添加属性变化监听器,监听 progress 或 state 的变化。
- 用途:实现动态界面更新。
- 示例:
myWorker.addPropertyChangeListener(evt -> {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((int) evt.getNewValue());
}
});
3. 事件分派线程
事件分派线程(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() 方法检查当前线程是否为事件分派线程。
示例:
if (SwingUtilities.isEventDispatchThread()) {
System.out.println("Running on Event Dispatch Thread");
} else {
System.out.println("Not on Event Dispatch Thread");
}
如何确保代码运行在事件分派线程上:
如果代码需要操作 Swing 组件但可能运行在非 EDT 的线程中,应该使用以下方法将任务调度到 EDT:
- SwingUtilities.invokeLater(Runnable)
- 将任务提交给 EDT 异步执行,不阻塞调用线程。
- 示例:
SwingUtilities.invokeLater(() -> {
JLabel label = new JLabel("Hello, Swing!");
frame.add(label);
});
- SwingUtilities.invokeAndWait(Runnable)
- 将任务提交给 EDT 并等待其完成(阻塞调用线程)。
- 示例:
try {
SwingUtilities.invokeAndWait(() -> {
JLabel label = new JLabel("Hello, Swing!");
frame.add(label);
});
} catch (Exception e) {
e.printStackTrace();
}
Swing多线程
public class ArithmeticAccumulatorDemo {
private JFrame frame;
private JLabel resultLabel, progressLabel;
private JButton startButton, showResultButton, pauseButton, resumeButton, resetButton;
private SwingWorker arithmeticWorker;
private volatile boolean isPaused = false;
private int accumulator = 0;
public ArithmeticAccumulatorDemo() {
// 创建主窗口
frame = new JFrame("Arithmetic Accumulator Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 350);
frame.setLayout(new BorderLayout());
// 顶部: 显示累加器结果和进度
JPanel displayPanel = new JPanel(new GridLayout(2, 1));
resultLabel = new JLabel("Current Value: 0", SwingConstants.CENTER);
resultLabel.setFont(new Font("Arial", Font.BOLD, 24));
progressLabel = new JLabel("Progress: N/A", SwingConstants.CENTER);
progressLabel.setFont(new Font("Arial", Font.PLAIN, 16));
displayPanel.add(resultLabel);
displayPanel.add(progressLabel);
frame.add(displayPanel, BorderLayout.CENTER);
// 底部: 按钮面板
JPanel buttonPanel = new JPanel(new FlowLayout());
startButton = new JButton("Start Accumulator");
showResultButton = new JButton("Show Interim Result");
pauseButton = new JButton("Pause");
resumeButton = new JButton("Resume");
resetButton = new JButton("Reset Accumulator");
buttonPanel.add(startButton);
buttonPanel.add(showResultButton);
buttonPanel.add(pauseButton);
buttonPanel.add(resumeButton);
buttonPanel.add(resetButton);
frame.add(buttonPanel, BorderLayout.SOUTH);
// 按钮事件
startButton.addActionListener(e -> startAccumulator());
showResultButton.addActionListener(e -> showInterimResult());
pauseButton.addActionListener(e -> pauseAccumulator());
resumeButton.addActionListener(e -> resumeAccumulator());
resetButton.addActionListener(e -> resetAccumulator());
frame.setVisible(true);
}
// 启动累加器
private void startAccumulator() {
if (arithmeticWorker != null && !arithmeticWorker.isDone()) {
JOptionPane.showMessageDialog(frame, "Accumulator is already running!", "Warning",
JOptionPane.WARNING_MESSAGE);
return;
}
accumulator = 0; // 重置累加器
isPaused = false;
arithmeticWorker = new SwingWorker() {
@Override
protected Void doInBackground() throws Exception {
while (!isCancelled()) {
if (!isPaused) {
accumulator++;
publish(accumulator); // 发布中间结果
setProgress(Math.min(accumulator, 100)); // 设置进度
Thread.sleep(500); // 模拟计算延迟
}
}
return null;
}
@Override
protected void process(List chunks) {
// 获取最后一个中间结果
int latestResult = chunks.get(chunks.size() - 1);
resultLabel.setText("Current Value: " + latestResult);
}
@Override
protected void done() {
JOptionPane.showMessageDialog(frame, "Accumulator task completed or stopped.", "Info",
JOptionPane.INFORMATION_MESSAGE);
}
};
// 绑定属性变化监听器
arithmeticWorker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressLabel.setText("Progress: " + evt.getNewValue() + "%");
} else if ("state".equals(evt.getPropertyName())) {
progressLabel.setText("State: " + evt.getNewValue());
}
}
});
arithmeticWorker.execute();
JOptionPane.showMessageDialog(frame, "Accumulator started in the background.", "Info",
JOptionPane.INFORMATION_MESSAGE);
}
// 显示临时结果
private void showInterimResult() {
if (arithmeticWorker == null || arithmeticWorker.isDone()) {
JOptionPane.showMessageDialog(frame, "Accumulator is not running!", "Warning", JOptionPane.WARNING_MESSAGE);
} else {
resultLabel.setText("Current Value: " + accumulator);
}
}
// 暂停累加器
private void pauseAccumulator() {
if (arithmeticWorker == null || arithmeticWorker.isDone()) {
JOptionPane.showMessageDialog(frame, "Accumulator is not running!", "Warning", JOptionPane.WARNING_MESSAGE);
} else if (isPaused) {
JOptionPane.showMessageDialog(frame, "Accumulator is already paused!", "Warning",
JOptionPane.WARNING_MESSAGE);
} else {
isPaused = true;
JOptionPane.showMessageDialog(frame, "Accumulator paused.", "Info", JOptionPane.INFORMATION_MESSAGE);
}
}
// 恢复累加器
private void resumeAccumulator() {
if (arithmeticWorker == null || arithmeticWorker.isDone()) {
JOptionPane.showMessageDialog(frame, "Accumulator is not running!", "Warning", JOptionPane.WARNING_MESSAGE);
} else if (!isPaused) {
JOptionPane.showMessageDialog(frame, "Accumulator is already running!", "Warning",
JOptionPane.WARNING_MESSAGE);
} else {
isPaused = false;
JOptionPane.showMessageDialog(frame, "Accumulator resumed.", "Info", JOptionPane.INFORMATION_MESSAGE);
}
}
// 重置累加器
private void resetAccumulator() {
if (arithmeticWorker != null && !arithmeticWorker.isDone()) {
arithmeticWorker.cancel(true);
}
accumulator = 0;
resultLabel.setText("Current Value: 0");
progressLabel.setText("Progress: N/A");
JOptionPane.showMessageDialog(frame, "Accumulator reset. You can start again.", "Info",
JOptionPane.INFORMATION_MESSAGE);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(ArithmeticAccumulatorDemo::new);
}
}
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
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
public class SwingMVCExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
// 创建数据模型 (Model)
DefaultTableModel tableModel = new DefaultTableModel(new Object[]{"ID", "Name", "Age"}, 0);
// 添加数据
tableModel.addRow(new Object[]{1, "Alice", 25});
tableModel.addRow(new Object[]{2, "Bob", 30});
tableModel.addRow(new Object[]{3, "Charlie", 35});
// 创建视图 (View)
JTable table = new JTable(tableModel);
// 添加事件监听器 (Controller)
table.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
int selectedRow = table.getSelectedRow();
if (selectedRow != -1) {
System.out.println("Selected Row Data: " +
tableModel.getValueAt(selectedRow, 1));
}
}
});
// 显示 GUI
JFrame frame = new JFrame("Swing MVC Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setVisible(true);
});
}
}
17. Swing的其他特性
1. 边框设置
并非所有组件都有默认边框。外边框通过 setBorder(Border border) 方法设置,所有继承自 JComponent 的组件均支持此方法。
- 有默认边框的组件:JTextField、JTextArea 通常有一个内置的边框。JScrollPane 也有默认的边框。
- 没有默认边框的组件:JButton 和 JLabel 通常没有明显的边框。
JLabel label = new JLabel("Example Label");
label.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2)); // 黑色边框,宽度为2像素
2. 外边距设置
外边距是指组件与组件之间的距离,它通常用于调整组件之间的间距。
- 外部间距(边框): 通过 setBorder 设置边框内的间距。
component.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.RED, 2), // 外边框
BorderFactory.createEmptyBorder(10, 15, 10, 15) // 外边距:上10px、左15px、下10px、右15px
));
3. 内边距设置
对于某些组件(如按钮),内边距直接控制组件内容与其边框之间的距离,不同组件提供了不同的方法。
- 可以通过 setMargin 方法设置文字或内容与边界的距离。
JButton button = new JButton("Click Me");
button.setMargin(new Insets(10, 20, 10, 20)); // 上10px、左20px、下10px、右20px
- 支持内边距的组件:
组件类型 | 方法 | 说明 |
JButton | setMargin(Insets margin) | 设置按钮内容与边框的距离 |
JTextField | setMargin(Insets margin) | 设置输入文本与边框的距离 |
JTextArea | setMargin(Insets margin) | 设置多行文本与边框的距离 |
JEditorPane | setMargin(Insets margin) | 设置编辑器内容与边框的距离 |
JPasswordField | setMargin(Insets margin) | 设置密码输入框内容与边框的距离 |
JTabbedPane | setTabComponentInsets(Insets insets) | 设置选项卡内容与边框的距离(需扩展方法) |
4. 布局管理器
- FlowLayout:通过构造函数或 setHgap 和 setVgap 方法设置组件之间的水平和垂直间距。
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 20, 10)); // 外边距
- GridBagLayout:通过 Insets 指定组件的外边距。
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10); // 上、左、下、右外边距
- BorderLayout:在每个区域使用 EmptyBorder 设置间距。
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
边框边距综合示例
public class SwingSpacingExample {
public static void main(String[] args) {
JFrame frame = new JFrame("Swing Spacing Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
// 为 JFrame 设置红色边框(通过内容面板设置)
JPanel frameContentPanel = (JPanel) frame.getContentPane();
frameContentPanel.setBorder(BorderFactory.createLineBorder(Color.RED, 5)); // 外边框:红色
// 创建主面板 JPanel
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout(FlowLayout.CENTER, 20, 20)); // 水平和垂直间距
panel.setBorder(BorderFactory.createLineBorder(Color.GREEN, 3)); // 外边框:绿色
// 创建按钮 JButton
JButton button = new JButton("Button");
button.setPreferredSize(new Dimension(120, 50));
button.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.BLUE, 2), // 外边框:蓝色
BorderFactory.createEmptyBorder(10, 10, 10, 10) // 内边框:空白,10像素
));
// 创建标签 JLabel
JLabel label = new JLabel("Label");
label.setPreferredSize(new Dimension(120, 50));
label.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.ORANGE, 2), // 外边框:橙色
BorderFactory.createEmptyBorder(5, 5, 5, 5) // 内边框:空白,5像素
));
// 添加组件到面板
panel.add(button);
panel.add(label);
// 添加主面板到 JFrame
frame.add(panel);
frame.setVisible(true);
}
}
5. 如何在组件上使用HTML
直接使用HTML语句来代替文本字符串即可。
使用HTML语句
public class SwingHtmlExample {
public static void main(String[] args) {
JFrame frame = new JFrame("Swing HTML Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
JPanel panel = new JPanel();
JButton jButton1 = new JButton("Hello, World!
");
JButton jButton2 = new JButton("Hello, World!
");
panel.add(jButton1);
panel.add(jButton2);
frame.add(panel);
frame.setVisible(true);
}
}
6. 设置按钮的图标
通过类ImageIcon来加载图片,并通过setIcon方法来设置按钮的图标。
ImageIcon示例
public class ImageIconExample {
public static void main(String[] args) {
JFrame frame = new JFrame("Swing HTML Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
JPanel panel = new JPanel();
ImageIcon icon1 = new ImageIcon("d://1.jpeg");
ImageIcon icon2 = new ImageIcon("d://2.jpeg");
JLabel jLabel1 = new JLabel(icon1);
JButton jButton1 = new JButton(icon2);
panel.add(jLabel1);
panel.add(jButton1);
frame.add(panel);
frame.setVisible(true);
}
}
7. 获取焦点
拿按钮举例,让按钮组件在窗口被激活时,理解获得动作焦点。
JButton.requestFocusInWindow();
8. 如何使用键绑定
在Java Swing中,可以为按钮绑定快捷键,通过设置键盘助记符(Mnemonic)或键绑定(Key Bindings)来实现。以下是两种常见的方法:
- 设置键盘助记符(Mnemonic) 键盘助记符允许用户按下 Alt + 字母 来激活按钮。
public class ButtonMnemonicExample {
public static void main(String[] args) {
// 创建 JFrame
JFrame frame = new JFrame("按钮快捷键示例");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
// 创建按钮
JButton button = new JButton("保存(S)");
// 设置助记符 Alt+S
button.setMnemonic('S');
// 添加按钮到 JFrame
frame.add(button);
// 显示 JFrame
frame.setVisible(true);
}
}
- 设置键绑定(Key Bindings) 键绑定可以绑定任意键盘组合到按钮,不局限于 Alt + 字母。按下 Ctrl + Enter,按钮被触发,并打印消息或执行其他逻辑。
public class ButtonKeyBindingExample {
public static void main(String[] args) {
// 创建 JFrame
JFrame frame = new JFrame("按钮快捷键示例");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
// 创建按钮
JButton button = new JButton("提交");
frame.add(button);
// 获取按钮的输入映射和动作映射
InputMap inputMap = button.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = button.getActionMap();
// 绑定快捷键 Ctrl+Enter
inputMap.put(KeyStroke.getKeyStroke("ctrl ENTER"), "submitAction");
// 定义快捷键触发的行为
actionMap.put("submitAction", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮通过 Ctrl+Enter 触发!");
JOptionPane.showMessageDialog(frame, "提交成功!");
}
});
// 显示 JFrame
frame.setVisible(true);
}
}
- 两种方法的区别
方法 | 适用场景 | 示例 |
键盘助记符 | 简单快捷,通常用于菜单或按钮,激活键为 Alt + 字母 | button.setMnemonic('S'); |
键绑定 | 更灵活,可以绑定任意键盘组合到组件,例如 Ctrl + Enter | inputMap.put(KeyStroke.getKeyStroke("ctrl ENTER"), "action"); |
9. 如何使用Modality
在 Java Swing 中,Modality 是用来控制对话框(JDialog)的模式行为。它定义了对话框在显示时是否会阻塞父窗口或应用程序的其他部分。常见的 Modality 类型包括:
- 模态对话框:阻塞父窗口。
- 非模态对话框:不阻塞父窗口。
- 应用程序模态:阻塞整个应用程序。
- 工具模态:只阻塞当前窗口。
使用 JDialog 的 Modality
- 创建一个模态对话框
可以通过 JDialog 构造函数或 setModalityType 方法来设置对话框的模式。
import javax.swing.*;
public class ModalityExample {
public static void main(String[] args) {
// 创建 JFrame
JFrame frame = new JFrame("主窗口");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
// 创建按钮打开对话框
JButton button = new JButton("打开模态对话框");
frame.add(button);
// 按钮点击事件
button.addActionListener(e -> {
// 创建模态对话框
JDialog dialog = new JDialog(frame, "模态对话框", Dialog.ModalityType.APPLICATION_MODAL);
dialog.setSize(200, 150);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
// 添加内容到对话框
JLabel label = new JLabel("这是一个模态对话框", SwingConstants.CENTER);
dialog.add(label);
// 显示对话框
dialog.setLocationRelativeTo(frame); // 对话框居中
dialog.setVisible(true);
});
// 显示主窗口
frame.setVisible(true);
}
}
- Modality 类型详解:Dialog.ModalityType 提供了以下几种模式:
- APPLICATION_MODAL(默认)
阻塞整个应用程序的其他窗口,直到对话框关闭。 - DOCUMENT_MODAL
阻塞对话框所属的窗口及其子窗口,不阻塞其他窗口。 - TOOLKIT_MODAL
阻塞所有同一个 Toolkit 的窗口(通常是同一 JVM 的窗口)。 - MODELESS
非模态对话框,不阻塞任何窗口。 - 切换不同的 Modality 类型
button.addActionListener(e -> {
JDialog dialog = new JDialog(frame, "模态对话框");
dialog.setSize(200, 150);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
// 设置不同的 Modality 类型
dialog.setModalityType(Dialog.ModalityType.DOCUMENT_MODAL);
dialog.add(new JLabel("Document Modal 示例", SwingConstants.CENTER));
dialog.setLocationRelativeTo(frame);
dialog.setVisible(true);
});
- 非模态对话框示例
将 ModalityType 设置为 MODELESS,使对话框非模态。效果,对话框显示时,用户仍然可以与主窗口交互。
button.addActionListener(e -> {
JDialog dialog = new JDialog(frame, "非模态对话框");
dialog.setSize(200, 150);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
// 设置为非模态
dialog.setModalityType(Dialog.ModalityType.MODELESS);
dialog.add(new JLabel("这是一个非模态对话框", SwingConstants.CENTER));
dialog.setLocationRelativeTo(frame);
dialog.setVisible(true);
});
10. 如何创建Splash Screen
在 Java 中,Splash Screen 是指在应用程序启动时显示的一个初始屏幕,用于展示启动画面或加载信息。Java 提供了两种实现方式:
- 通过 java.awt.SplashScreen 类
这是 Java 内置的方式,通常用于在应用程序启动之前显示一个启动画面。在应用程序启动时,会显示 splash.png,并绘制文字 “正在启动应用程序…",之后切换到主窗口。 - 步骤:
- 在应用程序的 JAR 文件中添加一个启动图片(例如 splash.png)。
- 在 MANIFEST.MF 文件中指定 SplashScreen-Image 属性。
- 示例代码:
- 添加 MANIFEST.MF 文件
Manifest-Version: 1.0
Main-Class: MainClass
SplashScreen-Image: splash.png
- 手动或自动将该文件打jar文件,splash.png图片、MANIFEST.MF放入项目正确目录中。
public class SplashScreenExample {
public static void main(String[] args) {
// 显示启动画面
showSplashScreen();
// 模拟加载过程
try {
Thread.sleep(3000); // 等待 3 秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 隐藏启动画面并显示主窗口
SwingUtilities.invokeLater(SplashScreenExample::showMainWindow);
}
private static void showSplashScreen() {
// 获取当前的 SplashScreen
SplashScreen splash = SplashScreen.getSplashScreen();
if (splash != null) {
// 获取 SplashScreen 的绘制画布
Graphics2D g = splash.createGraphics();
if (g != null) {
// 绘制文本
g.setColor(Color.BLACK);
g.setFont(new Font("Microsoft YaHei", Font.BOLD, 16));
g.drawString("正在启动应用程序...", 50, 180);
// 更新 SplashScreen 的内容
splash.update();
}
} else {
System.out.println("无法加载启动画面(请确保 splash.png 已正确设置)");
}
}
private static void showMainWindow() {
// 创建主窗口
JFrame frame = new JFrame("主窗口");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
// 设置主窗口内容
JLabel label = new JLabel("欢迎使用主窗口", JLabel.CENTER);
label.setFont(new Font("Microsoft YaHei", Font.BOLD, 24));
frame.add(label);
// 显示主窗口
frame.setVisible(true);
}
}
- 通过自定义的 JWindow 或 JFrame
这种方式可以完全自定义启动画面,并添加更多的交互或动画。
使用自定义 JWindow
如果需要更复杂的启动画面,比如动画或进度条,可以使用 JWindow 来实现。一个定制的窗口显示欢迎信息和进度条,3秒后切换到主窗口。
public class CustomSplashScreen {
public static void main(String[] args) {
// 创建一个 JWindow 作为启动画面
JWindow splash = new JWindow();
splash.setSize(400, 300);
splash.setLocationRelativeTo(null);
// 设置启动画面的内容
JPanel panel = new JPanel(new BorderLayout());
panel.setBackground(Color.WHITE);
// 设置支持中文的字体,例如微软雅黑
JLabel label = new JLabel("欢迎使用我的应用", JLabel.CENTER);
label.setFont(new Font("Microsoft YaHei", Font.BOLD, 24)); // 设置字体为微软雅黑
panel.add(label, BorderLayout.CENTER);
JProgressBar progressBar = new JProgressBar();
progressBar.setIndeterminate(true); // 显示不确定的进度条
panel.add(progressBar, BorderLayout.SOUTH);
splash.add(panel);
// 显示启动画面
splash.setVisible(true);
// 模拟加载过程
try {
Thread.sleep(3000); // 等待3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 隐藏启动画面并启动主窗口
splash.dispose();
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("主窗口");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setVisible(true);
});
}
}
- 使用 JWindow 加载图片
如果需要展示一个图片作为启动画面,可以直接加载图片到 JWindow。
import javax.swing.*;
import java.awt.*;
public class SplashScreenWithImage {
public static void main(String[] args) {
// 创建 JWindow
JWindow splash = new JWindow();
splash.setSize(400, 300);
splash.setLocationRelativeTo(null);
// 设置图片作为启动画面
JLabel imageLabel = new JLabel(new ImageIcon("splash.png"));
splash.add(imageLabel);
// 显示启动画面
splash.setVisible(true);
// 模拟加载过程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 隐藏启动画面并启动主窗口
splash.dispose();
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("主窗口");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setVisible(true);
});
}
}
方法 | 优点 | 缺点 |
java.awt.SplashScreen | 简单易用,不需要额外代码或组件 | 样式受限,不能交互或自定义内容 |
自定义 JWindow | 完全可定制,支持交互、动画或进度条 | 需要更多代码 |
选择合适的方法根据你的需求。如果需要简单的静态画面,用 SplashScreen;如果需要交互或动画,用自定义的 JWindow。
11. 如何使用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)
public class SystemTrayExample {
public static void main(String[] args) {
if (!SystemTray.isSupported()) {
System.out.println("System Tray is not supported on your system.");
return;
}
SystemTray systemTray = SystemTray.getSystemTray();
Image image = Toolkit.getDefaultToolkit().getImage("d:\\11.png");
PopupMenu popupMenu = new PopupMenu();
MenuItem exitItem = new MenuItem("Exit");
exitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
popupMenu.add(exitItem);
TrayIcon trayIcon = new TrayIcon(image, "System Tray Example", popupMenu);
trayIcon.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Tray icon clicked");
}
});
try {
systemTray.add(trayIcon);
} catch (AWTException e) {
System.out.println("Error adding tray icon: " + e);
}
}
}
12. 对齐
- GridBagConstraints.anchor 的作用 ,用于控制组件在其布局区域(由 gridwidth 和 gridheight 定义的单元格)内的对齐方式。设置为 EAST 时,组件会紧贴区域的右侧。
- 常见对齐常量GridBagConstraints.WEST 左对齐GridBagConstraints.EAST 右对齐GridBagConstraints.CENTER 居中对齐(默认值)GridBagConstraints.NORTH 顶部对齐GridBagConstraints.SOUTH 底部对齐
public class GridBagExample {
public static void main(String[] args) {
JFrame frame = new JFrame("GridBagLayout 示例");
frame.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
// 创建一个标签,右对齐
JLabel label = new JLabel("用户名:");
gbc.anchor = GridBagConstraints.EAST; // 右对齐
gbc.gridx = 0;
gbc.gridy = 0;
frame.add(label, gbc);
// 创建一个文本框,左对齐
JTextField textField = new JTextField(20);
gbc.anchor = GridBagConstraints.WEST; // 左对齐
gbc.gridx = 1;
gbc.gridy = 0;
frame.add(textField, gbc);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
总结
本章重点,Swing与并发、Swing的其他特性,知识点比较杂,结合例子多练习。