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形式发布程序,这些内容将在后续章节介绍。