Java Swing 教程:从入门到精通 – wiki大全


Java Swing 教程:从入门到精通

Java Swing 是 Java Foundation Classes (JFC) 的一部分,它提供了一套丰富的 GUI (Graphical User Interface) 组件,用于构建跨平台的桌面应用程序。尽管现代 Java GUI 开发有 JavaFX 等新选择,但 Swing 依然因其稳定性和庞大的现有代码库而占有一席之地。本教程将带您从 Swing 的基础知识开始,逐步深入到高级主题,助您成为 Swing 开发专家。

第一部分:Swing 入门基础

1. Swing 的核心概念

  • AWT (Abstract Window Toolkit) 与 Swing 的关系: AWT 是早期 Java GUI 库,使用操作系统原生组件,导致“重”组件(Heavyweight Components)在不同平台上外观不一致。Swing 则使用“轻”组件(Lightweight Components),完全由 Java 绘制,实现了跨平台一致的外观和感觉。
  • Swing 组件层次结构: Swing 组件都继承自 JComponent,它是 Container 的子类。理解容器(如 JFrame, JPanel)和组件(如 JButton, JTextField)之间的关系至关重要。
  • M-V-C (Model-View-Controller) 架构: Swing 内部广泛采用 M-V-C 模式。组件的“模型”负责数据,“视图”负责显示,“控制器”负责用户交互。这使得 UI 逻辑与数据分离,提高了可维护性。

2. 第一个 Swing 应用程序:Hello World

“`java
import javax.swing.*; // 导入所有 Swing 类

public class HelloWorldSwing {
public static void main(String[] args) {
// 确保 GUI 更新在事件调度线程 (Event Dispatch Thread, EDT) 上运行
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// 1. 创建顶层容器 JFrame
JFrame frame = new JFrame(“Hello Swing!”);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置关闭操作

            // 2. 创建一个简单的组件 JLabel
            JLabel label = new JLabel("Hello, Swing World!");

            // 3. 将组件添加到 JFrame 的内容面板
            frame.getContentPane().add(label);

            // 4. 设置窗口大小并使其可见
            frame.pack(); // 根据组件的首选大小调整窗口
            frame.setLocationRelativeTo(null); // 居中显示
            frame.setVisible(true);
        }
    });
}

}
“`

关键点:
JFrame: Swing 应用程序的主窗口。
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE): 当窗口关闭时,程序退出。
JLabel: 用于显示文本或图像的非交互式组件。
getContentPane(): 获取 JFrame 的内容面板,所有组件都应添加到此面板。
pack(): 自动调整窗口大小以适应其内容。
setLocationRelativeTo(null): 将窗口放置在屏幕中央。
SwingUtilities.invokeLater(): 非常重要! 确保所有 Swing GUI 操作都在 EDT 上执行,以避免线程安全问题和 UI 冻结。

3. 常用 Swing 组件

  • 顶层容器:
    • JFrame: 主应用程序窗口。
    • JDialog: 弹出式对话框。
    • JApplet: 在浏览器中运行的小程序(现在很少用)。
  • 通用容器:
    • JPanel: 最常用的通用容器,用于组织和分组组件。
    • JScrollPane: 为内容提供滚动条。
    • JSplitPane: 将两个组件分成可调整大小的区域。
    • JTabbedPane: 提供标签页界面。
  • 基本组件:
    • JButton: 按钮。
    • JTextField, JPasswordField: 单行文本输入。
    • JTextArea: 多行文本区域。
    • JCheckBox, JRadioButton: 复选框和单选按钮。
    • JComboBox: 下拉列表。
    • JList: 列表选择。
    • JTable: 表格显示数据。
    • JTree: 树形结构显示数据。
    • JMenu, JMenuItem, JMenuBar: 菜单栏。
    • JFileChooser: 文件选择器。
    • JOptionPane: 标准消息、确认、输入对话框。

4. 布局管理器 (Layout Managers)

Swing 不直接使用像素坐标来定位组件,而是依赖布局管理器来自动排列组件,以适应不同屏幕大小和分辨率。

  • FlowLayout: 默认布局,组件按行从左到右排列,到达边界时换行。
  • BorderLayout: 将容器分为东、西、南、北、中五个区域,每个区域只能放置一个组件。JFrame 的内容面板默认使用此布局。
  • GridLayout: 将组件排列在网格中,每个单元格大小相同。
  • GridBagLayout: 最强大、最灵活但也最复杂的布局管理器,允许组件跨越多个单元格,并控制其对齐、填充和权重。
  • BoxLayout: 沿水平或垂直方向排列组件。
  • CardLayout: 允许在一个容器中堆叠多个组件,每次只显示一个。
  • SpringLayout: 基于组件之间的弹簧约束,非常灵活但使用复杂。

示例:使用 BorderLayoutJPanel

