JavaSE-GUI图形用户界面


Java使用AWT和Swing完成图形用户界面编程,其中AWT的全称是抽象窗口工具集(Abstract Window Toolkit),它是SUN最早提供的GUI库,这个GUI库提供了一些基本功能,但这个GUI库的功能比较有限,所以后来又提供了Swing库

GUI概述

图形用户界面(GUI,Graphics User Interface)

JDK1.0时,SUN提供了一套基本的GUI类库,希望在所有平台下都能运行,这套基本类库被称为AWT,为Java应用程序提供了基本的图形组件.AWT是窗口框架,它从不同平台的窗口系统中抽取共同组件,当程序运行时,将这些组件的创建和动作委托给程序所在的运行平台.简而言之,当时用AWT编写图形界面应用时,程序仅指定了界面组件的位置和行为,并未提供真正的实现,JVM调用操作系统本地的图形界面来创建和平台一致的对等体.使用AWT创建的图形界面应用和所在的运行平台有相同的界面风格

1996年,Netscape公司开发了一套工作方式完全不同的GUI库,简称IFC(Internet Foundation Classes),这套GUI库的所有图形界面组件,例如文本框/按钮等都是绘制在空白窗口上的,只有窗口本身需要借助于操作系统的窗口实现.IFC真正实现了各种平台上的界面一致性.不久,SUN和Netscape合作完善了这种方法,并创建了一套新的用户界面库:Swing

AWT/Swing/辅助功能API/2D API以及拖放API共同组成了JFC(Java Foundation Classes,Java基础类库),其中Swing组件全面替代了Java1.0中的AWT组件,但保留了Java1.1中的AWT事件模型.总体上,AWT是图形用户界面编程的基础,Swing组件替代了绝大部分的AWT组件,对AWT图形用户界面编程有极好的补充和加强

Swing并没有完全替代AWT,而是建立在AWT基础之上.Swing仅提供了能力更强大的用户界面组件,即使是完全采用Swing编写的GUI程序,也依然需要使用AWT的事件处理机制.AWT组件在Swing里也有对应的实现,二者用法基本相似

AWT

所有和AWT编程相关的类都放在java.awt包以及它的子包中,AWT编程有两个基类:Component和MenuComponent

AWT容器

容器是Component的子类,因此容器对象本身也是一个组件,具有组件的所有性质,可以调用Component类的所有方法

Component类常用方法
常用方法 返回值类型 说明
setLocation(int x,int y) void 设置组件的位置
setSize(int width,int height) void 设置组件的大小
setBounds(int x,int y,int width,int height) void 同时设置组件的位置/大小
setVisible(boolean b) void 设置该组件的可见性

AWT主要提供了两种主要的容器类型

  • Window:可独立存在的顶级窗口
  • Panel:可作为容器容纳其他组件,但不能独立存在,必须被添加到其他容器中
    • Frame对象有标题,允许通过拖拉来改变窗口的位置/大小;初始化时不可见,需要用 setVisible(true)使其显式出来;默认使用BorderLayout作为其布局管理器
    • Panel可作为容器来盛装其他组件,为放置组件提供空间;不能单独存在,必须放置到其他容器中;默认使用FlowLayout作为其布局管理器
    • ScrollPane是一个带滚动条的容器,它也不能独立存在,必须被添加到其他容器中;当组件占用空间过大时,ScrollPane自动产生滚动条,当然也可以通过指定特定的构造器参数来指定默认具有滚动条;默认使用BorderLayout作为其布局管理器,ScrollPane通常用于盛装其他容器,所以通常不允许改变其布局管理器
Container容器类常用方法
常用方法 返回值类型 说明
add(Component comp) Component 向容器中添加其他组件(该组件可以是普通组件,也可以是容器)
getComponentAt(int x, int y) Component 返回指定点的组件
getComponentCount() int 返回该容器内组件的数量
getComponents() Component[] 返回该容器内的所有组件
setLayout(LayoutManager mgr) void 设置指定的布局管理器

注意:如果运行乱码,需要在idea的VM options中添加 -Dfile.encoding=gbk

Panel代码案例
Frame f = new Frame("测试窗口"); // 创建一个窗口
Panel p = new Panel(); // 创建一个Panel容器
p.add(new TextField(20)); // 向Panel中添加两个组件
p.add(new Button("单击我"));
f.add(p); // 将Panel容器添加到Frame窗口中
f.setBounds(30, 40, 250, 200); // 设置窗口的大小和位置
f.setVisible(true); // 将窗口显示出来(Frame默认处于隐藏状态)
ScrollPane代码案例
Frame f = new Frame("测试窗口"); // 创建一个窗口
ScrollPane sp = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS); // 创建一个ScrollPane容器,指定总是具有滚动条
sp.add(new TextField(20)); // 向ScrollPane中添加两个组件
sp.add(new Button("点击我"));
f.add(sp); // 将ScrollPane容器添加到Frame中
f.setBounds(30, 40, 250, 200); // 设置窗口的大小和位置
f.setVisible(true); // 将窗口显示出来(Frame默认处于隐藏状态)

布局管理器

为了使生成的图形用户界面具有良好的平台无关性,Java提供了布局管理器这个工具来管理组件在容器中的布局,而不适用直接设置组件位置和大小的方式

所有的AWT容器都有默认的布局管理器,如果没有为容器指定布局管理器,则该容器使用默认的布局管理器.为容器指定布局管理器通过调用容器对象的setLayout(LayoutManager Im)来完成

FlowLayout布局管理器

在FlowLayout布局管理器中,组件像水流一样向某方向流动 (排列) ,遇到障碍(边界)就折回,重头开始排列。在默认情况下,FlowLayout布局管理器从左向右排列所有组件,遇到边界就会折回下一行重新开始

Panel和Applet默认使用FlowLayout布局管理器

构造方法 方法功能
FlowLayout() 使用默认 的对齐方式及默认的垂直间距、水平间距创建 FlowLayout 布局管理器。
FlowLayout(int align) 使用指定的对齐方式及默认的垂直间距、水平间距创建 FlowLayout 布局管理器。
FlowLayout(int align,int hgap,int vgap) 使用指定的对齐方式及指定的垂直问距、水平间距创建FlowLayout 布局管理器。

