Java学习笔记
AWT编程
Java 9 改进的GUI(图形用户界面)和AWT
- 所有和AWT编程相关的类都放在java.awt包以及它的子包中,AWT有两个基类:Component和MenuComponent
- 在java.awt包中提供了两种基类表示图形界面元素:Component和MenuComponent,前者代表一个能以图形化方式显示出来,并可与用户交互的对象,例如Button代表一个按钮,TextField代表一个文本框等;而MenuComponent则代表图形界面的菜单组件,包括MenuBar(菜单条)、MenuItem(菜单项)等子类。
- 除此之外,AWT图形用户界面编程里还有两个重要概念:Container和LayoutManager,其中Container是一种特殊的Component,它代表一种容器,可以盛装普通的Component;而LayoutManager则是容器管理其他组件布局的方式。
AWT容器
- 容器(Container)是Component的子类,因此容器对象本身也是一个组件,具有组件的所有性质,可以调用Component类的所有方法。Component类提供了如下几个常用方法来设置组件的大小、位置和可见性等。
setLocation(int x, int y):
设置组件的位置setSize(int width, int height):
设置组件的大小setBounds(int x, int y, int width, int height):
同时设置组件的位置、大小setVisible(Boolean b):
设置该组件的可见性
- 容器还可以盛装其他组件,容器类(Container)提供了如下几个常用方法来访问容器里的组件
Component add(Component comp):
向容器中添加其他组件(该组件既可以是普通组件,也可以是容器),并返回被添加的组件Component getComponentAt(int x, int y):
返回指定点的组件int getComponentCount():
返回该容器内组件的数量Component[] getComponents():
返回该容器内的所有组件
- AWT主要提供了如下两种主要的容器类型
- Window:可独立存在的顶级窗口
- Panel:可作为容器容纳其他组件,但不能独立存在,必须被添加到其他容器中(如Window、Panel或者Applet等)。
- AWT容器的继承关系图如下所示
-
上图显示了AWT容器之间的继承层次,其中以粗黑线圈出的容器是AWT编程中常用的组件。Frame代表常见的窗口,它是Window类的子类,具有如下几个特点。
- Frame对象有标题,允许通过拖拉来改变窗口的位置、大小
- 初始化时为不可见,可以用
setVisible(true)
使其显示出来 - 默认使用BorderLayout作为其布局管理器
-
下面例子程序通过Frame创建了一个窗口。
import java.awt.*;
public class FrameTest {
public static void main(String[] args) {
Frame f = new Frame("测试窗口");
//设置窗口的大小、位置
f.setBounds(30, 30, 250, 200);
//将窗口显示出来(Frame对象默认处于隐藏状态)
f.setVisible(true);
}
}
- 运行上面程序,会看到一个简单的空白窗口。
- 该窗口是由AWT调用程序运行平台的本地API创建的。如果单击窗口右上角的“×”按钮,该窗口并不会关闭,这是因为还未为该窗口编写任何事件响应。如果想关闭该窗口,可以通过关闭运行该程序的命令行窗口进行关闭。
- Panel是AWT中另一个典型的容器,它代表了不能独立存在、必须放在其他容器中的容器。Panel外在表现为一个矩形区域,该区域内可以盛装其他组件。Panel容器存在的意义在于为其他组件提供空间,Panel容器具有如下几个特点:
- 可作为容器来盛装其他组件,为放置组件提供空间
- 不能单独存在,必须放置到其他容器中
- 默认使用FlowLayout作为其布局管理器
- 下面的例子程序使用Panel作为容器来盛装一个文本框课一个按钮,并将该Panel对象添加到Frame对象中。
import java.awt.*;
public class PanelTest {
public static void main(String[] args) {
Frame f = new Frame("测试窗口");
//创建一个Panel容器
Panel p = new Panel();
//向Panel容器中添加两个组件
p.add(new TextField(20));
p.add(new Button("click me"));
//将Panel容器添加到Frame窗口中
f.add(p);
//设置窗口的大小、位置
f.setBounds(30, 30, 250, 120);
//将窗口显示出来
f.setVisible(true);
}
}
- 运行上面程序,会看到下面的运行窗口
- 从上图可以看出,使用AWT创建窗口很简单,程序只需要通过Frame创建,然后在创建一些AWT组件,把这些组件添加到Frame创建的窗口中即可。
- ScrollPane是一个带滚动条的容器,他也不能独立存在,必须被添加到其他容器中。ScrollPane容器具有如下几个特点
- 可以作为容器来盛装其他组件,当组件占用空间过大时,ScrollPane自动产生滚动条。当然也可以通过指定特定的构造器参数来指定默认具有滚动条
- 不能单独存在,必须放置到其他容器中
- 默认使用BorderLayout作为其布局管理器。ScrollPane通常用于盛装其他容器,所以通常不允许改变ScrollPane的布局管理器。
- 下面的例子程序使用ScrollPane容器来代替Panel容器。
import java.awt.*;
public class ScrollPaneTest {
public static void main(String[] args) {
Frame f = new Frame("测试窗口");
//创建一个ScrollPane容器,指定总是具有滚动条
ScrollPane sp = new ScrollPane(
ScrollPane.SCROLLBARS_ALWAYS);
//向ScrollPane容器中添加两个组件
sp.add(new TextField(20));
sp.add(new Button("click me"));
f.add(sp);
f.setBounds(30, 30, 250, 120);
f.setVisible(true);
}
}
- 运行上面程序,会看到如下所示的窗口
- 上图中的窗口具有水平、垂直的滚动条,这符合使用ScrollPane后的效果。但是,我们只能看到按钮,却看不到文本框,这是为什么呢?这是因为ScrollPane使用BorderLayout布局管理器的缘故,而BorderLayout导致了该容器中只有一个组件被显示出来。
布局管理器
- 为了使生成的图形用户界面具有良好的平台无关性,Java语言提供了布局管理器这个工具来管理组件在容器中的布局,而不使用直接设置组件位置和大小的方式。
- 例如通过如下语句定义了一个标签(Label)
Label hello = new Label("Hello java");
- 为了让这个hello标签里刚好可以容纳“Hello Java”字符串,也就是实现该标签的最佳大小(既没有冗余空间,也没有内容被遮挡),Windows可能应该设置长100像素、高20像素,但换到UNIX上则可能需要设置长120像素、高24像素。当一个应用程序从Windows移植到UNIX上时,程序需要做大量的工作来调整图形界面。为了解决这个问题,Java提供了LayoutManager,LayoutManager可以根据运行平台来调整组件的大小,程序员要做的只是为容器选择合适的布局管理器。
- 所有的AWT容器都有默认的布局管理器,如果没有为容器指定布局管理器,则该容器使用默认的布局管理器。为容器指定布局管理器通过调用容器对象的
setLayout(LayoutManager lm)
方法来完成
c.setLayout(new XxxLayout());
- AWT提供了FlowLayout、BorderLayout、GridLayout、GridBagLayout、CardLayout五个常用的布局管理器。Swing还提供了一个BoxLayout布局管理器。
FlowLayout布局管理器
- 在FlowLayout布局管理器中,组件像水流一样向某方向流动(排列),遇到障碍(边界)就折回,从头开始排列。在默认情况下,FlowLayout布局管理器从左到右排列所有组件,遇到边界就会折回从下一行重新开始。
- FlowLayout有如下三个构造器:
FlowLayout():
使用默认的对齐方式及默认的垂直间距、水平间距创建FlowLayout布局管理器FlowLayout(int align):
使用指定的对齐方式及默认的垂直间距、水平间距创建FlowLayout布局管理器FlowLayout(int align, int hgap, int vgap):
使用指定的对齐方式、垂直间距、水平间距。
- 其中align表明FlowLayout中组件的排列方向(从左到右、从右到左、从中间到两边等),该参数应该使用FlowLayout类的静态常量:FlowLayout.LEFT、FlowLayout.RIGHT、FlowLayout.CENTER
- Panel和Applet默认使用FlowLayout布局管理器,下面程序将一个Frame改为使用FlowLayout布局管理器
import java.awt.*;
public class FlowLayoutTest {
public static void main(String[] args) {
Frame f = new Frame("测试窗口");
f.setLayout(new FlowLayout(FlowLayout.LEFT, 20, 5));
for (int i = 0; i < 10; i++)
{
f.add(new Button("click me "+i+" times"));
}
//设置窗口为最佳大小
f.pack();
f.setVisible(true);
}
}
- 结果如图所示
上面程序中执行了f.pack()方法,该方法是Window容器提供的一个方法,该方法用于将窗口调整到最佳大小。通过Java编写图形用户界面程序时,很少直接设置窗口的大小,通常都是调用pack()方法来将窗口调整到最佳大小
BorderLayout布局管理器
- 该布局管理器将容器分为EAST、SOUTH、WEST、NORTH、CENTER五个区域,普通组件可以被放置在这5个区域中的任意一个中。
- 当改变使用BorderLayout的容器大小时,南北中区域水平调整,东西中区域垂直调整,使用BorderLayout有如下两个注意点
- 当向使用BorderLayout布局管理器的容器中添加组件时,需要制定添加到哪个区域。如果没有指定则默认中间区域
- 如果向同一个区域中添加多个组件时,后放入的组件会覆盖先放入的组件。
- Frame、Dialog、ScrollPane默认使用BorderLayout布局管理器,BorderLayout有如下两个构造器
BorderLayout():
使用默认的水平间距、垂直间距创建BorderLayout布局管理器BorderLayout(int hgap, int vgap):
使用指定的水平间距、垂直间距创建BorderLayout布局管理器。
- 当向使用BorderLayout布局管理器的容器中添加组件时。应该使用BorderLayout类的几个静态变量来制定添加到哪个区域中。
import java.awt.*;
import static java.awt.BorderLayout.*;
public class BorderLayoutTest {
public static void main(String[] args) {
Frame f = new Frame("测试窗口");
f.setLayout(new BorderLayout(30, 5));
f.add(new Button("SOUTH"),SOUTH);
f.add(new Button("NORTH"),NORTH);
f.add(new Button("default"));
f.add(new Button("EAST"), EAST);
f.add(new Button("WEST"), WEST);
f.pack();
f.setVisible(true);
}
}
- 运行上面程序,会看到如下的窗口
import java.awt.*;
import static java.awt.BorderLayout.*;
public class BorderLayoutTest2 {
public static void main(String[] args) {
Frame f = new Frame("测试窗口");
f.setLayout(new BorderLayout(30, 5));
f.add(new Button("SOUTH"), SOUTH);
f.add(new Button("NORTH"), NORTH);
Panel p = new Panel();
p.add(new TextField(20));
p.add(new Button("click me"));
f.add(p);
f.add(new Button("EAST"), EAST);
f.pack();
f.setVisible(true);
}
}
- 上面程序没有向WEST添加组件,但是向CENTER区域添加了一个Panel容器,该容器中包含了一个文本框和一个按钮。
GridLayout布局管理器
- GridLayout布局管理器将容器分割成纵横线分隔的网格,每个网格所占的区域大小相同,当向使用GridLayout布局管理器的容器中添加组件时,默认从左向右、从上到下依次添加到每个网格中。与FlowLayout不同的是,放置在GridLayout布局管理器中的各组件的大小由组件所处的区域决定(每个组件将自动占满整个区域)。
- GridLayout有如下两个构造器
GridLayout(int rows, int cols):
采用指定的行数、列数,以及默认的横向间距、纵向间距将容器分割成多个网格。GridLayout(int rows, int cols, int hgap, int vgap):
采用指定的行数、列数,以及指定的横向间距、纵向间距将容器分割成多个网格。
- 如下程序结合BorderLayout和GridLayout开发了一个计算器的可视化窗口。
import java.awt.*;
import static java.awt.BorderLayout.NORTH;
public class GridLayoutTest {
public static void main(String[] args) {
Frame f = new Frame("Calculator");
Panel p1 = new Panel();
p1.add(new TextField(30));
f.add(p1, NORTH);
Panel p2 = new Panel();
p2.setLayout(new GridLayout(3,5,4,4));
String[] name = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "-", "*", "/", "."};
for (int i = 0 ; i < name.length; i++)
{
p2.add(new Button(name[i]));
}
f.add(p2);
f.pack();
f.setVisible(true);
}
}
- 结果如图所示:
GridBagLayout布局管理器
-
GridBagLayout布局管理器功能最强大,但也最复杂,与前面的GridLayout不同的是,GridBagLayout布局管理器中,一个组件可以跨越一个或多个网络,并可以设置各网格的大小互不相同,从而增加了布局的灵活性。当窗口大小发生变化时,GridBagLayout布局管理器也可以准确地控制窗口各部分的拉伸。
-
为了处理GridBagLayout中GUI组件的大小、跨越性,Java提供了GridBagConstraints对象,该对象与特定的GUI组件关联,用于控制该GUI组件的大小、跨越性。
-
使用GridBagLayout布局管理器的步骤如下:
-
创建GridBagLayout布局管理器,并指定GUI容器使用该布局管理器
GridBagLayout gb = new GridBagLayout(); container.setLayout(gb);
-
创建GridBagConstraints对象,并设置该对象的相关属性(用于设置受该对象控制的GUI组件的大小、跨越性等)
gbc.gridx = 2;//网络的横向索引 gbc.gridy = 1;//网络的纵向索引 gbc.gridwidth = 2;//横向跨越多少网格 gbc.gridheight = 1;//纵向跨越多少网格
-
调用GridBagLayout对象的方法来建立GirdBagConstraints对象和受控制组件之间的关联
gb.setConstraints(c, gbc);//设置c组件受gbc组件控制
-
添加组件,与采用普通布局管理器添加组件的方法完全一样
container.add(c);
-
-
如果需要向一个容器中添加多个GUI组件,则需要多次重复步骤2~4,由于GridBagConstraints对象可以重复利用,所以只需要创建一个GridBagConstraints对象,每次添加GUI组件时改变其属性即可。
-
GridBagConstraints对象的属性有下:
- gridx、gridy
- gridwidth、gridheight
- fill:设置受该对象控制的GUI组件如何占据空白区域:
- GridBagConstraints.NONE:GUI组件不扩大
- GridBagConstraints.HORIZONTAL:GUI组件水平扩大以占据空白区域
- GridBagConstraints.VERTICAL
- GridBagConstraints.BOTH
- ipadx、ipady:设置受该对象控制的GUI组件横向、纵向内部填充大小,即在该组件最小尺寸上的基础还需要增大多少。如果设置了这两个属性,则组件横向大小为最小宽度加ipadx*2像素,高度同理
- insets:设置受该对象控制的GUI组件的外部填充大小,即该组件和显示区域边界之间的距离。
- anchor:设置受该对象控制的GUI组件在其显示区域中的定位方式,例如GridBagConstraints.CENTER(中间),同理还有NORTH、NORTHWEST、NORTHEAST、SOUTH、SOUTHEAST、SOUTHWEST、EAST、WEST
- weightx、weighty:设置受该对象控制的GUI组件占据多余空间的水平、垂直增加比例(也叫权重),假设某个容器水平线上有三个GUI组件,他们的水平增加比例分别是1,2,3,当容器宽度增加60像素时,第一个组件宽度增加10像素,第二个20,第三个30.如果其增加比例为0,则表示不会增加。
如果希望某个组件的大小随容器的增大而增大,则必须同时设置控制该组件的GridBagConstraints对象中的fill属性和weightx、weighty属性
- 下面的例子程序示范了如何使用GridBagLayout布局管理器来管理窗口中的10个按钮。
import java.awt.*;
public class GridBagTest {
private Frame f = new Frame("测试窗口");
private GridBagLayout gb = new GridBagLayout();
private GridBagConstraints gbc = new GridBagConstraints();
private Button[] bs = new Button[10];
public void init()
{
f.setLayout(gb);
for (int i = 0; i < bs.length ; i++)
{
bs[i] = new Button("button"+i);
}
//所有组件可以在横向、纵向扩大
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
addButton(bs[0]);
addButton(bs[1]);
addButton(bs[2]);
//该GridBagConstraints控制的GUI组件将会成为横向最后一个组件
gbc.gridwidth = GridBagConstraints.REMAINDER;
addButton(bs[3]);
//该GridBagConstraints控制的GUI组件将在横向上不会扩大
gbc.weightx = 0;
addButton(bs[4]);
//该GridBagConstraints控制的GUI组件将横跨两个网格
gbc.gridwidth = 2;
addButton(bs[5]);
//该GridBagConstraints控制的GUI组件将横跨一个网格,纵跨两个网格,且将成为横向最后一个组件
gbc.gridwidth = 1;
gbc.gridheight = 2;
gbc.gridwidth = GridBagConstraints.REMAINDER;
addButton(bs[6]);
//该GridBagConstraints控制的GUI组件将横跨一个网格,纵跨两个网格,纵向扩大权重为1
gbc.gridwidth = 1;
gbc.gridheight = 2;
gbc.weighty = 1;
addButton(bs[7]);
//下面的按钮在纵向上不会扩大,横向上最后一个组件,纵向上横跨一个网格
gbc.weighty = 0;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.gridheight = 1;
addButton(bs[8]);
addButton(bs[9]);
f.pack();
f.setVisible(true);
}
private void addButton(Button button)
{
gb.setConstraints(button, gbc);
f.add(button);
}
public static void main(String[] args) {
new GridBagTest().init();
}
}
CardLayout布局管理器
- CardLayout布局管理器以时间而非空间来管理它里面的组件,他将加入容器的所有组件看成一叠卡片,每次只有最上面的那个Component才可见。就好像一副扑克牌叠在一起,CardLayout提供了如下两个构造器
CardLayout():
创建默认的CardLayout布局管理器CardLayout(int hgap, int vgap):
通过指定卡片与容器左右边界的间距(hagp)、上下边界(vgap)的间距来创建CardLayout布局管理器。
- CardLayout用于控制组件的可见的五个常用方法如下:
first(Container target):
显示target容器中第一张卡片last(Container target):
显示target容器中最后一张卡片previous(Container target):
显示target容器中前一张卡片next(Container target):
显示target容器中后一张卡片show(Container target, String name):
显示target容器中指定名字的卡片。
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
public class CardLayoutTest {
Frame f = new Frame("测试窗口");
String[] names = {
"1st", "2nd", "3rd", "4th", "5th"};
Panel p1 = new Panel();
public void init()
{
final CardLayout c = new CardLayout();
p1.setLayout(c);
for(int i = 0 ; i < names.length ; i++)
{
p1.add(names[i], new Button(names[i]));
}
Panel p = new Panel();
ActionListener listener = e ->
{
switch (e.getActionCommand())
{
case "previous":
c.previous(p1);
break;
case "next":
c.next(p1);
break;
case "first":
c.first(p1);
break;
case "last":
c.last(p1);
break;
case "third":
c.show(p1, "3rd");
break;
}
};
//控制显示上一张的按钮
Button previous = new Button("previous");
previous.addActionListener(listener);
Button next = new Button("next");
next.addActionListener(listener);
Button first = new Button("first");
first.addActionListener(listener);
Button last = new Button("last");
last.addActionListener(listener);
Button third = new Button("third");
third.addActionListener(listener);
p.add(previous);
p.add(next);
p.add(first);
p.add(last);
p.add(third);
f.add(p1);
f.add(p, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new CardLayoutTest().init();
}
}
- 上面程序通过Frame创建一个窗口,该窗口被BorderLayout分为上下两个部分,其中上面的Panel使用CardLayout布局管理器,该Panel中放置了五张卡片, 每张卡片里放一个按钮;下面的Panel使用FlowLayout布局管理器,依次放置了5个按钮用以控制上面的Panel中卡片的显示。
绝对定位
- Java提供了随意拖动控件的方式,即Java可以对GUI组件进行绝对定位。步骤如下:
- 将Container的布局管理器设置成null:
setLayout(null)
- 向容器中添加组件时,先调用
setBounds()
或setSize()
方法来设置组件的大小、位置,或者直接创建GUI组件时通过构造参数指定该组件的大小、位置,然后将该组件添加到容器中。
- 将Container的布局管理器设置成null:
- 下面程序示范了如何使用绝对定位来控制窗口中的GUI组件:
import java.awt.*;
public class NullLayoutTest {
Frame f = new Frame("测试窗口");
Button b1 = new Button("first button");
Button b2 = new Button("second button");
public void init()
{
f.setLayout(null);
b1.setBounds(20,30, 90, 28);
f.add(b1);
b2.setBounds(50, 45, 120, 35);
f.add(b2);
f.setBounds(50, 50, 200, 100);
f.setVisible(true);
}
public static void main(String[] args) {
new NullLayoutTest().init();
}
}
BoxLayout布局管理器
- GridBagLayout布局管理器虽然功能强大,但是太复杂了,Swing引入了一个新的布局管理器:BoxLayout,它保留了GridBagLayout的很多优点,但是却没那么复杂。BoxLayout可以在垂直和水平两个方向上摆放GUI组件,BoxLayout提供了如下一个简单的构造器。
BoxLayout(Container targer, int axis):
指定创建基于target容器的BoxLayout布局管理器,该布局管理器里的组件按axis方向排列。其中axis方向有BoxLayout.X_AXIS(横向)以及BoxLayout.Y_AXIS(纵向)两个方向
- 下面程序示范了使用BoxLayout布局管理器来控制容器中按钮的布局
import javax.swing.*;
import java.awt.*;
public class BoxLayoutTest {
private Frame f = new Frame("测试窗口");
public void init()
{
f.setLayout(new BoxLayout(f, BoxLayout.Y_AXIS));
//下面按钮将会垂直排列
f.add(new Button("first"));
f.add(new Button("second"));
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new BoxLayoutTest().init();
}
}
- BoxLayout通常会和Box容器结合使用,Box是一种特殊的容器,有点像Panel容器,但该容器默认使用BoxLayout布局管理器。Box提供了如下两个静态方法来创建Box对象
createHorizontalBox():
创建一个水平排列组件的Box容器createVerticalBox():
创建一个垂直排列组件的Box容器
- 一旦获得了Box容器之后,就可以使用Box来盛装普通的GUI组件,然后将这些Box组件添加到其他容器中,从而形成整体的窗口布局。下面的例子程序示范了如何使用Box容器:
import javax.swing.*;
import java.awt.*;
public class BoxTest {
private Frame f = new Frame("测试");
private Box horizontal = Box.createHorizontalBox();
private Box vertical = Box.createVerticalBox();
public void init()
{
horizontal.add(new Button("水平按钮一"));
horizontal.add(new Button("水平按钮二"));
vertical.add(new Button("垂直按钮一"));
vertical.add(new Button("垂直按钮二"));
f.add(horizontal, BorderLayout.NORTH);
f.add(vertical);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new BoxTest().init();
}
}
- BoxLayout使用Glue、Strut和RigidArea的组件来控制组件间的距离。其中Glue代表可以在横向、纵向两个放心上同时拉伸的空白组件(间距),Strut代表可以在横向、纵向任意一个方向上拉伸的空白组件,RigidArea代表不可拉伸的空白组件。
- Box提供了如下五个静态方法来创建Glue、Sturt和RigidArea
createHorizontalGlue():
创建一条水平GluecreateVerticalGlue():
创建一条垂直GluecreateHorizontalStrut(int width):
创建一条指定宽度的水平StrutcreateVerticalStrut(int height):
创建一条指定高度的垂直StrutcreateRigidArea(Dimension d):
创建指定宽度、高度的RigidArea
- 上面五个方法都返回Component对象:
import javax.swing.*;
import java.awt.*;
public class BoxSpaceTest {
private Frame f = new Frame("测试");
private Box horizontal = Box.createHorizontalBox();
private Box vertical = Box.createVerticalBox();
public void init()
{
horizontal.add(new Button("水平按钮一"));
horizontal.add(Box.createHorizontalGlue());
horizontal.add(Box.createHorizontalStrut(10));
horizontal.add(new Button("水平按钮二"));
vertical.add(new Button("垂直按钮一"));
vertical.add(Box.createVerticalGlue());
vertical.add(new Button("垂直按钮二"));
vertical.add(Box.createVerticalStrut(10));
vertical.add(new Button("垂直按钮三"));
f.add(horizontal, BorderLayout.NORTH);
f.add(vertical);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new BoxSpaceTest().init();
}
}
因为BoxLayout是Swing提供的布局管理器,所以用于管理Swing组件将会有更好的表现。
AWT常用组件
- AWT组件需要调用运行平台的图形界面来创建和平台一致的对等体。因此,AWT只能使用所有平台都支持的公共组件,所以AWT只提供了一些常用的GUI组件。
基本组件
- AWT提供了如下基本组件
- Button:按钮,可接受单击操作
- Canvas:用于绘图的画布
- Checkbox:复选框组件(也可以变成单选框组件)
- CheckboxGroup:用于将多个Checkbox组件组合成一组,一组Checkbox组件将只有一个可以被选中,即全部变成单选框组件。
- Choice:下拉式选择框组件
- Frame:窗口
- Label:标签类,用于放置提示性文本
- List:列表框组件,可以添加多项条目
- Panel:不能单独存在基本容器类,必须要放置在其他容器中
- Scrollbar:滑动条组件,如果需要用户输入某个范围的值,就可以使用滑动条组件,比如调色板中设置RGB的三个值所用的滑动条
- ScrollPane:带水平及垂直滚动条的容器组件
- TextArea:多行文本域
- TextField:单行文本域
- 下面的例子程序示范了它们的基本用法
import org.w3c.dom.Text;
import javax.swing.*;
import java.awt.*;
public class CommomComponent {
Frame f = new Frame("test");
Button ok = new Button("确认");
CheckboxGroup cbg = new CheckboxGroup();
Checkbox male = new Checkbox("男", cbg, true);
Checkbox female = new Checkbox("女", cbg, false);
Checkbox married = new Checkbox("是否已婚", false);
Choice colorChooser = new Choice();
List colorList = new List(6, true);
TextArea ta = new TextArea(5, 20);
TextField name = new TextField(50);
public void init()
{
colorChooser.add("红色");
colorChooser.add("绿色");
colorChooser.add("蓝色");
colorList.add("红色");
colorList.add("绿色");
colorList.add("蓝色");
Panel bottom = new Panel();
bottom.add(name);
bottom.add(ok);
f.add(bottom, BorderLayout.SOUTH);
Panel checkPanel = new Panel();
checkPanel.add(colorChooser);
checkPanel.add(male);
checkPanel.add(female);
checkPanel.add(married);
Box topLeft = Box.createVerticalBox();
topLeft.add(ta);
topLeft.add(checkPanel);
Box top = Box.createHorizontalBox();
top.add(topLeft);
top.add(colorList);
f.add(top);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new CommomComponent().init();
}
}
对话框
- Dialog是Window类的子类,是一个容器类,属于特殊组件。对话框是可以独立存在的顶级窗口,因此用法语普通窗口的用法几乎完全一样。但对话框有如下两点需要注意
- 对话框通常依赖于其他窗口,就是通常有一个parent窗口
- 对话框有非模式(non-modal)和模式(modal)两种,当某个模式对话框被打开之后,该模式对话框总是位于它依赖的窗口之上;在模式对话框被关闭之前,它依赖的窗口无法获得焦点。
- 对话框有多个重载的构造器,它的构造器可能有如下三个参数:
- owner:指定该对话框所以来的窗口,既可以是窗口也可以是对话框
- title:指定该对话框的窗口标题
- modal:指定该对话框是否是模式的,是布尔值。
- 下面的例子程序示范了模式对话框和非模式对话框的用法
import java.awt.*;
public class DialogTest {
Frame f = new Frame("test");
Dialog d1 = new Dialog(f, "模式对话框", true);
Dialog d2 = new Dialog(f, "非模式对话框", false);
Button b1 = new Button("打开模式对话框");
Button b2 = new Button("打开非模式对话框");
public void init()
{
d1.setBounds(20, 30, 300, 400);
d2.setBounds(20, 30, 300, 400);
b1.addActionListener(e->d1.setVisible(true));
b2.addActionListener(e -> d2.setVisible(true));
f.add(b1);
f.add(b2, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new DialogTest().init();
}
}
事件处理
- 前面介绍了如何放置各种组件,从而得到了丰富的图形界面,但是这些界面不能响应用户的任何操作。比如单击“×”按钮,窗口并不会关闭。这是因为在AWT编程中,所有事件必须由特定对象(事件监听器)来处理,而Frame和组件本身并没有事件处理能力。
Java事件模型的流程
- 为了使图形界面能够接收用户的操作,必须给各个组件加上事件处理机制。
- 在事件处理过程中,主要涉及三类对象。
Event Source(事件源):
事件发生的场所,通常就是各个组件,例如按钮、窗口、菜单等Event(事件):
事件封装了GUI组件上发生的特定事情(通常是一次用户操作)。如果程序需要获得GUI组件上所发生事件的相关信息,都通过Event对象来取得。Event Listener(事件监听器):
负责监听事件源所发生的事件,并对各种事件做出响应处理。
- 下图显示了AWT的事件处理流程示意图:
- 下面以一个简单的HelloWorld程序来示范AWT事件处理
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class EventQs {
private Frame f = new Frame("test");
private Button ok = new Button("确定");
private TextField tf = new TextField(30);
public void init()
{
//注册事件监听器
ok.addActionListener(new OkListener());
f.add(tf);
f.add(ok, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
//定义事件监听器类
class OkListener implements ActionListener
{
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("用户单击了ok按钮");
tf.setText("Hello world");
}
}
public static void main(String[] args) {
new EventQs().init();
}
}
事件和事件监听器
- AWT事件机制涉及到三个成员:事件源、事件和事件监听器,其中事件源最容易创建,只需要通过new来创建一个AWT组件,该组件就是事件源。事件是由系统自动产生的,无需程序员关心。所以,实现事件监听器是整个事件处理的核心。
- 事件监听器必须实现事件监听器接口,AWT提供了大量的事件监听器接口用于实现不同类型的事件监听器,用来监听不同类型的事件。AWT中提供了大量的事件类,用于封装不同组件上发生的特定操作——AWT的事件类都是AWTEvent的子类,AWTEvent是EventObject的子类。
EventObject类代表更广义的事件对象,包括Swing组件上所触发的事件、数据库连接所触发的事件等。
- AWT事件分为两大类:低级事件和高级事件
低级事件
- 低级事件是指基于特定动作的事件。比如进入、点击、拖放等动作的鼠标指针,当组件得到焦点、失去焦点时触发焦点事件。
- ComponentEvent:组件事件,当组件尺寸变化、位置移动、显示隐藏状态改变时触发该事件
- ContainerEvent:容器事件,容器里发生添加组件、删除组件触发该事件
- WindowEvent:窗口事件,当窗口状态发生改变(如打开、关闭、最大化、最小化)时触发该事件
- FocusEvent:焦点事件,当组件得到焦点或失去焦点时触发该事件
- KeyEvent:键盘事件,当按键被按下、松开、单击时触发该事件
- MouseEvent:鼠标事件,当进行单击、按下、松开、移动鼠标等操作时触发该事件
- PaintEvent:组件绘制事件,该事件是一个特殊的事件类型,当GUI组件调用update/paint方法来呈现自身时触发该事件,该事件并非专用于事件处理模型。
高级事件(语义事件)
- 高级事件是基于语义的事件,它可以不和特定的动作相关联,而依赖于触发此事件的类。比如,在TextField中按Enter键会触发ActionEvent事件,在滑动条上移动滑块会触发AdjustmentEvent事件,选中项目列表的某一项就会触发ItemEvent事件。
- ActionEvent:动作事件,当按钮、菜单项被单击,在TextField中按Enter键时触发该事件
- AdjustmentEvent:调节事件,在滑动条上移动滑块以调解数值时触发该事件
- ItemEvent:选项事件,当用户选中某一项、或取消选中某一项时触发该事件
- TextEvent:文本事件、当文本框、文本域里的文本发生改变时触发该事件。
- AWT事件继承层次图如上所示
-
ActionListener、AdjustmentListener等事件监听器接口只包含一个抽象方法,这种接口也就是前面所介绍的函数式接口,因此可用Lambda表达式来创建监听器对象。
-
下面程序示范了一个监听器监听多个组件,一个组件被多个监听器监听的效果。
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MultiListener {
private Frame f = new Frame("test");
private TextArea ta = new TextArea(6, 40);
private Button b1 = new Button("button 1");
private Button b2 = new Button("button 2");
public void init()
{
FirstListener f1 = new FirstListener();
b1.addActionListener(f1);
b1.addActionListener(new SecondListener());
b2.addActionListener(f1);
f.add(ta);
Panel p = new Panel();
p.add(b1);
p.add(b2);
f.add(p, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
class FirstListener implements ActionListener
{
@Override
public void actionPerformed(ActionEvent e) {
ta.append("第一个事件监听器被触发,事件源是:"+e.getActionCommand()+"\n");
}
}
class SecondListener implements ActionListener
{
@Override
public void actionPerformed(ActionEvent e) {
ta.append("单击了“"+e.getActionCommand()+"”按钮\n");
}
}
public static void main(String[] args) {
new MultiListener().init();
}
}
- 下面程序为窗口添加窗口监听器,从而示范窗口监听器的用法,并允许用户单击窗口右上角的”ד按钮来结束程序。
import java.awt.*;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
public class WindowListenerTest {
private Frame f = new Frame("test");
private TextArea ta = new TextArea(6, 40);
public void init()
{
f.addWindowListener(new MyListener());
f.add(ta);
f.pack();
f.setVisible(true);
}
class MyListener implements WindowListener
{
@Override
public void windowActivated(WindowEvent e) {
ta.append("窗口被激活\n");
}
@Override
public void windowClosed(WindowEvent e) {
ta.append("窗口被关闭\n");
}
@Override
public void windowClosing(WindowEvent e) {
ta.append("用户关闭窗口\n");
System.exit(0);
}
@Override
public void windowDeactivated(WindowEvent e) {
ta.append("窗口失去焦点\n");
}
@Override
public void windowDeiconified(WindowEvent e) {
ta.append("窗口被恢复\n");
}
@Override
public void windowIconified(WindowEvent e) {
ta.append("窗口被最小化\n");
}
@Override
public void windowOpened(WindowEvent e) {
ta.append("窗口初次被打开\n");
}
}
public static void main(String[] args)
throws Exception
{
new WindowListenerTest().init();
}
}
事件适配器
- 事件适配器是监听器接口的空实现——事件适配器实现了监听器接口,并为该接口里的每个方法都提供了实现,这种实现是一种空实现(方法体内没有任何代码)。当需要创建监听器时,可以通过继承事件适配器,而不是实现监听器接口。因为事件适配器里已经为监听器接口的每一个方法提供了空实现,所以程序自己的监听器无需实现监听器接口里的每一个方法,只需要重写自己感兴趣的方法,从而可以简化事件监听器的实现类代码。
- 从下表可以看出,所有包含多个方法的监听器接口都有一个对应的适配器。
监听器接口 | 事件适配器 |
---|---|
ContainerListener | ContainerAdapter |
FocusListener | FocusAdapter |
ComponentListener | ComponentAdapter |
KeyListener | KeyAdapter |
MouseListener | MouseAdapter |
MouseMotionListener | MouseMotionAdapter |
WindowListener | WindowAdapter |
- 下面程序通过事件适配器来创建事件监听器
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class WindowAdapterTest {
private Frame f = new Frame("test");
private TextArea ta = new TextArea(6, 40);
public void init()
{
f.addWindowListener(new MyListener());
f.add(ta);
f.pack();
f.setVisible(true);
}
class MyListener extends WindowAdapter
{
@Override
public void windowClosing(WindowEvent e) {
System.out.println("用户关闭窗口\n");
System.exit(0);
}
}
public static void main(String[] args) {
new WindowAdapterTest().init();
}
}
- 从上面程序中可以看出,窗口监听器继承了WindowAdapter事件适配器,只需要重写windowClosing方法即可。
使用内部类实现监听器
- 事件监听器是一个特殊的Java对象,实现事件监听器对象有如下几种形式
- 内部类形式:将事件监听器定义为当前类的内部类
- 外部类形式:将事件监听器类定义为一个外部类
- 类本身作为事件监听器类
- 匿名内部类形式
- 前面示例程序中的所有事件监听类都是内部类形式。
使用外部类实现监听器
- 这种形式比较少见,主要有两个原因
- 事件监听器通常属于特定的GUI界面,定义成外部类不利于提高程序的内聚性。
- 外部类形式的事件监听器不能自由访问创建GUI界面类中的组件,编程不够简洁。
- 但是如果某个事件监听器确实需要被多个GUI界面共享,且主要是完成某种业务逻辑的实现,则可以考虑使用外部类形式来定义事件监听器。
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MailerListener implements ActionListener {
private TextField mailAddress;
public MailerListener(){
}
public MailerListener(TextField mailAddress)
{
this.mailAddress = mailAddress;
}
public void setMailAddress(TextField mailAddress)
{
this.mailAddress = mailAddress;
}
public void actionPerformed(ActionEvent e)
{
System.out.println("程序向“"+mailAddress.getText()+"”发送邮件...");
}
}
- 上面的事件监听器类没有与任何GUI界面耦合,创建该界面对象时传入一个TextField对象,该文本框里的字符串将被作为收件人地址,下面程序使用了该事件监听器来监听窗口中的按钮。
import java.awt.*;
public class SendMailer {
private Frame f = new Frame("test");
private TextField tf = new TextField(40);
private Button send = new Button("send");
public void init()
{
send.addActionListener(new MailerListener(tf));
f.add(tf);
f.add(send, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new SendMailer().init();
}
}
类本身作为事件监听器类
- 这种方式比较简单,也是早期AWT事件编程里常采用的形式,但是有两种缺点:
- 这种形式可能造成混乱的程序结构,GUI界面的职责主要是完成界面初始化工作,但此时还需要包含事件处理器方法,从而降低了程序的可读性。
- 如果GUI界面类需要继承事件适配器,将会导致该GUI界面类不能继承其他父类。
- 下面程序使用GUI界面类作为事件监听器类。
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class SimpleEventHandler extends WindowAdapter {
private Frame f = new Frame("test");
private TextArea ta = new TextArea(6, 40);
public void init()
{
//将该类的默认对象作为事件监听器对象
f.addWindowListener(this);
f.add(ta);
f.pack();
f.setVisible(true);
}
public void windowClosing(WindowEvent e)
{
System.out.println("用户关闭窗口\n");
System.exit(0);
}
public static void main(String[] args) {
new SimpleEventHandler().init();
}
}
匿名内部类实现监听器
- 大部分时候,事件处理器都没有复用价值,所以使用匿名内部类形式的事件监听器最合适。
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class AnonymousEventHandler {
private Frame f = new Frame("test");
private TextArea ta = new TextArea(6, 40);
public void init()
{
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.out.println("用户试图关闭窗口\n");
System.exit(0);
}
});
f.add(ta);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new AnonymousEventHandler().init();
}
}