“`java
import javax.swing.;
import java.awt.
; // 导入 AWT 布局相关的类

public class LayoutExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame(“Layout Manager Example”);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setLocationRelativeTo(null);

        // 设置主窗口使用 BorderLayout
        frame.setLayout(new BorderLayout());

        // 添加按钮到不同的区域
        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);

        // 创建一个 JPanel,使用 FlowLayout,并将其添加到 CENTER 区域
        JPanel centerPanel = new JPanel();
        centerPanel.setLayout(new FlowLayout());
        centerPanel.add(new JButton("Center 1"));
        centerPanel.add(new JButton("Center 2"));
        frame.add(centerPanel, BorderLayout.CENTER);

        frame.setVisible(true);
    });
}

}
“`

5. 事件处理 (Event Handling)

用户与 GUI 的交互(点击按钮、输入文本、选择菜单项等)会触发事件。Swing 使用事件监听器 (Event Listeners) 机制来响应这些事件。

  • 事件源 (Event Source): 产生事件的组件(如 JButton)。
  • 事件对象 (Event Object): 封装了事件信息的对象(如 ActionEvent)。
  • 事件监听器 (Event Listener): 实现特定接口(如 ActionListener)的对象,用于处理事件。

示例:按钮点击事件

“`java
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ButtonClickExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame(“Button Click”);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
frame.setLocationRelativeTo(null);

        JButton button = new JButton("Click Me!");
        JLabel messageLabel = new JLabel("No click yet.");

        // 创建一个匿名内部类作为 ActionListener
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                messageLabel.setText("Button clicked!");
                System.out.println("Button was clicked!");
            }
        });

        // 使用 Lambda 表达式 (Java 8+) 简化
        // button.addActionListener(e -> {
        //     messageLabel.setText("Button clicked!");
        //     System.out.println("Button was clicked (lambda)!");
        // });

        JPanel panel = new JPanel();
        panel.add(button);
        panel.add(messageLabel);

        frame.add(panel);
        frame.setVisible(true);
    });
}

}
“`

第二部分:Swing 进阶主题

1. Model-View-Controller (MVC) 模式深入

虽然 Swing 组件内部实现了 MVC,但对于更复杂的自定义组件或数据密集型应用程序,我们通常会手动将业务逻辑、数据模型和视图表示分离。

  • JTableJTree 的模型: JTable 使用 TableModel 接口(或其实现 DefaultTableModel)来管理数据。JTree 使用 TreeModel 来管理树节点。
  • 自定义模型: 当内置模型不满足需求时,可以实现自己的 TableModelTreeModel

2. 自定义组件和绘制 (Custom Painting)

  • 覆盖 paintComponent(): 可以在 JComponent 的子类中覆盖 paintComponent(Graphics g) 方法来进行自定义绘制。Graphics 对象提供了丰富的绘图 API。
  • 双缓冲 (Double Buffering): Swing 组件默认支持双缓冲,以减少闪烁。在自定义绘制时,通常不需要手动实现,但了解其原理有助于优化。
  • Graphics2D: 将 Graphics 对象向下转型为 Graphics2D 可以获得更强大的 2D 图形功能,如抗锯齿、变换、渐变等。

3. 线程与并发 (Concurrency)

  • EDT (Event Dispatch Thread): 所有 GUI 更新必须在 EDT 上进行。耗时的操作(如网络请求、文件 I/O、复杂计算)绝不能在 EDT 上执行,否则会冻结 UI。
  • SwingWorker: 专为解决耗时操作而设计的实用工具类。它允许你在后台线程执行任务,并在任务完成后安全地更新 EDT 上的 UI。

“`java
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class SwingWorkerExample {
private JLabel statusLabel;
private JButton startButton;

public SwingWorkerExample() {
    JFrame frame = new JFrame("SwingWorker Example");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(300, 200);
    frame.setLocationRelativeTo(null);

    statusLabel = new JLabel("Ready.");
    startButton = new JButton("Start Long Task");

    startButton.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            startButton.setEnabled(false); // 禁用按钮
            statusLabel.setText("Task started...");
            new LongTaskWorker().execute(); // 启动后台任务
        }
    });

    JPanel panel = new JPanel();
    panel.add(startButton);
    panel.add(statusLabel);

    frame.add(panel);
    frame.setVisible(true);
}

class LongTaskWorker extends SwingWorker<String, Integer> { // <ResultType, ProgressType>
    @Override
    protected String doInBackground() throws Exception {
        // 这是在后台线程执行的代码
        for (int i = 0; i <= 100; i += 10) {
            TimeUnit.MILLISECONDS.sleep(200); // 模拟耗时操作
            publish(i); // 发布进度更新
        }
        return "Task completed!"; // 返回任务结果
    }

    @Override
    protected void process(List<Integer> chunks) {
        // 这是在 EDT 上执行的代码,处理 publish() 发布的进度
        for (Integer progress : chunks) {
            statusLabel.setText("Progress: " + progress + "%");
        }
    }

    @Override
    protected void done() {
        // 这是在 EDT 上执行的代码,任务完成时调用
        try {
            String result = get(); // 获取 doInBackground 的结果
            statusLabel.setText(result);
        } catch (Exception ex) {
            statusLabel.setText("Task failed: " + ex.getMessage());
        } finally {
            startButton.setEnabled(true); // 重新启用按钮
        }
    }
}

public static void main(String[] args) {
    SwingUtilities.invokeLater(SwingWorkerExample::new);
}

}
“`