上面三个构造器的hgap/vgap代表水平间距/垂直间距,为这两个参数传入整数值即可.其中align表明FlowLayout中组件的排列方向(从左向右、从右向左、从中间向两边等),该参数应该使用FlowLayout类的静态常量:FlowLayout.LEFT、FlowLayout.CENTER、FlowLayout.RIGHT,默认是左对齐

//1.创建Frame对象
Frame frame = new Frame("这里测试FlowLayout");
//2.修改Frame容器的布局管理器为FlowLayout
frame.setLayout(new FlowLayout(FlowLayout.LEFT, 20, 20));
//3.往Frame中添加100个button
for (int i = 0; i < 100; i++) {
    frame.add(new Button("按钮" + i));
}
//4.将窗口调整到最佳大小,通过Java编写用户界面程序时,很少直接设置窗口的大小,通常都是调用pack()方法来将窗口调整到最佳大小
frame.pack();
//5.设置Frame可见
frame.setVisible(true);

BorderLayout布局管理器

BorderLayout将容器分为EAST、SOUTH、WEST、NORTH、CENTER五个区域,普通组件可以被放置在这5个区域的任意一个中。BorderLayout布局管理器的布局示意图如图所示

当改变使用BorderLayout的容器大小时,NORTH、SOUTH和CENTER区域水平调整,而EAST、WEST和CENTER区域垂直调整。使用BorderLayout有如下几个注意点:

  1. 当向使用BorderLayout布局管理器的容器中添加组件时,需要指定要添加到哪个区域中。如果没有指定添加到哪个区域中,则默认添加到中间区域中
  2. 如果向同一个区域中添加多个组件时,后放入的组件会覆盖先放入的组件
  3. BorderLayout最多只能放置5个组件,但可以放置少于5个组件,如果某个区域没有放置组件,该区域并不会出现空白,旁边区域的组件会自动占据该区域,从而保证窗口有较好的外观

Frame/Dialog/ScrollPane默认采用BorderLayout布局管理器

当使用BorderLayout布局管理器的容器中添加组件时,应该使用BorderLayout类的几个静态常量来指定添加到哪个区域中

  • BorderLayout.MORTH
  • BorderLayout.SOUTH
  • BorderLayout.WEST
  • BorderLayout.EAST
  • BorderLayout.CENTER
构造方法 方法功能
BorderLayout() 使用默认的水平间距、垂直间距创建BorderLayout布局管理器
BorderLayout(int hgap,int vgap): 使用指定的水平间距、垂直间距创建BorderLayout布局管理器
Frame f = new Frame("测试窗口");
f.setLayout(new BorderLayout(30, 5));
f.add(new Button("北"), BorderLayout.NORTH);
f.add(new Button("南"), BorderLayout.SOUTH);
f.add(new Button("东"), BorderLayout.EAST);
Panel p = new Panel();
p.add(new TextField(20));
p.add(new Button("单击我"));
f.add(p); // 默认添加到中间区域
f.pack();
f.setVisible(true);

GridLayout布局管理器

GridLayout布局管理器将容器分割成纵横线分隔的网格,每个网格所占的区域大小相同。当向使用GridLayout布局管理器的容器中添加组件时,默认从左向右、从上向下依次添加到每个网格中。与FlowLayout不同的是,放置在GridLayout布局管理器中的各组件的大小由组件所处的区域决定(每个组件将自动占满整个区域)

构造方法 方法功能
GridLayout(int rows,in t cols) 采用指定的行数、列数,以及默认的横向间距、纵向间距将容器分割成多个网格
GridLayout(int rows,int cols,int hgap,int vgap) 采用指定 的行数、列数 ,以及指定的横向间距、纵向间距将容器分割成多个网格
Frame f = new Frame("计算器");
Panel p1 = new Panel();
p1.add(new TextField(30));
f.add(p1, BorderLayout.NORTH);
Panel p2 = new Panel();
p2.setLayout(new GridLayout(3, 5, 4, 4));
String[] names = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "-", "*", "/", "."};
for (int i = 0; i < names.length; i++) {
    p2.add(new Button(names[i]));
}
f.add(p2);
f.pack();
f.setVisible(true);

GridBagLayout布局管理器

GridBagLayout布局管理器的功能最强大,但也最复杂,与GridLayout布局管理器不同的是,在GridBagLayout布局管理器中,一个组件可以跨越一个或多个网格,并可以设置各网格的大小互不相同,从而增加了布局的灵活性。当窗口的大小发生变化时,GridBagLayout布局管理器也可以准确地控制窗口各部分的拉伸

由于在GridBagLayout布局中,每个组件可以占用多个网格,此时,我们往容器中添加组件的时候,就需要具体的控制每个组件占用多少个网格,java提供的GridBagConstaints类,与特定的组件绑定,可以完成具体大小和跨越性的设置

GridBagConstraints API:

