杰瑞科技汇

Java Swing 教程,从零开始怎么学?

Java Swing 完整教程

目录

  1. 什么是 Swing?
  2. Swing 的核心概念
    • AWT vs. Swing
    • MVC (Model-View-Controller) 设计模式
    • 顶层容器
    • 中间容器
    • 基本组件
    • 布局管理器
    • 事件处理
  3. 第一个 Swing 程序:Hello World
  4. 常用组件详解
    • 标签、按钮、文本框
    • 列表框、下拉框
    • 单选按钮、复选框
    • 菜单栏、工具栏
    • 表格
  5. 布局管理器详解
    • FlowLayout (流式布局)
    • BorderLayout (边界布局)
    • GridLayout (网格布局)
    • BoxLayout (箱式布局)
    • GridBagLayout (网格包布局 - 最强大也最复杂)
    • GroupLayout ( GroupLayout - 在 GUI 设计器中常用)
  6. 事件处理机制
    • 监听器接口
    • 匿名内部类实现监听器
    • Lambda 表达式 (Java 8+)
  7. 高级特性
    • 多线程与 SwingWorker
    • 自定义绘制
    • JOptionPane (消息对话框)
    • JFileChooser (文件选择器)
  8. 实战项目:一个简单的记事本
  9. 学习资源与最佳实践

什么是 Swing?

Swing 是 Java 基础类库的一部分,它是一个用于开发图形用户界面的工具包,使用 Swing,你可以创建独立于平台的窗口应用程序,这些应用程序可以在任何安装了 Java 虚拟机的操作系统上运行,而无需修改代码。

Java Swing 教程,从零开始怎么学?-图1
(图片来源网络,侵删)

核心特点:

  • 轻量级: 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 模式,这使得代码结构更清晰、更易于维护。

Java Swing 教程,从零开始怎么学?-图2
(图片来源网络,侵删)
  • Model (模型): 负责存储数据。JButton 的模型会记录按钮是否被按下。
  • View (视图): 负责组件的视觉呈现。JButton 的外观(颜色、文字、边框)。
  • Controller (控制器): 负责处理用户输入并更新模型,当用户点击 JButton 时,控制器会通知模型状态改变,模型再通知视图进行重绘。

在 Swing 中,你可以将这三者分离,为 JButton 设置一个自定义的模型来改变其行为。

顶层容器

所有 Swing 组件都必须被放置在顶层容器中,顶层容器是应用程序的根窗口。

  • JFrame: 最常用的顶层容器,代表一个独立的窗口,有标题栏、边框、最小化/最大化/关闭按钮。一个 GUI 应用通常只有一个 JFrame
  • JDialog: 一个对话框窗口,通常用于从用户那里获取信息或显示消息,它必须依附于另一个窗口(通常是 JFrame)。
  • JApplet: 用于在网页中运行的 Java 小程序(现已不推荐使用)。

中间容器

用于组织和布局其他组件。

  • JPanel: 最常用的中间容器,没有任何特殊的装饰,主要配合布局管理器来组织组件。
  • JScrollPane: 提供滚动功能的容器,当你放入的组件内容超出可视区域时,会出现滚动条。
  • JSplitPane: 一个可以水平或垂直分割两个组件的容器,用户可以拖动分割线来调整两个组件的大小。
  • JTabbedPane: 创建带有标签页的容器,每个标签页可以包含一组组件。

基本组件

构成 GUI 的具体元素。

Java Swing 教程,从零开始怎么学?-图3
(图片来源网络,侵删)
  • 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 表示按钮被点击)。
  • 监听器: 一个实现了特定监听器接口的对象,它“监听”来自特定事件源的事件,并在事件发生时执行相应的代码。

要让一个按钮在点击时执行某些操作,你需要:

  1. 为按钮添加一个 ActionListener 监听器。
  2. 在监听器的 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);
    }
}

代码解释:

  1. SwingUtilities.invokeLater(...): 这是启动 Swing 应用的标准方式,它确保 GUI 的创建和更新都在事件分发线程 上执行,这是线程安全的,对于小型应用,你可能看不到直接调用的区别,但对于任何涉及用户交互的复杂应用,这都是必须遵守的规则。
  2. JFrame: 我们的应用窗口。
  3. JPanel: 我们使用 BorderLayout 来布局,将标签放在中间。
  4. JLabel: 显示文本的组件。
  5. 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 -> ActionListener
  • JTextField -> ActionEvent (当用户按回车时) -> ActionListener
  • JCheckBox, JRadioButton -> ItemEvent -> ItemListener
  • JList, 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);
        });
    }
}

实战项目:一个简单的记事本

这个项目将综合运用前面学到的知识。

功能:

  1. 创建一个窗口,包含一个文本区域。
  2. 创建一个菜单栏,包含“文件”菜单。
  3. “文件”菜单下有“新建”、“打开”、“保存”和“退出”选项。
  4. 实现打开和保存文本文件的功能。

代码结构:

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 设计器支持。

最佳实践

  1. 始终在 EDT 中操作 GUI: 使用 SwingUtilities.invokeLater() 来启动你的 GUI。
  2. 不要阻塞 EDT: 耗时任务放在单独的线程中执行,并使用 SwingWorkerjavax.swing.Timer 来与 EDT 安全交互。
  3. 优先使用布局管理器: 避免使用绝对定位 (setBounds)。
  4. 分离逻辑和界面: 尽量将业务逻辑与 GUI 创建代码分开,可以使用 MVC 模式,或者至少将界面初始化代码封装在单独的方法中。
  5. 使用 JPanel 进行逻辑分组: 将功能相关的组件放在同一个 JPanel 中,并为该 JPanel 设置合适的布局,可以简化复杂的界面布局。
  6. 善用 BorderLayout 作为顶层布局: 对于主窗口,通常将主要的 JPanelJScrollPane 放在 BorderLayout.CENTER,其他工具栏、状态栏等放在其他区域。
  7. 熟悉常用组件和布局: JPanel, JButton, JTextField, JLabel, JScrollPane, FlowLayout, BorderLayout, GridLayout, GridBagLayout 是最常用的,必须熟练掌握。

这份教程为你提供了 Java Swing 的全景图,从基础开始,多写多练,你很快就能掌握这门强大的技术,并开发出美观实用的桌面应用程序,祝你学习愉快!

分享:
扫描分享到社交APP
上一篇
下一篇