4. 高级组件与功能

  • JTable 的定制:
    • 渲染器 (Cell Renderers): 定制单元格的显示方式(如不同颜色、图标)。
    • 编辑器 (Cell Editors): 定制单元格的编辑方式(如下拉列表、日期选择器)。
    • 排序和过滤: TableRowSorterRowFilter 用于表格数据的排序和过滤。
  • JTree 的定制:
    • 渲染器 (Cell Renderers): 定制树节点的显示(如图标、不同字体)。
    • DefaultTreeModelDefaultMutableTreeNode: 构建树结构的基础。
  • 国际化 (Internationalization – i18n): 使用 ResourceBundleLocale 来支持多语言界面。
  • 拖放 (Drag and Drop): TransferHandler API 允许在 Swing 组件之间以及与其他应用程序之间实现拖放功能。

5. 外观和感觉 (Look and Feel)

  • UIManager: Swing 提供了 UIManager 类来设置应用程序的全局外观和感觉。
  • 内置 L&F:
    • CrossPlatformLookAndFeel (Metal/Synth): Java 默认的、跨平台一致的外观。
    • SystemLookAndFeel: 使用操作系统原生的外观(如 Windows、macOS)。
    • NimbusLookAndFeel: 现代、平滑的 Java L&F。
  • 第三方 L&F: 可以集成像 FlatLaf 这样的第三方 L&F 来获得更现代的 UI 效果。

“`java
import javax.swing.*;

public class LookAndFeelExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
try {
// 设置为系统默认的外观
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// 设置为 Nimbus 外观 (需要 Java 6+)
// UIManager.setLookAndFeel(“javax.swing.plaf.nimbus.NimbusLookAndFeel”);
// 设置为 FlatLaf 外观 (需要添加 FlatLaf 库)
// UIManager.setLookAndFeel(“com.formdev.flatlaf.FlatDarculaLaf”);

        } catch (Exception e) {
            e.printStackTrace();
        }

        JFrame frame = new JFrame("Look and Feel");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300, 200);
        frame.setLocationRelativeTo(null);

        JPanel panel = new JPanel();
        panel.add(new JButton("A Button"));
        panel.add(new JCheckBox("A CheckBox"));
        panel.add(new JTextField("A Text Field", 15));

        frame.add(panel);
        frame.setVisible(true);
    });
}

}
“`

6. 持久化数据

  • 文件 I/O: 将应用程序数据保存到文件(文本文件、CSV、XML、JSON)。
  • Properties: 方便地保存和加载键值对配置。
  • SQLite 或 H2 嵌入式数据库: 对于更复杂的数据,可以使用轻量级嵌入式数据库。

第三部分:Swing 最佳实践与技巧

  1. 始终在 EDT 上操作 GUI: 重申!这是 Swing 开发中最重要的规则。
  2. 使用合适的布局管理器: 避免使用 null 布局(绝对定位),因为它使得界面难以适应变化。优先使用 JPanel 和嵌套布局来构建复杂界面。
  3. 遵循 Java 命名约定: 类名使用 CamelCase,变量名使用 camelCase
  4. 清晰的代码结构: 将相关的组件和逻辑封装在自定义的 JPanelJFrame 子类中。
  5. 内存管理: 注意事件监听器的生命周期,避免内存泄漏,尤其是在创建大量组件或频繁添加/移除监听器时。
  6. 错误处理: 为关键操作添加 try-catch 块,并使用 JOptionPane 等显示友好的错误信息。
  7. 可访问性 (Accessibility): 考虑为视觉障碍用户提供文本替代、键盘导航等功能。
  8. 使用 JGoodies FormsMigLayout (第三方): 对于需要更精细控制的布局,这些第三方布局管理器提供了比 Swing 内置布局更强大的功能。
  9. 单元测试: 针对非 GUI 业务逻辑编写单元测试。GUI 自动化测试相对复杂,但也有工具如 Abbot、FEST-Swing 等。
  10. 持续学习与实践: 官方 Swing 教程 (The Java Tutorials – Creating a GUI With JFC/Swing) 是宝贵的资源。通过构建实际的小项目来巩固知识。

总结

Java Swing 提供了一个功能强大且成熟的框架来构建桌面应用程序。从理解其核心概念、熟练使用基本组件和布局管理器入手,到掌握事件处理、并发编程 (SwingWorker) 和高级组件定制,您将能够构建出健壮且用户友好的 Java 桌面应用。尽管新一代 GUI 技术不断涌现,但 Swing 的知识和经验仍然是宝贵的资产。希望这篇教程能帮助您在 Swing 的世界中从入门走向精通!


滚动至顶部