成员变量 含义
gridx 设置受该对象控制的GUI组件左上角所在网格的横向索引
gridy 设置受该对象控制的GUI组件左上角所在网格的纵向索引
gridwidth 设置受该对象控制的GUI组件横向跨越多少个网格,如果属性值为GridBagContraints.REMAIND,则表明当前组件是横向最后一个组件,如果属性值为GridBagConstraints.RELATIVE,表明当前组件是横向倒数第二个组件
gridheight 设置受该对象控制的GUI组件纵向跨越多少个网格,如果属性值为GridBagContraints.REMAIND,则表明当前组件是纵向最后一个组件,如果属性值为GridBagConstraints.RELATIVE,表明当前组件是纵向倒数第二个组件
fill 当"显示区域"大于"组件"的时候,如何调整组件 :
GridBagConstraints.NONE:GUI组件不扩大
GridBagConstraints.HORIZONTAL:GUI组件水平扩大以占据空白区域
GridBagConstraints.VERTICAL:GUI组件垂直扩大以占据空白区域
GridBagConstraints.BOTH:GUI组件水平、垂直同时扩大以占据空白区域
ipadx 设置受该对象控制的GUI组件横向内部填充的大小,即在该组件最小尺寸的基础上还需要增大多少
ipady 设置受该对象控制的GUI组件纵向内部填充的大小,即在该组件最小尺寸的基础上还需要增大多少
insets 设置受该对象控制的GUI组件的外部填充的大小,即该组件边界和显示区域边界之间的距离
weightx 设置受该对象控制的GUI组件占据多余空间的水平比例,假设某个容器的水平线上包括三个GUI组件,它们的水平增加比例分别是1、2、3,但容器宽度增加60像素时,则第一个组件宽度增加10像素,第二个组件宽度增加20像素,第三个组件宽度增加30像素。如果其增加比例为0,则表示不会增加
weighty 设置受该对象控制的GUI组件占据多余空间的垂直比例
anchor 设置受该对象控制的GUI组件在其显示区域中的定位方式:
GridBagConstraints.CENTER (中 间 )
GridBagConstraints.NORTH (上中 )
GridBagConstraints.NORTHWEST(左上角)
GridBagConstraints.NORTHEAST(右上角)
GridBagConstraints.SOUTH(下中)
GridBagConstraints.SOUTHEAST(右下角)
GridBagConstraints.SOUTHWEST(左下角)
GridBagConstraints.EAST(右中)
GridBagConstraints.WEST(左中)
  • gridx: 用于指定组件在网格中所处的横向索引
  • gridy: 用于执行组件在网格中所处的纵向索引
  • gridwidth: 用于指定组件横向跨越多少个网格
  • gridheight: 用于指定组件纵向跨越多少个网格
  • 如果希望某个组件的大小随容器的增大而增大,则必须同时设置控制该组件的GridBagConstraints对象的fill属性和weightx/weighty属性

GridBagLayout使用步骤

  1. 创建GridBagLaout布局管理器对象,并给容器设置该布局管理器对象
  2. 创建GridBagConstraints对象,并设置该对象的控制属性
  3. 调用GridBagLayout对象的setConstraints(Component c,GridBagConstraints gbc)方法,把即将要添加到容器中的组件c和GridBagConstraints对象关联起来
  4. 把组件添加到容器中
GridBagLayout代码案例
public class GridBagTest {
    private Frame frame = new Frame("测试窗口");
    private GridBagLayout gridBagLayout = new GridBagLayout();
    private GridBagConstraints gbc = new GridBagConstraints();
    private Button[] bs = new Button[10];

    public void init() {
        frame.setLayout(gridBagLayout);
        for (int i = 0; i < bs.length; i++) {
            bs[i] = new Button("按钮" + i);
        }
        gbc.fill = GridBagConstraints.BOTH; // 设置所有的GridBagConstraints对象的fill属性,当有空白区域时,组件自动扩大占满空白区域
        gbc.weightx = 1; // 设置GridBagConstraints对象的weightx设置为1,表示横向扩展比例为1
        addComponent(bs[0]); // 添加第1个按钮
        addComponent(bs[1]); // 添加第2个按钮
        addComponent(bs[2]); // 添加第3个按钮
        gbc.gridwidth = GridBagConstraints.REMAINDER; // 表明当前组件是横向最后一个组件
        addComponent(bs[3]); // 添加第4个按钮
        gbc.weightx = 0; // 表示横向不会扩大
        addComponent(bs[4]); // 添加第5个按钮
        // 把GridBagConstraints的gridwidth和gridheight设置为2,表示横向和纵向会占用两个网格
        gbc.gridwidth = 2;
        gbc.gridheight = 2;
        addComponent(bs[5]); // 添加第6个按钮
        // 把GridBagConstraints的gridwidth和gridheight设置为1,表示横纵会占用1个网格
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        addComponent(bs[6]); // 添加第7个按钮
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        addComponent(bs[7]); // 添加第8个按钮
        gbc.gridwidth = 1; // 占用一个网格
        addComponent(bs[8]); // 添加第9个按钮
        addComponent(bs[9]); // 添加第10个按钮
        frame.pack();
        frame.setVisible(true);
    }

    private void addComponent(Button button) {
        gridBagLayout.setConstraints(button, gbc);
        frame.add(button);
    }

    public static void main(String[] args) {
        new GridBagTest().init();
    }
}

CardLayout布局管理器

CardLayout布局管理器以时间而非空间来管理它里面的组件,它将加入容器的所有组件看成一叠卡片(每个卡片其实就是一个组件),每次只有最上面的那个Component才可见。就好像一副扑克牌,它们叠在一起,每次只有最上面的一张扑克牌才可见

方法名称 方法功能
CardLayout() 创建默认的CardLayout布局管理器
CardLayout(int hgap,int vgap) 通过指定卡片与容器左右边界的间距(hgap)、上下边界(vgap)的间距来创建CardLayout布局管理器
first(Container target) 显示target容器中的第一张卡片
last(Container target) 显示target容器中的最后一张卡片
previous(Container target) 显示target容器中的前一张卡片
next(Container target) 显示target容器中的后一张卡片
show(Container taget,String name) 显示target容器中指定名字的卡片
CardLayout代码案例
public class CardLayoutTest {
    Frame frame = new Frame("这里测试CardLayout");
    String[] names = {"第一张", "第二张", "第三张", "第四张", "第五张"};


    public void init() {
        Panel p1 = new Panel(); // 创建一个Panel容器p1,并设置其布局管理器为CardLayout,用来存放多张卡片
        CardLayout cardLayout = new CardLayout();
        p1.setLayout(cardLayout);
        for (int i = 0; i < 5; i++) {
            p1.add(names[i], new Button(names[i]));
        }

        Panel p2 = new Panel(); // 创建一个Panel容器p2,用来存储5个按钮,完成卡片的切换
        // 创建5个按钮,并给按钮设置监听器
        ActionListener listener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String command = e.getActionCommand();
                switch (command) {
                    case "上一张":
                        cardLayout.previous(p1);
                        break;
                    case "下一张":
                        cardLayout.next(p1);
                        break;
                    case "第一张":
                        cardLayout.first(p1);
                        break;
                    case "最后一张":
                        cardLayout.last(p1);
                        break;
                    case "第三张":
                        cardLayout.show(p1, "第三张");
                        break;
                }
            }
        };
        Button b1 = new Button("上一张");
        Button b2 = new Button("下一张");
        Button b3 = new Button("第一张");
        Button b4 = new Button("最后一张");
        Button b5 = new Button("第三张");
        b1.addActionListener(listener);
        b2.addActionListener(listener);
        b3.addActionListener(listener);
        b4.addActionListener(listener);
        b5.addActionListener(listener);

        // 把5个按钮添加到p2中
        p2.add(b1);
        p2.add(b2);
        p2.add(b3);
        p2.add(b4);
        p2.add(b5);
        // 把p1添加到frame的中间区域
        frame.add(p1);
        // 把p2添加到frame的底部区域
        frame.add(p2, BorderLayout.SOUTH);

        // 设置frame最佳大小并可见
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        new CardLayoutTest().init();
    }
}

