Swing 是一个为Java设计的GUI工具包。Swing是JAVA基础类的一部分。Swing包括了图形用户界面(GUI)器件如:文本框,按钮,分隔窗格和表。
Swing提供许多比AWT更好的屏幕显示元素。它们用纯Java写成,所以同Java本身一样可以跨平台运行,这一点不像AWT。它们是JFC的一部分。它们支持可更换的面板和主题(各种操作系统默认的特有主题),然而不是真的使用原生平台提供的设备,而是仅仅在表面上模仿它们。这意味着你可以在任意平台上使用JAVA支持的任意面板。轻量级组件的缺点则是执行速度较慢,优点就是可以在所有平台上采用统一的行为。
Swing gui包含了两种元素:组件和容器。它们的区别主要是在概念上的。因为每个容器也都是组件。组件是单独的控制元素,例如按键或者文本编辑框。组件要放到容器中才能显示出来,由于容器也是组件,因此容器也可放到别的容器中。故组件和容器构成了包含层级关系。
Swing的组件继承于JComponent类。JComponent类提供了所有组件都需要的功能。比如,支持可更换的视觉效果。JComponent继承于AWT的类Component及其子类Container。常见的组件有标签JLabel、按键JButton、输入框JTextField、复选框JCheckBox、列表JList。
容器是一种可以包含组件的特殊组件。Swing中有两大类容器。一类是重量级容器,或者称为顶层容器(top-level container),它们不是继承于JComponent。它们包括JFrame,JApplet,JWindow,JDialog。它们的最大特点是不能被别的容器包含,只能作为界面程序的最顶层容器来包含其它组件。第二类容器是轻量级容器,或者称为中间层容器,它们继承于JComponent,包括JPanel,JScrollPane等。中间层容器用来将若干个相关联的组件放在一起。由于中间层容器继承于JComponent,因此它们本身也是组件,它们可以(也必须)包含在其它的容器中。
Swing的程序界面图
顶层容器JFrame可独立存在,可被移动,也可被最大化和最小化,有标题栏、边框,可添加菜单栏。中间层容器JPanel不能独立存在,必须包含在另一个容器中。
布局管理器控制着容器中组件的位置。当向容器中增加组件时,需要给容器设置一种布局管理器,让它来管理容器中各个组件的位置,即排列布局方式。Java提供了若干种布局管理器。
布局管理器 | 特性 |
---|---|
FlowLayout | 流式布局管理器,是从左到右,中间放置,一行放不下就换到另外一行。 |
BorderLayout | 这种布局管理器分为东、南、西、北、中心五个方位。 |
GridLayout | 网格式布局 |
GridBagLayout | 网格式布局,可以放置不同大小的组件 |
BoxLayout | 把组件水平或者竖直排在一起 |
SpringLayout | 按照一定的约束条件来组织组件 |
各种布局管理器效果一览
import javax.swing.*;
class SwingDemo {
private static void createAndShowGUI() {
// Create a new JFrame container.
JFrame jfrm = new JFrame("A Simple Swing Application");
// Give the frame an initial size.
jfrm.setSize(275, 100);
// Terminate the program when the user closes the application.
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Create a text-based label.
JLabel jlab = new JLabel(" Swing defines the modern Java GUI.");
// Add the label to the content pane.
jfrm.getContentPane().add(jlab);
// Display the frame.
jfrm.setVisible(true);
}
public static void main(String args[]) {
// Create the frame on the event dispatching thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
程序的第一句话是import语句:
import javax.swing.*;
这是因为javax.swing包中包含了Swing中的组件和容器。
接下来,程序定义了SwingDemo类以及它的默认构造函数,其中创建了图形程序中的全部内容。构造函数中首条语句:
JFrame jfrm = new JFrame("A Simple Swing Application");
创建了JFrame对象jfrm。JFrame是顶层容器,带有最小化、最大化、关闭按钮。并将字符串传入到JFrame的构造函数中,作为窗口的标题。
jfrm.setSize(275, 100);
设置了窗口的宽和高的值,单位是像素。
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
默认情况下,关闭顶层窗口并不会关闭应用程序,而只是把窗口从屏幕上移除。但是大多数情况不需要这种行为,关闭窗口意味着关闭整个应用程序。如果需要这样,可以调用setDefaultCloseOperation()函数。
这个方法的一般形式是:
void setDefaultCloseOperation(int what);
传入参数what决定了当窗口关闭时会发生什么。除了JFrame.EXIT_ON_CLOSE,其它的包括:
JFrame.DISPOSE_ON_CLOSE
JFrame.HIDE_ON_CLOSE
JFrame.DO_NOTHING_ON_CLOSE
从它们的名字里我们就可以猜出含义。这些常量定义在WindowConstants
中,它是在javax.swing
中定义的接口(interface),JFrame实现了它。
下面的代码创建了JLable组件,
JLabel jlab = new JLabel(" Swing defines the modern Java GUI.");
JLabel组件的作用是显示字符串或者图片,它只能输入信息,没有办法接受输入。
jfrm.getContentPane().add(jlab);
所有的顶层容器都包含一个叫content pane(内容面板)的容器(中间层容器,默认类型为JPanel)来存储组件。为了将组件加到frame中,需要将它加到frame的content pane中。getContentPane()
函数的原型是
Container JFrame.getContentPane()
其中Container
是AWT中定义的容器类,JComponent
是它的直接子类,而JPanel
是JComponent
的直接子类。这里实际上返回的是一个JPanel,即content pane内容面板是一个JPanel对象。
Container的add()方法有很多(重载)版本,这里使用的是
Component Container.add(Component comp)
默认情况下,JFrame的content pane的布局管理器的类型是BorderLayout。BorderLayout布局管理器将容器分为东、南、西、北、中心五个方位,上面版本的add()函数将组件放入到中心位置。当组件加入到容器的中心位置,它的大小将调整到充满中心。其它版本的add()函数可以将组件加入到容器中的其它位置。
在JDK 5之后上面的代码可以写成
jfrm.add(jlab);
需要清楚jlab不是直接增加到JFrame中,而是JFrame包含的content pane中,上面的写法只是为了方便而提供的。而且,这样写就不需要接触AWT中的容器类型Container了。但是目前还有很多代码是老的写法的代码。为了了解背后的原理,我们还是以老的方式来增加控件。
jfrm.setVisible(true);
将jfrm
显示出来,默认情况下JFrame是不显示的。
最后在main()函数中,需要这样写
public static void main(String args[]) {
// Create the frame on the event dispatching thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
那为什么不能简单的在main()函数中直接调用createAndShowGUI()呢?
public static void main(String args[]) {
// Create the frame on the main thread
// which is not event dispatching thread.
createAndShowGUI();
}
如果这样写,可能发现程序也能够运行,但是基于线程安全的考虑,不能这样写。
首先,线程方面的知识告诉我们从不同的线程中访问同一个数据会造成数据出现问题。对于窗口程序的界面元素它们实际上也是数据(即Swing窗口元素对象,只是绘图代码将它们绘制出来了),为了避免在多个线程中对Swing窗口对象进行操作也会出现各种各样的问题(比如创建、删除、更新界面元素以及进行事件处理),需要在整个程序的某一个线程中处理界面操作。这个线程就是事件派发线程。这个线程是系统自动创建的。而main()函数的所在的线程是另外的线程,在包括main()函数在内的其它线程中访问Swing对象是不好的,可能会出现问题。因此,提供了invokeLater等方法,它们会将代码转到事件派发线程中来执行。
SwingUtilities.invokeLater()
函数的样子是
static void invokeLater(Runnable obj)
,
而Runnable
是定义在java.lang
包中的接口,代码
new Runnable() {
public void run() {
createAndShowGUI();
}
}
是Java中定义匿名内部类的写法,它会创建一个匿名对象,该对象实现了Runnable
接口。
参考Java多线程开发系列之番外篇:事件派发线程—EventDispatchThread
以上代码就是一个简单窗口程序,还有一种写法是把创建窗口的代码写到SwingDemo的构造函数中。在main()函数中创建SwingDemo对象即可。
import javax.swing.*;
class SwingDemo {
SwingDemo() {
// Create a new JFrame container.
JFrame jfrm = new JFrame("A Simple Swing Application");
// Give the frame an initial size.
jfrm.setSize(275, 100);
// Terminate the program when the user closes the application.
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Create a text-based label.
JLabel jlab = new JLabel(" Swing defines the modern Java GUI.");
// Add the label to the content pane.
jfrm.getContentPane().add(jlab);
// Display the frame.
jfrm.setVisible(true);
}
public static void main(String args[]) {
// Create the frame on the event dispatching thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new SwingDemo();
}
});
}
}
参考文档
JFrame(框架)中添加和设置JPanel(面板)的方法
JFrame, JPanel, JComponent
import java.awt.*;
import javax.swing.*;
public class BorderLayoutDemo {
private static void addComponentsToPane(Container pane) {
// content pane默认是BorderLayout,因此这里可以省略
//pane.setLayout(new BorderLayout());
// 构造函数BorderLayout(int horizontalGap, int verticalGap)
// 设置组件的水平和竖直方向上间隔
//pane.setLayout(new BorderLayout(10, 20));
JButton button = new JButton("Button 1 (PAGE_START)");
pane.add(button, BorderLayout.PAGE_START);
//Make the center component big, since that's the
//typical usage of BorderLayout.
button = new JButton("Button 2 (CENTER)");
button.setPreferredSize(new Dimension(200, 100));
pane.add(button, BorderLayout.CENTER);
button = new JButton("Button 3 (LINE_START)");
pane.add(button, BorderLayout.LINE_START);
button = new JButton("Long-Named Button 4 (PAGE_END)");
pane.add(button, BorderLayout.PAGE_END);
button = new JButton("5 (LINE_END)");
pane.add(button, BorderLayout.LINE_END);
}
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("BorderLayout");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Set up the content pane.
addComponentsToPane(frame.getContentPane());
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
import java.awt.*;
import javax.swing.*;
public class FlowLayoutDemo {
private static void addComponentsToPane(Container pane) {
pane.setLayout(new FlowLayout()); // content pane默认是BorderLayout
pane.add(new JButton("Button 1"));
pane.add(new JButton("Button 2"));
pane.add(new JButton("Button 3"));
pane.add(new JButton("Long-Named Button 4"));
pane.add(new JButton("5"));
}
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("FlowLayoutDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Set up the content pane.
addComponentsToPane(frame.getContentPane());
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
参考文档:
JavaSwing_1.1: FlowLayout(流式布局)
JavaSwing_1.7: BorderLayout(边界布局)
How to Use BorderLayout
How to Use FlowLayout
中间容器设置了某种layout,需要观察容器中添加的组件(包括中间容器)的位置,如果组件的背景没有边框,而且颜色和窗口是一样的,就不好区分各个组件的位置,因此可以为不同的组件设置不同的背景色,Swing中设置组件的背景色
comp.setOpaque(true); // 设置组件不透明
comp.setBackground(Color.RED) // 设置组件的背景色为某种颜色
以下代码展示了JButton的使用
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
//1. 监听者类ButtonListener实现ActionListener接口
class ButtonListener implements ActionListener{
public void actionPerformed(ActionEvent e) { // 4. 当按钮btnUp或btnDown被按下,此函数调用
String name = ((JButton)e.getSource()).getText();
System.out.println("Button " + name + " is clicked");
}
}
public class ButtonDemo{
private ButtonListener bl;
ButtonDemo(){
bl = new ButtonListener(); // 2. 创建监听者类对象bl
JFrame frame = new JFrame("ButtonDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
// 等价于frame.getContentPane().setLayout(new FlowLayout());
JButton btnUp = new JButton("Up");
JButton btnDown = new JButton("Down");
btnUp.addActionListener(bl); // 3.1 注册bl到btnUp按钮中
btnDown.addActionListener(bl); // 3.2 注册bl到btnDown按钮中
frame.add(btnUp);
frame.add(btnDown);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ButtonDemo();
}
});
}
}
如代码所示,ButtonListener
类,不妨称为监听者类,实现ActionListener
接口,创建该类的对象bl
并注册到button中,那么当button被按下,就会调用该对象的actionPerformed()
方法。利用这种机制,让按钮自己负责按下事件的产生和通知,而具体处理代码可以和按钮分离,这种处理事件驱动的方式非常方便。
ActionListener
接口的actionPerformed()
函数的参数变量的类型是ActionEvent
,通过传入ActionEvent
类型的参数对象我们可以获取很多按钮按下事件的信息。
比如,getSource()
方法可以获取事件发生在哪个对象上,因为其返回类型是Object
,故要强制类型转换。
而附加在button对象上的action command字符串可以用来标志button对象。在actionPerformed()
回调函数中可以用getActionCommand()
方法可以获取该字符串。setActionCommand()
方法可以设置新的action command字符串。默认情况下,这个字符串等于button对象上显示的文本。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class ButtonListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("Up")) { // JButton的action command默认是JButton上显示的文本
System.out.println("Button Up is clicked");
}else {
System.out.println("Button Down is clicked");
}
}
}
public class ButtonDemo{
private ButtonListener bl;
ButtonDemo(){
bl = new ButtonListener();
JFrame frame = new JFrame("ButtonDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
// 等价于frame.getContentPane().setLayout(new FlowLayout());
JButton btnUp = new JButton("Up");
JButton btnDown = new JButton("Down");
btnUp.addActionListener(bl);
btnDown.addActionListener(bl);
frame.add(btnUp);
frame.add(btnDown);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ButtonDemo();
}
});
}
}
ActionLisner
接口有时监听者类不需要新建,让代码中现有类实现ActionListener
接口即可作为监听者类,在下面的代码中,我们就是让ButtonDemo
类同时充当监听者类。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonDemo implements ActionListener{
public void actionPerformed(ActionEvent e) {
String name = ((JButton)e.getSource()).getText();
System.out.println("Button " + name + " is clicked");
}
ButtonDemo(){
JFrame frame = new JFrame("ButtonDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
// 等价于frame.getContentPane().setLayout(new FlowLayout());
JButton btnUp = new JButton("Up");
JButton btnDown = new JButton("Down");
btnUp.addActionListener(this);
btnDown.addActionListener(this);
frame.add(btnUp);
frame.add(btnDown);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ButtonDemo();
}
});
}
}
这里,ButtonDemo
类本身实现了ActionListener
接口,因此它自己就可以作为监听者对象注册到button中。在成员函数中this
关键字指的就是当前调用此成员函数的对象自身,这里构造函数中的this
就是指当前正在初始化的ButtonDemo
对象自身。
这种写法比单独定义监听者类的便利之处在于,当我们需要在actionPerformed()
中访问当前窗口的某些控件时,我们可以将那些控件作为ButtonDemo
类的成员变量,因为actionPerformed()
现在也是ButtonDemo
的成员函数,因此可以直接访问那些控件成员变量。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonDemo implements ActionListener{
JTextField tf; // tf是ButtonDemo类的成员变量
public void actionPerformed(ActionEvent e) {
String name = ((JButton)e.getSource()).getText();
tf.setText("Button " + name + " is clicked"); // 直接访问类的成员变量tf
}
ButtonDemo(){
JFrame frame = new JFrame("ButtonDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
// 等价于frame.getContentPane().setLayout(new FlowLayout());
JButton btnUp = new JButton("Up");
JButton btnDown = new JButton("Down");
tf = new JTextField(20);
btnUp.addActionListener(this);
btnDown.addActionListener(this);
frame.add(btnUp);
frame.add(btnDown);
frame.add(tf);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ButtonDemo();
}
});
}
}
还有一种编写接口监听按钮按下事件的方法是用内部类,或者匿名内部类。
下面的代码演示了单行文字输入控件JTextField
的使用。如果当前焦点在JTextField
中,当按下回车键时,JTextField
也会像JButton
那样产生ActionEvent
事件,如果有监听者对象注册了事件监听,那么也会调用监听者的actionPerformed()
方法。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JTextFieldDemo implements ActionListener{
JTextField leftJtf;
JTextField rightJtf;
JLabel resultJlb;
JTextFieldDemo(){
JFrame frame = new JFrame("JTextFieldDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
leftJtf = new JTextField(5);
rightJtf = new JTextField(5);
leftJtf.setActionCommand("leftJtf");
leftJtf.addActionListener(this);
rightJtf.setActionCommand("rightJtf");
rightJtf.addActionListener(this);
JButton jBtn = new JButton("=");
jBtn.addActionListener(this);
resultJlb = new JLabel();
frame.add(leftJtf);
frame.add(new JLabel("+"));
frame.add(rightJtf);
frame.add(jBtn);
frame.add(resultJlb);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new JTextFieldDemo();
}
});
}
@Override
public void actionPerformed(ActionEvent ae) {
// TODO Auto-generated method stub
if(ae.getActionCommand().equals("=")) {
resultJlb.setText(leftJtf.getText() + rightJtf.getText());
}else if(ae.getActionCommand().equals("leftJtf")) {
leftJtf.setText("");
}else if(ae.getActionCommand().equals("rightJtf")) {
rightJtf.setText("");
}
}
}
参考文档:
JavaSwing_2.2: JButton(按钮)
JavaSwing_2.6: JTextField(文本框)
内部类是定义在另一个类中的类,内部类的最大特性是在它的成员函数中可以直接访问定义它的外部类的数据和方法成员。
(a) A类和Test类是独立的类
class Test {
...
}
class A {
...
}
(b)A类定义在Test类中,是Test的内部类
class Test {
...
// Inner class
class A {
...
}
}
(c)InnerClass类是OuterClass的内部类,
// OuterClass.java: inner class demo
class OuterClass {
private int data;
/** A method in the outer class */
public void m() {
// Do something
}
// An inner class
class InnerClass {
/** A method in the inner class */
public void mi() {
// Directly reference data and method
// defined in its outer class
data++;
m();
}
}
}
下面程序中, ButtonListener
为内部类。它的成员函数可以直接访问ButtonDemo
的成员变量tf。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonDemo{
private ButtonListener bl;
private JTextField tf;
/*** 内部类ButtonListener ***/
class ButtonListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("Up")) { // JButton的action command默认是JButton上显示的文本
tf.setText("Button Up is clicked");
}else {
tf.setText("Button Down is clicked");
}
}
}
ButtonDemo(){
bl = new ButtonListener();
JFrame frame = new JFrame("ButtonDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
// 等价于frame.getContentPane().setLayout(new FlowLayout());
JButton btnUp = new JButton("Up");
JButton btnDown = new JButton("Down");
tf = new JTextField(20);
btnUp.addActionListener(bl);
btnDown.addActionListener(bl);
frame.add(btnUp);
frame.add(btnDown);
frame.add(tf);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ButtonDemo();
}
});
}
}
匿名内部类是一种直接实例化一个内部类对象,而不给出这个内部类类名的使用方式。下面的代码y用匿名内部类,实现了ActionListener
接口。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonDemo{
private JTextField tf;
private ActionListener bl;
ButtonDemo(){
JFrame frame = new JFrame("ButtonDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
// 等价于frame.getContentPane().setLayout(new FlowLayout());
JButton btnUp = new JButton("Up");
JButton btnDown = new JButton("Down");
tf = new JTextField(20);
// 匿名内部类,实现了ActionListener接口
bl = new ActionListener() {
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if (e.getActionCommand().equals("Up")) {
tf.setText("Button Up is clicked");
}else {
tf.setText("Button Down is clicked");
}
}
};
btnUp.addActionListener(bl);
btnDown.addActionListener(bl);
frame.add(btnUp);
frame.add(btnDown);
frame.add(tf);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ButtonDemo();
}
});
}
}
可以看到new
关键字后面的是该匿名内部类要实现的接口的名字,而后在一对大括号{}
中给出该接口中的抽象函数的实现。
bl = new ActionListener() {
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if (e.getActionCommand().equals("Up")) {
tf.setText("Button Up is clicked");
}else {
tf.setText("Button Down is clicked");
}
}
};
对于创建一般匿名内部类的语法形式,
new 接口名/父类名() {
// 实现抽象方法或者重写方法
}
关于匿名内部类:
很多都参考了网络上的文章,比如Java Swing 介绍,Java Swing 图形界面开发(目录),java中AWT和SWing的区别与联系,Java中awt和swing的关系和区别,官方的Swing教程以及两本英文书《Java A Beginner’s Guide Sixth Edition》《Introduction To Java Programming Comprehensive Version 10Th Edition》。尤其是《Java A Beginner’s Guide Sixth Edition》,很多内容来自于它,这本书讲的虽然不是非常全,但是内容非常流畅,如果有一定编程基础,学习这本书会非常快的掌握Java基础。网络上还有其它专门介绍Swing的文章,比如java Gui编程 事件监听机制等。以上如有侵权,请联系删除。