Java Swing 完整教程
目录
- 什么是 Swing?
- Swing 的核心概念
- AWT vs. Swing
- MVC (Model-View-Controller) 设计模式
- 顶层容器
- 中间容器
- 基本组件
- 布局管理器
- 事件处理
- 第一个 Swing 程序:Hello World
- 常用组件详解
- 标签、按钮、文本框
- 列表框、下拉框
- 单选按钮、复选框
- 菜单栏、工具栏
- 表格
- 树
- 布局管理器详解
FlowLayout(流式布局)BorderLayout(边界布局)GridLayout(网格布局)BoxLayout(箱式布局)GridBagLayout(网格包布局 - 最强大也最复杂)GroupLayout( GroupLayout - 在 GUI 设计器中常用)
- 事件处理机制
- 监听器接口
- 匿名内部类实现监听器
- Lambda 表达式 (Java 8+)
- 高级特性
- 多线程与
SwingWorker - 自定义绘制
JOptionPane(消息对话框)JFileChooser(文件选择器)
- 多线程与
- 实战项目:一个简单的记事本
- 学习资源与最佳实践
什么是 Swing?
Swing 是 Java 基础类库的一部分,它是一个用于开发图形用户界面的工具包,使用 Swing,你可以创建独立于平台的窗口应用程序,这些应用程序可以在任何安装了 Java 虚拟机的操作系统上运行,而无需修改代码。

核心特点:
- 轻量级: Swing 组件(除了
JFrame,JDialog,JApplet等顶层容器)不依赖操作系统的本地 GUI,而是用纯 Java 绘制,因此外观更统一,且在不同平台下表现一致。 - 功能丰富: 提供了大量的组件,如按钮、文本框、菜单、表格、树等,足以构建复杂的 GUI 应用。
- 可定制性: 你可以轻松地修改组件的外观(通过 Look and Feel)和行为,甚至创建自己的自定义组件。
- 事件驱动: Swing 应用程序是基于事件的,用户的操作(如点击按钮、敲击键盘)会触发事件,你的代码通过监听这些事件来做出响应。
Swing 的核心概念
在开始编码前,理解这些核心概念至关重要。
AWT vs. Swing
- AWT (Abstract Window Toolkit): Java 最早的 GUI 工具包,其组件是“重量级”的,意味着它们直接由操作系统的本地代码创建和渲染,这导致了在不同平台上外观和行为不一致的问题。
- Swing: 作为 AWT 的继者,Swing 大部分组件是“轻量级”的,它们不直接调用系统 API,而是在画布上自己绘制,Swing 重用了 AWT 中的一些基础类(如
Event,Color,Font),但提供了全新的、功能更强大的组件集。
现在开发 Java GUI 应该优先选择 Swing,而不是 AWT。
MVC (Model-View-Controller) 设计模式
Swing 的许多组件都采用了 MVC 模式,这使得代码结构更清晰、更易于维护。

- Model (模型): 负责存储数据。
JButton的模型会记录按钮是否被按下。 - View (视图): 负责组件的视觉呈现。
JButton的外观(颜色、文字、边框)。 - Controller (控制器): 负责处理用户输入并更新模型,当用户点击
JButton时,控制器会通知模型状态改变,模型再通知视图进行重绘。
在 Swing 中,你可以将这三者分离,为 JButton 设置一个自定义的模型来改变其行为。
顶层容器
所有 Swing 组件都必须被放置在顶层容器中,顶层容器是应用程序的根窗口。
JFrame: 最常用的顶层容器,代表一个独立的窗口,有标题栏、边框、最小化/最大化/关闭按钮。一个 GUI 应用通常只有一个JFrame。JDialog: 一个对话框窗口,通常用于从用户那里获取信息或显示消息,它必须依附于另一个窗口(通常是JFrame)。JApplet: 用于在网页中运行的 Java 小程序(现已不推荐使用)。
中间容器
用于组织和布局其他组件。
JPanel: 最常用的中间容器,没有任何特殊的装饰,主要配合布局管理器来组织组件。JScrollPane: 提供滚动功能的容器,当你放入的组件内容超出可视区域时,会出现滚动条。JSplitPane: 一个可以水平或垂直分割两个组件的容器,用户可以拖动分割线来调整两个组件的大小。JTabbedPane: 创建带有标签页的容器,每个标签页可以包含一组组件。
基本组件
构成 GUI 的具体元素。