BoxLayout布局管理器

GridBagLayout布局管理器虽然功能强大,但它实在太复杂了,所以Swing引入了一个新的布局管理器:BoxLayout.BoxLayout可以在垂直和水平两个方向上摆放GUI组件,BoxLayout提供了如下一个简单的构造器

方法名称 方法功能
BoxLayout(Container target,int axis) 指定创建基于target容器的BoxLayout布局管理器,该布局管理器里的组件按axis方向排列。其中axis有BoxLayout.X_AXIS(横向)和BoxLayout.Y _AXIS(纵向)两个方向

BoxLayout通常和Box容器(在java.swing包中)结合使用,Box是一个特殊的容器,它有点像Panel容器,但该容器默认使用BoxLayout布局管理器.大多数情况下,使用Box容器去容纳多个GUI组件,然后再把Box容器作为一个组件,添加到其他的容器中,从而形成整体窗口布局

方法名称 方法功能
static Box createHorizontalBox() 创建一个水平排列组件的Box容器
static Box createVerticalBox() 创建一个垂直排列组件的Box容器

组件之间都会有一些间距,那使用BoxLayout如何给组件设置间距呢?其实很简单,我们只需要在原有的组件需要间隔的地方,添加间隔即可,而每个间隔可以是一个组件,只不过该组件没有内容,仅仅起到一种分隔的作用——BoxLayout使用Glue(橡胶)/Strut(支架)/PigidArea(刚性区域)

  • Glue:代表可以在横向/纵向两个方向上同时拉伸的空白间距
  • Strut:代表可以在横向/纵向任意一个方向上拉伸的空白间距
  • RigidArea代表不可拉伸的空白间距

Box类中,提供了5个方便的静态方法来生成这些间隔组件:

方法名称 方法功能
static Component createHorizontalGlue() 创建一条水平Glue(可在两个方向上同时拉伸的间距)
static Component createVerticalGlue() 创建一条垂直Glue(可在两个方向上同时拉伸的间距)
static Component createHorizontalStrut(int width) 创建一条指定宽度(宽度固定了,不能拉伸)的水平Strut(可在垂直方向上拉伸的间距)
static Component createVerticalStrut(int height) 创建一条指定高度(高度固定了,不能拉伸)的垂直Strut(可在水平方向上拉伸的间距)
BoxLayout代码案例
Frame frame = new Frame("这里测试BoxLayout");
Box hBox = Box.createHorizontalBox(); // 创建一个横向的Box,并添加两个按钮
hBox.add(new Button("水平按钮一"));
hBox.add(Box.createHorizontalGlue()); // 两个方向都可以拉伸的间隔
hBox.add(new Button("水平按钮二"));
hBox.add(Box.createHorizontalStrut(10)); // 水平方向不可拉伸,固定间隔为10;垂直间方向可以拉伸
hBox.add(new Button("水平按钮3"));
Box vBox = Box.createVerticalBox(); // 创建一个纵向的Box,并添加两个按钮
vBox.add(new Button("垂直按钮一"));
vBox.add(Box.createVerticalGlue()); // 两个方向都可以拉伸的间隔
vBox.add(new Button("垂直按钮二"));
vBox.add(Box.createVerticalStrut(10)); // 垂直间隔固定,水平方向可以拉伸
vBox.add(new Button("垂直按钮三"));
frame.add(hBox, BorderLayout.NORTH); // 把box容器添加到frame容器中
frame.add(vBox);
frame.pack();
frame.setVisible(true);

绝对定位

  • 先将Container的布局管理器设为null:setLayout(null)
  • 向容器中添加组件时,先调用setBounds()或setSize()方法来设置组件的大小/位置,或者直接创建GUI组件时通过构造参数指定该组件的大小/位置,然后将该组件添加到容器中
Frame frame = new Frame("测试窗口");
Button button1 = new Button("第一个按钮");
Button button2 = new Button("第二个按钮");
frame.setLayout(null);
button1.setBounds(20, 30, 90, 28);
button2.setBounds(50, 45, 120, 35);
frame.add(button1);
frame.add(button2);
frame.setVisible(true);

AWT常用组件

组件名 功能
Button 按钮,可接受单击操作
TextArea 多行文本域
TextField 单行文本框
Checkbox 复选框组件(也可当做单选框组件使用)
CheckboxGroup 用于将多个Checkbox组件组合成一组,一组Checkbox组件将只有一个可以被选中,即全部变成单选框组件
Choice 下拉选择框
List 列表框组件,可以添加多项条目
Canvas 用于绘图的画布
Label 标签类,用于放置提示性文本
Frame 窗口,在GUI程序里通过该类创建窗口
Panel 不能单独存在基本容器类,必须放到其他容器中
Scrollbar 滑动条组件;如果需要用户输入位于某个范围的值,就可以使用滑动条组件,比如调色板中设置RGB的三个值所用的滑动条.当创建一个滑动条时,必须指定它的方向/初始值/滑块的大小/最小值和最大值
ScrollPane 带水平及垂直滚动条的容器组件
AWT常用组件代码案例
public class Main {
    Frame frame = new Frame("这里测试基本组件");
    Button ok = new Button("确认"); // 定义一个按钮
    CheckboxGroup cbg = new CheckboxGroup(); // 定义一个复选框组
    Checkbox male = new Checkbox("男", cbg, true); // 定义一个单选框,初始处于被选中状态,并添加到cbg组中
    Checkbox female = new Checkbox("女", cbg, false); // 定义一个单选框,初始处于未被选中状态,并添加到cbg组中
    Checkbox married = new Checkbox("是否已婚?", false); // 定义一个复选框,初始处于未被选中状态
    Choice choice = new Choice(); // 定义一个下拉选择框
    List list = new List(6, true); // 定义一个列表选择框
    TextArea ta = new TextArea(5, 20); // 定义一个5行,20列的多行文本域
    TextField tf = new TextField(50); // 定义一个50列的单行文本域

