Swing框架概述

Swing是Java中用于开发图形用户界面(GUI)的框架,它是Java标准类库(JDK)的一部分。Swing是基于Java的原生GUI工具包Abstract Window Toolkit(AWT)实现的,但Swing提供了更为强大和灵活的功能。Swing提供了大量的GUI组件,能够实现跨平台GUI开发,Swing框架设计优雅清晰,并具有高度可定制性。

在很多年前,Swing曾经广泛用于企业级系统客户端,不过如今在企业级管理系统领域,客户端/服务器模式(C/S)逐渐被浏览器/服务器模式(B/S)端取代。不过,得益于Java的跨平台性,如今仍有很多专业领域软件使用Swing开发,如Eclipse、Matlab(GUI部分)等。

Swing的特点

平台无关性:Swing是用Java编写的,是跨平台的,能够在所有支持Java的图形界面操作系统上运行。

轻量级控件:与基于对等体控件的“重量级”AWT组件不同,Swing的组件不依赖于操作系统的原生窗口系统。Swing的控件全部使用Java2D重新实现,它们不依赖于操作系统的原生控件,因此被称为“轻量级”控件,Swing控件的外观和行为可以根据需要进行自定义。

可定制性:Swing提供了丰富的接口来允许开发者修改组件的外观和行为。通过LookAndFeel(LAF)机制,开发者可以更换不同的界面主题风格,甚至定制自己的主题。

事件驱动:Swing是基于事件驱动模型的,用户与界面的交互通过事件处理机制来实现。

AWT和Swing的关系

JDK中实际上还有一个比Swing更古老一些的AWT库,AWT是基于窗口对等体实现的GUI控件,简单来说它是操作系统原生控件的一层包装,因此AWT控件有不同平台下表现不同的缺点,而且为了控件的兼容性,不得不保持控件在一个所有平台的最大公约数上,这就很影响软件的跨平台兼容性,也限制了AWT包含的功能。由于AWT的诸多缺点,新的Swing框架选择不使用窗口对等体而是采用Java2D自绘制实现GUI控件,因此Swing不依赖于特定平台的窗体控件,具有良好的跨平台性。

不过这里要注意一个历史遗留问题,Swing是在AWT之上开发的,这两个库之间有一定的耦合性而非完全独立的两个库,例如Swing的JFrame继承了AWT的Frame,Swing的事件处理也完全借用AWT的实现,AWT为Swing提供了一些基础的构件和事件处理支持,这也意味着使用Swing的过程中,也同样可能要使用到AWT库的API。

下图展示了Swing中关键组件的继承层次。

创建Swing应用程序示例

创建工程

这里我们使用WindowsXP SP3操作系统,JDK1.6以及Eclipse Luna版本进行演示。我们在Eclipse IDE中创建一个普通Java工程即可。

工程目录结构如下。

src
  |_ com.gacfox.demo
    |_ Main.java # 程序入口类
    |_ MainFrame.java # 程序主窗体类,继承javax.swing.JFrame

编写GUI代码

我们编写如下代码。

Main.java

package com.gacfox.demo;

import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class Main {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MainFrame mainFrame = new MainFrame();
                mainFrame
                        .setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                mainFrame.setVisible(true);
            }
        });
    }
}

这段代码乍一看可能有点奇怪,为什么我们新创建了一个线程来初始化窗体呢?实际上,这种写法做了一个重要操作,它将创建初始窗体的操作提交到事件分发线程(EDT,即Event Dispatch Thread)以便在Swing应用程序中安全地操作图形用户界面。

我们要注意,在Swing中,创建和更新窗体以及其他GUI控件必须在事件分发线程(EDT)中进行,尽管有时在主线程中直接创建窗体不会立即报错,但这种做法存在隐患。实际上不仅是Swing,大多数GUI框架都有类似的要求。Swing中,GUI控件并没有内部的多线程同步机制,如果在多个线程中同时访问和修改同一个组件,可能会导致组件处于不一致的状态,界面会出现错误或崩溃。如果你从主线程或其他线程中直接修改GUI控件的属性和状态,它们有出现异常或死锁的潜在可能,而EDT则确保了控件的更新是按顺序进行的,不会出现多个线程同时修改组件的情况,通过将所有的更新操作放到EDT中,Swing将保证这些操作不会发生竞争条件。当然,如果你初次接触GUI编程可能无法理解这段话的含义,记住这是个固定写法就行了。

注:有些文章也将SwingUtilities.invokeLater()静态方法写为EventQueue.invokeLater()静态方法,它们是完全等效的,这是历史原因造成的。前者是Swing框架提供的,后者是AWT提供的,它们在此处功能没有区别,可以互相替代使用。

EDT线程内部的代码就很容易理解了,我们创建了MainFrame类的实例,它实际上是一个窗体,setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)设置了窗体关闭时程序退出,setVisible(true)则设置了代码运行到此处时该窗体将显示出来。

MainFrame.java

package com.gacfox.demo;

import javax.swing.JFrame;

public class MainFrame extends JFrame {
    public MainFrame() {
        setSize(300, 200);
        setTitle("Hello, Swing!");
    }
}

MainFrame是我们自定义的窗体类,它继承Swing框架的JFrame,在构造方法中,我们调用了一系列的set函数,设置了窗体的各种属性。

为什么不直接在一个Main类里实例化JFrame,然后编写frame.setSize()之类的调用若干个配置JFrame的API最后setVisible()呢?实际上这不符合面向对象的封装性。我们继承一个控件,可以把这个控件做一系列扩展,然后进行复用,否则只能一次次复制粘贴一堆代码,不够优雅。Swing编程中,涉及扩展一个GUI控件时,我们通常都使用类似上述的继承写法。

运行程序后,我们可以看到窗体被创建。

生成可执行Jar

得益于Eclipse IDE的强大功能,我们可以省去使用JDK自带的命令行工具的麻烦,这里我们可以直接将工程构建并导出为可执行Jar文件。在工程上点击右键,选择Export...选项,然后在弹出的对话框中选择Java -> Runnable JAR file,可以看到如下的导出选项。

在Launch Configuration中我们选择可执行Jar的主类,这里就是我们的com.gacfox.demo.Main类,下面的Export Destination字段我们填写导出的位置,配置完成后点击Finish按钮,IDE会自动编译和构建程序,并生成可执行Jar文件。

我们可以直接将这个可执行Jar文件分发给用户,只要用户安装有版本合适的Java运行时,它就可以直接双击运行该程序,十分方便。实际上,Java程序的发布形式不止于此,我们还可以使用Applet或Java Web Start形式发布程序,这些内容将在后续章节介绍。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。