JLabel: 用于显示文本或图像。JButton: 一个可点击的按钮。JTextField: 单行文本输入框。JTextArea: 多行文本区域。JPasswordField: 密码输入框,输入的字符会显示为掩码(如 )。JList: 显示一个可滚动的列表项列表。JComboBox: 下拉列表框。JCheckBox: 复选框。JRadioButton: 单选按钮,需要配合ButtonGroup使用。JMenu,JMenuBar,JMenuItem: 用于构建菜单栏和菜单。
布局管理器
布局管理器负责决定组件在容器中的位置和大小。绝对定位(使用 setBounds(x, y, width, height))在 Swing 中是不推荐的,因为它会使界面在不同分辨率的屏幕上显示混乱。
Swing 提供了多种内置的布局管理器:
FlowLayout: 从左到右、从上到下地排列组件,像文字一样。BorderLayout: 将容器分为五个区域:北、南、东、西、中。GridLayout: 将容器划分为一个网格,每个组件占据一个网格单元。BoxLayout: 允许你垂直或水平地将组件排列在一条线上。GridBagLayout: 最强大也最复杂的布局管理器,允许组件跨越多个网格,并对齐方式灵活。
事件处理
Swing 应用是事件驱动的,当用户与组件交互时,组件会创建一个 Event 对象,并将其分发给所有已注册的监听器。
- 事件源: 产生事件的组件(如
JButton)。 - 事件: 描述发生了什么事情的对象(如
ActionEvent表示按钮被点击)。 - 监听器: 一个实现了特定监听器接口的对象,它“监听”来自特定事件源的事件,并在事件发生时执行相应的代码。
要让一个按钮在点击时执行某些操作,你需要:
- 为按钮添加一个
ActionListener监听器。 - 在监听器的
actionPerformed方法中编写要执行的代码。
第一个 Swing 程序:Hello World
这是一个最简单的 Swing 程序,它创建一个窗口并在窗口中央显示一个 "Hello, Swing!" 的标签。
import javax.swing.*;
import java.awt.*;
public class HelloWorldSwing {
public static void main(String[] args) {
// 1. 在事件分发线程中创建和显示 GUI
// 这是 Swing 编程的最佳实践,可以避免线程安全问题
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
// 2. 创建顶层容器 JFrame
JFrame frame = new JFrame("HelloWorldSwing");
// 当用户关闭窗口时,程序退出
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 3. 创建一个中间容器 JPanel,并设置其布局管理器
JPanel panel = new JPanel(new BorderLayout());
// 4. 创建一个基本组件 JLabel
JLabel label = new JLabel("Hello, Swing!", SwingConstants.CENTER);
// 5. 将组件添加到容器中
panel.add(label, BorderLayout.CENTER);
// 6. 将面板添加到窗口中
frame.getContentPane().add(panel);
// 7. 设置窗口大小并使其可见
frame.setSize(400, 300);
frame.setVisible(true);
}
}
代码解释:
SwingUtilities.invokeLater(...): 这是启动 Swing 应用的标准方式,它确保 GUI 的创建和更新都在事件分发线程 上执行,这是线程安全的,对于小型应用,你可能看不到直接调用的区别,但对于任何涉及用户交互的复杂应用,这都是必须遵守的规则。JFrame: 我们的应用窗口。JPanel: 我们使用BorderLayout来布局,将标签放在中间。JLabel: 显示文本的组件。frame.getContentPane().add(...):JFrame的内容区默认使用BorderLayout,我们直接将面板添加进去。
常用组件详解
标签、按钮、文本框
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class SimpleComponents {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Simple Components");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout()); // 使用流式布局
// JLabel
JLabel label = new JLabel("Enter your name:");
// JTextField
JTextField textField = new JTextField(20); // 20列宽度
// JButton
JButton button = new JButton("Click Me");
// 为按钮添加事件监听器
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String name = textField.getText();
JOptionPane.showMessageDialog(frame, "Hello, " + name + "!");
}
});
// 将组件添加到窗口
frame.add(label);
frame.add(textField);
frame.add(button);
frame.pack(); // 自动调整窗口大小以适应组件
frame.setVisible(true);
});
}
}
JOptionPane.showMessageDialog 是一个非常方便的静态方法,用于快速显示一个消息对话框。
列表框、下拉框
// 在上面的 SimpleComponents.java 中添加
// JList
String[] flavors = {"Chocolate", "Strawberry", "Vanilla"};
JList<String> flavorList = new JList<>(flavors);
flavorList.setVisibleRowCount(3); // 设置可见行数
JScrollPane listScrollPane = new JScrollPane(flavorList); // 为列表添加滚动条
// JComboBox
JComboBox<String> flavorCombo = new JComboBox<>(flavors);
// 将它们添加到窗口
frame.add(new JLabel("Choose a flavor (List):"));
frame.add(listScrollPane);
frame.add(new JLabel("Choose a flavor (Combo):"));
frame.add(flavorCombo);
单选按钮、复选框
单选按钮需要放在一个 ButtonGroup 中,以确保同一时间只有一个被选中。
// 在上面的代码中添加
// JCheckBox
JCheckBox checkBox1 = new JCheckBox("Option 1");
JCheckBox checkBox2 = new JCheckBox("Option 2");
// JRadioButton 和 ButtonGroup
JRadioButton radio1 = new JRadioButton("Option A");
JRadioButton radio2 = new JRadioButton("Option B");
ButtonGroup group = new ButtonGroup();
group.add(radio1);
group.add(radio2);
// 默认选中一个
radio1.setSelected(true);
// 将它们添加到窗口
frame.add(checkBox1);
frame.add(checkBox2);
frame.add(new JLabel("Radio Buttons:"));
frame.add(radio1);
frame.add(radio2);
布局管理器详解
FlowLayout (流式布局)
组件从左到右、从上到下排列,像文字换行,默认 JPanel 的布局。
JPanel panel = new JPanel(); // 默认就是 FlowLayout
panel.add(new JButton("Button 1"));
panel.add(new JButton("A Longer Button 2"));
panel.add(new JButton("3"));
BorderLayout (边界布局)
将容器分为五个区域:NORTH, SOUTH, EAST, WEST, CENTER,每个区域只能添加一个组件,默认 JFrame 的布局。
JFrame frame = new JFrame();
frame.setLayout(new BorderLayout(5, 5)); // 水平、垂直间距为5像素
frame.add(new JButton("North"), BorderLayout.NORTH);
frame.add(new JButton("South"), BorderLayout.SOUTH);
frame.add(new JButton("East"), BorderLayout.EAST);
frame.add(new JButton("West"), BorderLayout.WEST);
frame.add(new JTextArea("Center Content"), BorderLayout.CENTER);
GridLayout (网格布局)
将容器划分为一个 MxN 的网格,每个组件占据一个大小相等的单元格。
JPanel panel = new JPanel(new GridLayout(3, 2)); // 3行2列
panel.add(new JLabel("Name:"));
panel.add(new JTextField());
panel.add(new JLabel("Password:"));
panel.add(new JPasswordField());
panel.add(new JButton("Login"));
panel.add(new JButton("Cancel"));
GridBagLayout (网格包布局)
最强大也最复杂的布局,它允许组件跨越多行多列,并可以设置对齐方式和填充方式,通常使用 GridBagConstraints 对象来配置每个组件的约束。
示例:一个登录窗口
import javax.swing.*;
import java.awt.*;
public class GridBagLayoutExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("GridBagLayout Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5); // 组件间距
// Username
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.EAST; // 在网格单元内靠右对齐
frame.add(new JLabel("Username:"), gbc);
gbc.gridx = 1;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.HORIZONTAL; // 水平填充
gbc.weightx = 1.0; // 在水平方向上分配额外的空间
frame.add(new JTextField(20), gbc);
// Password
gbc.gridx = 0;
gbc.gridy = 1;
gbc.fill = GridBagConstraints.NONE; // 不填充
gbc.weightx = 0; // 重置权重
frame.add(new JLabel("Password:"), gbc);
gbc.gridx = 1;
gbc.gridy = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1.0;
frame.add(new JPasswordField(20), gbc);
// Login Button
gbc.gridx = 0;
gbc.gridy = 2;
gbc.gridwidth = 2; // 跨2列
gbc.fill = GridBagConstraints.NONE;
gbc.anchor = GridBagConstraints.CENTER; // 居中
gbc.weightx = 0;
frame.add(new JButton("Login"), gbc);
frame.pack();
frame.setVisible(true);
});
}
}
事件处理机制
监听器接口
每个组件都有对应的事件类型和监听器接口。
JButton->ActionEvent->ActionListenerJTextField->ActionEvent(当用户按回车时) ->ActionListenerJCheckBox,JRadioButton->ItemEvent->ItemListenerJList,JComboBox->ListSelectionEvent->ListSelectionListener
匿名内部类实现监听器
这是最常见的方式,直接在添加监听器的地方创建一个匿名的类来实现接口。
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked!");
}
});
Lambda 表达式 (Java 8+)
从 Java 8 开始,可以使用 Lambda 表达式让代码更简洁。actionPerformed 方法只有一个参数 ActionEvent e,且方法体只有一行,非常适合用 Lambda 简化。
// 传统方式
button.addActionListener(e -> {
System.out.println("Button clicked with Lambda!");
});
// 如果方法体只有一行,可以省略花括号和分号
button.addActionListener(e -> System.out.println("Button clicked!"));
高级特性
多线程与 SwingWorker
严禁在事件分发线程 中执行耗时操作(如网络请求、文件读写、复杂计算),否则会导致整个 GUI 界面“冻结”(无响应)。
SwingWorker 是一个专门用于在后台线程执行耗时任务,并能安全地更新 GUI 的工具。
示例:一个带进度条的下载任务
// 这是一个简化的概念性代码
class DownloadTask extends SwingWorker<Void, Integer> {
@Override
protected Void doInBackground() throws Exception {
for (int i = 0; i <= 100; i++) {
// 模拟耗时操作
Thread.sleep(50);
// 发布进度更新
publish(i);
}
return null;
}
@Override
protected void process(List<Integer> chunks) {
// 在EDT中更新进度条
int latestProgress = chunks.get(chunks.size() - 1);
progressBar.setValue(latestProgress);
}
@Override
protected void done() {
// 在EDT中执行任务完成后的操作
if (isCancelled()) {
label.setText("Download cancelled.");
} else {
label.setText("Download complete!");
}
}
}
// 使用时
// DownloadTask task = new DownloadTask();
// task.execute();
自定义绘制
你可以通过继承 JComponent 并重写其 paintComponent(Graphics g) 方法来自定义组件的绘制。
import javax.swing.*;
import java.awt.*;
public class CustomDrawing extends JPanel {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 始终先调用父类方法
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 绘制一个红色的圆
g2d.setColor(Color.RED);
g2d.fillOval(50, 50, 100, 100);
// 绘制一条蓝色的线
g2d.setColor(Color.BLUE);
g2d.drawLine(0, 0, getWidth(), getHeight());
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Custom Drawing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new CustomDrawing());
frame.setSize(300, 300);
frame.setVisible(true);
});
}
}
实战项目:一个简单的记事本
这个项目将综合运用前面学到的知识。
功能:
- 创建一个窗口,包含一个文本区域。
- 创建一个菜单栏,包含“文件”菜单。
- “文件”菜单下有“新建”、“打开”、“保存”和“退出”选项。
- 实现打开和保存文本文件的功能。
代码结构:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
public class SimpleNotepad {
private JFrame frame;
private JTextArea textArea;
private JMenuBar menuBar;
private JMenu fileMenu;
private JMenuItem newItem, openItem, saveItem, exitItem;
public SimpleNotepad() {
createAndShowGUI();
}
private void createAndShowGUI() {
// 1. 创建主窗口
frame = new JFrame("Simple Notepad");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
// 2. 创建文本区域
textArea = new JTextArea();
// 将文本区域放入带滚动条的面板
JScrollPane scrollPane = new JScrollPane(textArea);
frame.add(scrollPane, BorderLayout.CENTER);
// 3. 创建菜单栏
menuBar = new JMenuBar();
frame.setJMenuBar(menuBar);
// 4. 创建文件菜单
fileMenu = new JMenu("File");
menuBar.add(fileMenu);
// 5. 创建菜单项并添加到文件菜单
newItem = new JMenuItem("New");
openItem = new JMenuItem("Open");
saveItem = new JMenuItem("Save");
exitItem = new JMenuItem("Exit");
fileMenu.add(newItem);
fileMenu.add(openItem);
fileMenu.add(saveItem);
fileMenu.addSeparator(); // 添加分隔线
fileMenu.add(exitItem);
// 6. 为菜单项添加事件监听器
newItem.addActionListener(e -> textArea.setText(""));
openItem.addActionListener(e -> openFile());
saveItem.addActionListener(e -> saveFile());
exitItem.addActionListener(e -> System.exit(0));
// 7. 显示窗口
frame.setVisible(true);
}
private void openFile() {
JFileChooser fileChooser = new JFileChooser();
int returnValue = fileChooser.showOpenDialog(frame);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
textArea.read(reader, null); // JTextArea 有直接读取文件的便捷方法
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
private void saveFile() {
JFileChooser fileChooser = new JFileChooser();
int returnValue = fileChooser.showSaveDialog(frame);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
textArea.write(writer); // JTextArea 有直接写入文件的便捷方法
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(SimpleNotepad::new);
}
}
学习资源与最佳实践
学习资源
- 官方文档: Java Swing Tutorial (Oracle) - 最权威、最全面的资料。
- 书籍:
- 《Java Swing (第二版)》 - 经典 Swing 入门书籍。
- 《Core Java Volume II—Advanced Features》 - 也有详细的 Swing 章节。
- 在线教程: Swing Tutorial (GeeksforGeeks), Java Swing Tutorial (w3schools) 等。
- GUI 设计器:
- NetBeans: 内置了非常优秀的 Matisse GUI 设计器,可以通过拖拽方式快速构建界面,并自动生成代码。
- IntelliJ IDEA: 也提供了强大的 GUI 设计器支持。
最佳实践
- 始终在 EDT 中操作 GUI: 使用
SwingUtilities.invokeLater()来启动你的 GUI。 - 不要阻塞 EDT: 耗时任务放在单独的线程中执行,并使用
SwingWorker或javax.swing.Timer来与 EDT 安全交互。 - 优先使用布局管理器: 避免使用绝对定位 (
setBounds)。 - 分离逻辑和界面: 尽量将业务逻辑与 GUI 创建代码分开,可以使用 MVC 模式,或者至少将界面初始化代码封装在单独的方法中。
- 使用
JPanel进行逻辑分组: 将功能相关的组件放在同一个JPanel中,并为该JPanel设置合适的布局,可以简化复杂的界面布局。 - 善用
BorderLayout作为顶层布局: 对于主窗口,通常将主要的JPanel或JScrollPane放在BorderLayout.CENTER,其他工具栏、状态栏等放在其他区域。 - 熟悉常用组件和布局:
JPanel,JButton,JTextField,JLabel,JScrollPane,FlowLayout,BorderLayout,GridLayout,GridBagLayout是最常用的,必须熟练掌握。
这份教程为你提供了 Java Swing 的全景图,从基础开始,多写多练,你很快就能掌握这门强大的技术,并开发出美观实用的桌面应用程序,祝你学习愉快!