    public void init() {
        // 往下拉选择框中添加内容
        choice.add("红色");
        choice.add("绿色");
        choice.add("蓝色");

        // 往列表选择框中添加内容
        list.add("红色");
        list.add("绿色");
        list.add("蓝色");

        // 创建一个装载按钮和文本框的Panel容器
        Panel bottom = new Panel();
        bottom.add(tf);
        bottom.add(ok);

        // 把bottom添加到Frame的底部
        frame.add(bottom, BorderLayout.SOUTH);

        // 创建一个Panel容器,装载下拉选择框,单选框和复选框
        Panel checkPanel = new Panel();
        checkPanel.add(choice);
        checkPanel.add(male);
        checkPanel.add(female);
        checkPanel.add(married);

        // 创建一个垂直排列的Box容器,装载 多行文本域和checkPanel
        Box topLeft = Box.createVerticalBox();
        topLeft.add(ta);
        topLeft.add(checkPanel);

        // 创建一个水平排列的Box容器,装载topLeft和列表选择框
        Box top = Box.createHorizontalBox();
        top.add(topLeft);
        top.add(list);

        // 将top添加到frame的中间区域
        frame.add(top);

        // 设置frame最佳大小并可见
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        new Main().init();
    }
}

对话框(Dialog)

Dialog是Window类的子类,是一个容器类,属于特殊组件。对话框是可以独立存在的顶级窗口,因此用法与普通窗口的用法几乎完全一样,但是使用对话框需要注意下面两点:

  • 对话框通常依赖于其他窗口,就是通常需要有一个父窗口
  • 对话框有模式(modal)和非模式(non-modal)两种;当某个模式对话框被打开后,该模式对话框总是位于它的父窗口之上
    • 模式:在模式对话框被关闭之前,父窗口无法获得焦点
    • 非模式:在模式对话框被关闭之前,父窗口可以获得焦点
方法名称 方法功能
Dialog(Frame owner,String title,boolean modal) 创建一个对话框对象:
Frame:当前对话框的父窗口
String:当前对话框的标题
boolean:当前对话框是否是模式对话框,true/false
Dialog代码案例
Frame frame = new Frame("这里测试Dialog");
Dialog dialog = new Dialog(frame, "模式对话框", true);
Box vBox = Box.createVerticalBox(); //往对话框中添加内容
vBox.add(new TextField(15));
vBox.add(new JButton("确认"));
dialog.add(vBox);
Button b1 = new Button("打开模式对话框");
dialog.setBounds(20, 30, 200, 100); //设置对话框的大小和位置
//给b1绑定监听事件
b1.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        dialog.setVisible(true);
    }
});
//把按钮添加到frame中
frame.add(b1);
//设置frame最佳大小并可见
frame.pack();
frame.setVisible(true);

Dialog 类还有一个子类:FileDialog,它代表一个文件对话框,用于打开或者保存文件,需要注意的是FileDialog无法指定模态或者非模态,这是因为FileDialog依赖于运行平台的实现,如果运行平台的文件对话框是模态的,那么FileDialog 也是模态的;否则就是非模态的

方法名称 方法功能
FileDialog(Frame parent,String title,int mode) 创建一个文件对话框:
Frame:指定父窗口
String:对话框标题
int:文件对话框类型,如果指定为FileDialog.load,用于打开文件,如果指定为FileDialog.SAVE,用于保存文件
String getDirectory() 获取被打开或保存文件的绝对路径
String getFile() 获取被打开或保存文件的文件名
FileDialog代码案例
Frame frame = new Frame("这里测试FileDialog");
FileDialog d1 = new FileDialog(frame, "选择需要加载的文件", FileDialog.LOAD);
FileDialog d2 = new FileDialog(frame, "选择需要保存的文件", FileDialog.SAVE);
Button b1 = new Button("打开文件");
Button b2 = new Button("保存文件");
//给按钮添加事件
b1.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        d1.setVisible(true);
        //打印用户选择的文件路径和名称
        System.out.println("用户选择的文件路径:" + d1.getDirectory());
        System.out.println("用户选择的文件名称:" + d1.getFile());
    }
});
System.out.println("-------------------------------");
b2.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        d2.setVisible(true);
        //打印用户选择的文件路径和名称
        System.out.println("用户选择的文件路径:" + d2.getDirectory());
        System.out.println("用户选择的文件名称:" + d2.getFile());
    }
});
//添加按钮到frame中
frame.add(b1);
frame.add(b2, BorderLayout.SOUTH);
//设置frame最佳大小并可见
frame.pack();
frame.setVisible(true);

事件处理机制

事件:当在某个组件上发生某些操作的时候,会自动的触发一段代码的执行

在事件处理的过程中,主要涉及三类对象

  • 事件源(Event Source):操作发生的场所,通常指某个组件,例如按钮、窗口等
  • 事件(Event):在事件源上发生的操作可以叫做事件,GUI会把事件都封装到一个Event对象中,如果需要知道该事件的详细信息,就可以通过Event对象来获取
  • 事件监听器(Event Listener):当在某个事件源上发生了某个事件,事件监听器就可以对这个事件进行处理

注册监听:把某个事件监听器(A)通过某个事件(B)绑定到某个事件源(C)上,当在事件源C上发生了事件B之后,那么事件监听器A的代码就会自动执行

使用步骤

  1. 创建事件源组件对象
  2. 自定义类,实现XxxListener接口,重写方法
  3. 创建事件监听器对象(自定义类对象)
  4. 调用事件源组件对象的addXxxListener方法完成注册监听
事件处理代码案例
Frame frame = new Frame("测试窗口");
TextField textField = new TextField(30);
Button button = new Button("确定");
button.addActionListener((ActionEvent e) -> {
    textField.setText("helloWorld");
});
frame.add(textField, BorderLayout.NORTH);
frame.add(button);
frame.pack();
frame.setVisible(true);

当外部动作在AWT组件上进行操作时,系统会自动生成事件对象,这个事件对象时EventObject子类的实例,该事件对象会触发注册到事件源上的事件监听器
AWT事件机制涉及三个成员:事件源/事件/事件监听器,其中事件源就是组件;事件是由系统自动产生的,无需程序员关心;所以,实现事件监听器就是整个事件处理的核心

事件监听器必须实现事件监听器接口,AWT提供了大量的事件监听器接口用于实现不同类型的事件监听器,用于监听不同类型的事件——AWT的事件类都是AWTEvent类的子类,AWTEvent是EventObject的子类

AWT事件分为两大类:低级事件和高级事件

  1. 低级事件:这类事件是基于某个特定动作的事件。比如进入、点击、拖放等动作的鼠标事件,再比如得到焦点和失去焦点等焦点事件
  2. 高级事件:这类事件并不会基于某个特定动作,而是根据功能含义定义的事件
低级事件
事件 触发时机
WindowEvent 窗口事件,当窗口状态发生改变(如打开、关闭、最大化、最小化)时触发该事件
ComponentEvent 组件事件,当组件尺寸发生变化、位置发生移动、显示/隐藏状态发生改变时触发该事件
ContainerEvent 容器事件,当容器里发生添加组件、删除组件时触发该事件
FocusEvent 焦点事件,当组件得到焦点或失去焦点时触发该事件
MouseEvent 鼠标事件,当进行单击、按下、松开、移动鼠标等动作时触发该事件
KeyEvent 键盘事件,当按键被按下、松开、单击时触发该事件
PaintEvent 组件绘制事件,该事件是一个特殊的事件类型,当GUI组件调用update/paint方法来呈现自身时触发该事件,该事件并非专用于事件处理模型
高级事件
事件 触发时机
ActionEvent 动作事件,当按钮、菜单项被单击,在TextField中按Enter键时触发
AjustmentEvent 调节事件,在滑动条上移动滑块以调节数值时触发该事件
ltemEvent 选项事件,当用户选中某项,或取消选中某项时触发该事件
TextEvent 文本事件,当文本框、文本域里的文本发生改变时触发该事件

不同的事件需要使用不同的监听器监听,不同的监听器需要实现不同的监听器接口,当指定事件发生后,事件监听器就会调用所包含的事件处理器(实例方法)来处理事件

事件监听器
事件类别 描述信息 监听器接口名
WindowEvent 窗口收到窗口级事件 WindowListener
ComponentEvent 对象移动缩放显示隐藏等 ComponentListener
ContainerEvent 容器中增加删除了组件 ContainerListener
FocusEvent 组件收到或失去焦点 FocusListener
MouseEvent 鼠标移动 MouseMotionListener
MouseEvent 鼠标点击等 MouseListener
KeyEvent 键盘输入 KeyListener
ActionEvent 激活组件 ActionListener
AdjustmentEvent 移动了滚动条等组件 AdjustmentListener
ItemEvent 选择了某些项目 ItemListener
TextEvent 文本字段或文本区发生改变 TextListener
点击查看代码
事件类别 监听器接口 适配器接口 触发时机
WindowEvent WindowListener WindowAdapter windowOpened:窗口首次被打开时触发
windowClosing:用户单击窗口右上角的"x"按钮时触发
windowClosed:窗口调用dispose()即将关闭时触发
windowIconified:窗口最小化时触发
windowDeiconified:窗口被恢复时触发
windowActivated:窗口被激活时触发
windowDeactivated:窗口失去激活时触发
ComponentEvent ComponentListener ComponentAdapter componentResized:组件大小发生改变时触发
componentMoved:组件位置发生改变时触发
componentShown:组件被显式时触发
componentHidden:组件被隐藏时触发
ContainerEvent ContainerListener ContainerAdapter componentAdded:向容器中添加组件时触发
componentRemoved:向容器中删除组件时触发
FocusEvent FocusListener FocusAdapter focusGained:组件得到焦点时触发
focusLost:组件失去焦点时触发
MouseEvent MouseListener MouseAdapter mouseClicked:在某个组件上单击鼠标键时触发
mousePressed:在某个组件上按下鼠标键时触发
mouseReleased:在某个组件上松开鼠标键时触发
mouseEntered:鼠标进入某个组件时触发
mouseExited:鼠标离开某个组件时触发
MouseEvent MouseMotionListener MouseMotionAdapter mouseDragged:在某个组件上移动鼠标,且按下鼠标键时触发
mouseMoved:在某个组件上移动鼠标,且没有按下鼠标键时触发
KeyEvent KeyListener KeyAdapter keyTyped:单击某个按键时触发
keyPressed:按下某个按键时触发
keyReleased:松开某个按键时触发
ActionEvent ActionListener —— actionPerformed:按钮/文本框/菜单项被单击时触发
AdjustmentEvent AdjustmentListener —— adjustmentValueChanged:滑块位置发生改变时触发
ItemEvent ItemListener —— itemStateChanged:某项被选中或取消选中时触发
TextEvent TextListener —— textValueChanged:文本组件里的文本发生改变时触发
事件监听器代码案例
Frame frame = new Frame("测试窗口");

Choice choice = new Choice();
choice.add("张三");
choice.add("李四");
choice.add("王五");
TextField textField = new TextField(30);

choice.addItemListener((e) -> {
    Object item = e.getItem();
    System.out.println("当前选择的列表项是:" + item);
});
textField.addTextListener((e) -> {
    String text = textField.getText();
    System.out.println("当前文本框中的内容是:" + text);
});
frame.addContainerListener(new ContainerListener() {
    @Override
    public void componentAdded(ContainerEvent e) {
        Component child = e.getChild();
        System.out.println("frame中添加了:" + child);
    }
    @Override
    public void componentRemoved(ContainerEvent e) {
    }
});

Box box = Box.createHorizontalBox();
box.add(choice);
box.add(textField);
frame.add(box);
frame.pack();
frame.setVisible(true);
关闭窗口代码案例
Frame frame = new Frame("测试窗口");
frame.setBounds(200, 200, 500, 300);
// 使用适配器模式,使得不用实现WindowListener的全部方法,直接按需写实现方法即可
frame.addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
});
frame.setVisible(true);

菜单组件

下表中给出常见的菜单相关组件:

菜单组件名称 功能
MenuBar 菜单条,菜单的容器
Menu 菜单组件,菜单项的容器.它也是Menultem的子类,所以可作为菜单项使用
PopupMenu 上下文菜单组件(右键菜单组件)
Menultem 菜单项组件
CheckboxMenuItem 复选框菜单项组件

下图是常见菜单相关组件集成体系图:

菜单代码案例
Frame frame = new Frame("测试窗口");
MenuBar menuBar = new MenuBar();
Menu file = new Menu("文件");
Menu edit = new Menu("编辑");
Menu format = new Menu("格式");
MenuItem newItem = new MenuItem("新建");
MenuItem save = new MenuItem("保存");
MenuItem exit = new MenuItem("退出", new MenuShortcut(KeyEvent.VK_X)); // Ctrl+X
CheckboxMenuItem autoWrap = new CheckboxMenuItem("自动换行");
MenuItem copy = new MenuItem("复制");
MenuItem paste = new MenuItem("粘贴");
MenuItem comment = new MenuItem("注释 Ctrl+Shift+/", new MenuShortcut(KeyEvent.VK_SLASH, true));
MenuItem cancelComment = new MenuItem("取消注释");
TextArea textArea = new TextArea(6, 40);

exit.addActionListener((e) -> {
    String command = e.getActionCommand();
    textArea.append("单击" + command + "\n");
    if (command.equals("退出")) {
        System.exit(0);
    }
});

comment.addActionListener((e) -> {
    String command = e.getActionCommand();
    System.out.println(command);
});

file.add(newItem);
file.add(save);
file.add(exit);

edit.add(autoWrap);
edit.addSeparator();
edit.add(copy);
edit.add(paste);
edit.add(new MenuItem("-"));
format.add(comment);
format.add(cancelComment);
edit.add(format);

menuBar.add(file);
menuBar.add(edit);
frame.setMenuBar(menuBar);
frame.addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
});
frame.add(textArea);
frame.pack();
frame.setVisible(true);
右键菜单代码案例
Frame frame = new Frame("测试窗口");
TextArea textArea = new TextArea(6, 40);
Panel panel = new Panel();
PopupMenu menu = new PopupMenu();
MenuItem comment = new MenuItem("注释", new MenuShortcut(KeyEvent.VK_SLASH, true));
MenuItem cancelComment = new MenuItem("取消注释");
MenuItem copy = new MenuItem("复制");
MenuItem paste = new MenuItem("粘贴");

ActionListener actionListener = e -> {
    String command = e.getActionCommand();
    textArea.append(command);
    if (command.equals("退出")) {
        System.exit(0);
    }
};

menu.add(comment);
menu.add(cancelComment);
menu.add(copy);
menu.add(paste);
comment.addActionListener(actionListener);
cancelComment.addActionListener(actionListener);
copy.addActionListener(actionListener);
paste.addActionListener(actionListener);

panel.add(menu);
panel.setPreferredSize(new Dimension(400, 150));

panel.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseReleased(MouseEvent e) {
        if (e.isPopupTrigger()) {
            menu.show(panel, e.getX(), e.getY());
        }
    }
});

frame.addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
});
frame.add(textArea);
frame.add(panel, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);

AWT绘图

很多程序如各种小游戏都需要在窗口中绘制各种图形,除此之外,即使在开发JavaEE项目时,有时候也必须"动态"地向客户端生成各种图形、图表,比如图形验证码、统计图等,这都需要利用AWT的绘图功能

之前我们已经学习过很多组件,例如Button、Frame、Checkbox等等,不同的组件,展示出来的图形都不一样,其实这些组件展示出来的图形,其本质就是用AWT的绘图来完成的

程序中绘图也一样,也需要画布,画笔,颜料等等

  • Canvas类充当画布(继承自Component组件)
  • Graphics类来充当画笔
  • 通过调用Graphics对象的setColor()方法可以给画笔设置颜色

在Component类里提供了和绘图相关的三个方法

  • paint(Graphics g):绘制组件的外观
  • update(Graphics g):调用paint()方法,刷新组件外观
  • repaint():调用update()方法,刷新组件外观

Graphics画笔类,可以在组件上绘制丰富多彩的几何图形和位图

Graphics画笔类常用方法
常用方法 返回值类型 说明
setColor(Color c) abstract void 设置颜色
setFont(Font font) abstract void 设置字体
drawLine(int x1,int y1,int x2,int y2) abstract void 绘制直线
drawRect(int x,int y,int width,int height) void 绘制矩形
fillRect(int x,int y,int width,int height) abstract void 填充矩形区域
drawRoundRect(int x,int y,int width,int height,int arcWidth,int arcHeight) abstract void 绘制圆角矩形
fillRoundRect(int x,int y,int width,int height,int arcWidth,int arcHeight) abstract void 填充圆角矩形区域
drawOval(int x,int y,int width,int height) abstract void 绘制椭圆形
fillOval(int x,int y,int width,int height) abstract void 填充椭圆区域
drawPolygon(Polygon p) void 绘制多边形
fillPolygon(Polygon p) void 填充多边形区域
drawPolyline(int[] xPoints,int[] yPoints,int nPoints) abstract void 绘制多边形
fillPolygon(int[] xPoints,int[] yPoints,int nPoints) abstract void 填充多边形区域
drawArc(int x,int y,int width,int height,int startAngle,int arcAngle) abstract void 绘制圆弧
fillArc(int x,int y,int width,int height,int startAngle,int arcAngle) abstract void 填充圆弧对应的扇形区域
drawPolyline(int[] xPoints,int[] yPoints,int nPoints) abstract void 绘制折线
drawImage(Image img,int x,int y,Color bgcolor,ImageObserver observer) abstract boolean 绘制位图
代码案例:绘制矩形和椭圆
public class Main {
    private final String RECT_SHAPE = "rect";
    private final String OVAL_SHAPE = "oval";
    private Frame frame = new Frame("测试窗口");
    private Button rect = new Button("绘制矩形");
    private Button oval = new Button("绘制圆形");
    private MyCanvas canvas = new MyCanvas();
    private String shape = "";

    public void init() {
        rect.addActionListener(e -> {
            shape = RECT_SHAPE;
            canvas.repaint();
        });
        oval.addActionListener(e -> {
            shape = OVAL_SHAPE;
            canvas.repaint();
        });

        Panel panel = new Panel();
        panel.add(rect);
        panel.add(oval);
        canvas.setPreferredSize(new Dimension(250, 180));
        frame.add(canvas);
        frame.add(panel, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }


    public static void main(String[] args) {
        new Main().init();
    }

    class MyCanvas extends Canvas {
        @Override
        public void paint(Graphics g) {
            Random rand = new Random();
            if (shape.equals(RECT_SHAPE)) {
                g.setColor(new Color(220, 100, 80));
                g.drawRect(rand.nextInt(200), rand.nextInt(120), 40, 60);
            } else if (shape.equals(OVAL_SHAPE)) {
                g.setColor(new Color(80, 100, 200));
                g.fillOval(rand.nextInt(200), rand.nextInt(120), 50, 40);
            }
        }
    }
}
代码案例:小球游戏
public class PinBall {
    private final int TABLE_WIDTH = 300; // 桌面宽度
    private final int TABLE_HEIGHT = 400; // 桌面高度
    private final int RACKET_WIDTH = 60; // 球拍的高度和宽度
    private final int RACKET_HEIGHT = 20;
    private final int BALL_SIZE = 16; // 小球的大小
    private int ySpeed = 10; // 定义小球纵向运行速度
    private int xSpeed = 5; // 小球横向运行速度
    private int ballX = 120; // 定义小球的初始坐标
    private int ballY = 20;
    private int rackeX = 120; // 定义球拍的初始坐标,x坐标会发生变化,y坐标不会发生变化
    private final int RACKET_Y = 340;
    private Timer timer; // 声明定时器
    private boolean isLose = false; // 定义游戏结束的标记
    private MyCanvas tableArea = new MyCanvas(); // 声明一个桌面
    private Frame frame = new Frame("弹球游戏"); // 创建窗口对象

    public void init() {
        //设置桌面区域的最佳大小
        tableArea.setPreferredSize(new Dimension(TABLE_WIDTH, TABLE_HEIGHT));
        frame.add(tableArea); // 把桌面添加到frame中
        //定义键盘监听器
        KeyListener keyListener = new KeyAdapter() {
            // 监听键盘 ←  → 按下操作,当指定的键按下时,球拍的水平坐标分别会增加或者减少
            @Override
            public void keyPressed(KeyEvent e) {
                int keyCode = e.getKeyCode();
                if (keyCode == KeyEvent.VK_LEFT) {//←
                    // 没有到左边界,可以继续向左移动
                    if (rackeX > 0) {
                        rackeX -= 10;
                    }
                }
                if (keyCode == KeyEvent.VK_RIGHT) {//→
                    // 没有到右边界,可以继续向右移动
                    if (rackeX < TABLE_WIDTH - RACKET_WIDTH) {
                        rackeX += 10;
                    }
                }
            }
        };
        // 为窗口和tableArea分别添加键盘事件
        frame.addKeyListener(keyListener);
        tableArea.addKeyListener(keyListener);
        // 定义ActionListener,用来监听小球的变化情况
        ActionListener timerTask = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // 小球碰到左右边框
                if (ballX <= 0 || ballX >= TABLE_WIDTH - BALL_SIZE) {
                    xSpeed = -xSpeed;
                }
                // 小球的高度超出了球拍的位置,且横向不在球拍范围内,则游戏结束
                if (ballY > RACKET_Y && (ballX < rackeX || ballX > rackeX + RACKET_WIDTH)) {
                    // 结束定时器
                    timer.stop();
                    // 把游戏结束的标记设置为true
                    isLose = true;
                    // 重绘界面
                    tableArea.repaint();
                }
                // 如果小球横向在球拍范围内,且到达球拍位置或者到达顶端位置,则小球反弹
                if (ballY <= 0 || (ballY >= RACKET_Y - BALL_SIZE && ballX >= rackeX && ballX <= rackeX + RACKET_WIDTH)) {
                    ySpeed = -ySpeed;
                }
                // 更新小球的坐标
                ballX += xSpeed;
                ballY += ySpeed;
                // 重绘桌面
                tableArea.repaint();
            }
        };
        // 设置定时器,定时任务就是timerTask
        timer = new Timer(100, timerTask);
        timer.start();
        // 设置frame最佳大小,并可视
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        new PinBall().init();
    }

    private class MyCanvas extends Canvas {
        // 重写paint方法,实现绘图
        @Override
        public void paint(Graphics g) {
            // 判断游戏是否结束
            if (isLose) { // 结束
                g.setColor(Color.BLUE);
                g.setFont(new Font("Times", Font.BOLD, 30));
                g.drawString("游戏结束!", 50, 200);
            } else { // 没有结束
                // 设置颜色并绘制小球
                g.setColor(Color.RED);
                g.fillOval(ballX, ballY, BALL_SIZE, BALL_SIZE);
                // 设置颜色并绘制球拍
                g.setColor(Color.PINK);
                g.fillRect(rackeX, RACKET_Y, RACKET_WIDTH, RACKET_HEIGHT);
            }
        }
    }
}

绘制位图

AWT也允许在组件上绘制位图,Graphics提供了drawlmage()方法用于绘制位图,该方法需要一个Image参数一一代表位图,通过该方法就可 以绘制出指定的位图

位图使用步骤:

  1. 创建Image的子类对象BufferedImage(int width,int height,int ImageType),创建时需要指定位图的宽高及类型属性;此时相当于在内存中生成了一张图片;
  2. 调用BufferedImage对象的getGraphics()方法获取画笔,此时就可以往内存中的这张图片上绘图了,绘图的方法和之前学习的一模一样;
  3. 调用组件的drawImage()方法,一次性的内存中的图片BufferedImage绘制到特定的组件上。

使用位图绘制组件的好处:使用位图来绘制组件,相当于实现了图的缓冲区,此时绘图时没有直接把图形绘制到组件上,而是先绘制到内存中的BufferedImage上,等全部绘制完毕,再一次性的图像显示到组件上即可,这样用户的体验会好一些

Swing