1 用AWT生成图形化用户界面
抽象窗口工具包AWT (Abstract Window Toolkit) 是 API为Java 程序提供的建立图形用户界面GUI (Graphics User Interface)工具集,AWT可用于Java的applet和applications中。它支持图形用户界面编程的功能包括: 用户界面组件;事件处理模型;图形和图像工具,包括形状、颜色和字体类;布局管理器,可以进行灵活的窗口布局而与特定窗口的尺寸和屏幕分辨率无关;数据传送类,可以通过本地平台的剪贴板来进行剪切和粘贴。
1.1 java.awt包
java.awt包中提供了GUI设计所使用的类和接口,可从图5.1中看到主要类之间的关系。
java.awt包提供了基本的java程序的GUI设计工具。主要包括下述三个概念:
组件--Component
容器--Container
布局管理器--LayoutManager
1.2 组件和容器
Java的图形用户界面的最基本组成部分是组件(Component),组件是一个可以以图形化的方式显示在屏幕上并能与用户进行交互的对象,例如一个按钮,一个标签等。组件不能独立地显示出来,必须将组件放在一定的容器中才可以显示出来。
类java.awt.Component是许多组件类的父类,Component类中封装了组件通用的方法和属性,如图形的组件对象、大小、显示位置、前景色和背景色、边界、可见性等,因此许多组件类也就继承了Component类的成员方法和成员变量,相应的成员方法包括:
getComponentAt(int x, int y)
getFont()
getForeground()
getName()
getSize()
paint(Graphics g)
repaint()
update()
setVisible(boolean b)
setSize(Dimension d)
setName(String name)等
容器(Container)也是一个类,实际上是Component的子类,因此容器本身也是一个组件,具有组件的所有性质,但是它的主要功能是容纳其它组件和容器。
布局管理器(LayoutManager):每个容器都有一个布局管理器,当容器需要对某个组件进行定位或判断其大小尺寸时,就会调用其对应的布局管理器。
为了使我们生成的图形用户界面具有良好的平台无关性,Java语言中,提供了布局管理器这个工具来管理组件在容器中的布局,而不使用直接设置组件位置和大小的方式。
在程序中安排组件的位置和大小时,应该注意以下两点:
1.容器中的布局管理器负责各个组件的大小和位置,因此用户无法在这种情况下设置组件的这些属性。如果试图使用Java 语言提供的setLocation(),setSize(),setBounds() 等方法,则都会被布局管理器覆盖。
2.如果用户确实需要亲自设置组件大小或位置,则应取消该容器的布局管理器,方法为:
setLayout(null);
1.3 常用容器
容器java.awt.Container是Component的子类,一个容器可以容纳多个组件,并使它们成为一个整体。容器可以简化图形化界面的设计,以整体结构来布置界面。所有的容器都可以通过add()方法向容器中添加组件。
有三种类型的容器:Window、Panel、ScrollPane,常用的有Panel, Frame, Applet。
1.Frame
以下是容器的例子:
例5.1
import java.awt.*;
public class MyFrame extends Frame{
public static void main(String args[ ]){
MyFrame fr = new MyFrame("Hello Out There!");
//构造方法
fr.setSize(200,200);
//设置Frame的大小,缺省为(0,0)
fr.setBackground(Color.red);
//设置Frame的背景,缺省为红色
fr.setVisible(true);
//设置Frame为可见,缺省为不可见
}
public MyFrame (String str){
super(str); //调用父类的构造方法
}
}
查看运行结果
一般我们要生成一个窗口,通常是用Window的子类Frame来进行实例化,而不是直接用到Window类。Frame的外观就像我们平常在windows系统下见到的窗口,有标题、边框、菜单、大小等等。每个Frame的对象实例化以后,都是没有大小和不可见的,因此必须调用setSize( )来设置大小,调用setVisible(true)来设置该窗口为可见的。
另外,AWT在实际的运行过程中是调用所在平台的图形系统,因此同样一段AWT程序在不同的操作系统平台下运行所看到的图形系统是不一样的。例如在windows下运行,则显示的窗口是windows风格的窗口;而在UNIX下运行时,则显示的是UNIX风格的窗口。
2. Panel
例5.2
import java.awt.*;
public class FrameWithPanel extends Frame{
public FrameWithPanel(String str){
super(str);
}
public static void main(String args[]){
FrameWithPanel fr = new FrameWithPanel("Frame with Panel");
Panel pan=new Panel();
fr.setSize(200,200);
fr.setBackground(Color.red);
//框架fr的背景颜色设置为红色
fr.setLayout(null);
//取消布局管理器
pan.setSize(100,100);
pan.setBackground(Color.yellow);
//设置面板pan的背景颜色为黄色
fr.add(pan); //用add方法把面板pan添加到框架fr中
fr.setVisible(true);
}
}
查看运行结果
一般我们要生成一个窗口,通常是用Window的子类Frame来进行实例化,而不是直接用到Window类。Frame的外观就像我们平常在windows系统下见到的窗口,有标题、边框、菜单、大小等等。每个Frame的对象实例化以后,都是没有大小和不可见的,因此必须调用setSize( )来设置大小,调用setVisible(true)来设置该窗口为可见的。
另外,AWT在实际的运行过程中是调用所在平台的图形系统,因此同样一段AWT程序在不同的操作系统平台下运行所看到的图形系统是不一样的。例如在windows下运行,则显示的窗口是windows风格的窗口;而在UNIX下运行时,则显示的是UNIX风格的窗口。
1.4 LayoutManager 布局管理器(1)
java为了实现跨平台的特性并且获得动态的布局效果,java将容器内的所有组件安排给一个"布局管理器"负责管理,如:排列顺序,组件的大小、位置,当窗口移动或调整大小后组件如何变化等功能授权给对应的容器布局管理器来管理,不同的布局管理器使用不同算法和策略,容器可以通过选择不同的布局管理器来决定布局。
布局管理器主要包括:FlowLayout,BorderLayout,GridLayout,CardLayout,GridBagLayout
例5.3
import java.awt.*;
public class ExGui{
private Frame f;
private Button b1;
private Button b2;
public static void main(String args[]){
ExGui that = new ExGui();
that.go();
}
public void go(){
f = new Frame("GUI example");
f.setLayout(new FlowLayout());
//设置布局管理器为FlowLayout
b1 = new Button("Press Me");
//按钮上显示字符"Press Me"
b2 = new Button("Don't Press Me");
f.add(b1);
f.add(b2);
f.pack();
//紧凑排列,其作用相当于setSize(),即让窗口
尽量小,小到刚刚能够包容住b1、b2两个按钮
f.setVisible(true);
}
}
查看运行结果
1. FlowLayout
FlowLayout 是Panel,Applet的缺省布局管理器。其组件的放置规律是从上到下、从左到右进行放置,如果容器足够宽,第一个组件先添加到容器中第一行的最左边,后续的组件依次添加到上一个组件的右边,如果当前行已放置不下该组件,则放置到下一行的最左边。
构造方法主要下面几种:
FlowLayout(FlowLayout.RIGHT,20,40);
/*第一个参数表示组件的对齐方式,指组件在这一行中的位置是居中对齐、居右对齐还是居左对齐,第二个参数是组件之间的横向间隔,第三个参数是组件之间的纵向间隔,单位是象素。*/
FlowLayout(FlowLayout.LEFT);
//居左对齐,横向间隔和纵向间隔都是缺省值5个象素
FlowLayout();
//缺省的对齐方式居中对齐,横向间隔和纵向间隔都是缺省值5个象素
例5.4
import java.awt.*;
public class myButtons{
public static void main(String args[])
{
Frame f = new Frame();
f.setLayout(new FlowLayout());
Button button1 = new Button("Ok");
Button button2 = new Button("Open");
Button button3 = new Button("Close");
f.add(button1);
f.add(button2);
f.add(button3);
f.setSize(300,100);
f.setVisible(true);
}
}
查看运行结果
当容器的大小发生变化时,用FlowLayout管理的组件会发生变化,其变化规律是:组件的大小不变,但是相对位置会发生变化。例如上图中有三个按钮都处于同一行,但是如果把该窗口变窄,窄到刚好能够放下一个按钮,则第二个按钮将折到第二行,第三个按钮将折到第三行。按钮"Open"本来在按钮"OK"的右边,但是现在跑到了下面,所以说"组件的大小不变,但是相对位置会发生变化"。
2. BorderLayout
BorderLayout 是Window,Frame和Dialog的缺省布局管理器。BorderLayout布局管理器把容器分成5个区域:North,South,East,West和Center,每个区域只能放置一个组件。各个区域的位置及大小如下图所示:
例5.5
import java.awt.*;
public class buttonDir{
public static void main(String args[]){
Frame f = new Frame("BorderLayout");
f.setLayout(new BorderLayout());
f.add("North", new Button("North"));
//第一个参数表示把按钮添加到容器的North区域
f.add("South", new Button("South"));
//第一个参数表示把按钮添加到容器的South区域
f.add("East", new Button("East"));
//第一个参数表示把按钮添加到容器的East区域
f.add("West", new Button("West"));
//第一个参数表示把按钮添加到容器的West区域
f.add("Center", new Button("Center"));
//第一个参数表示把按钮添加到容器的Center区域
f.setSize(200,200);
f.setVisible(true);
}
}
查看运行结果
在使用BorderLayout的时候,如果容器的大小发生变化,其变化规律为:组件的相对位置不变,大小发生变化。例如容器变高了,则North、South区域不变,West、Center、East区域变高;如果容器变宽了,West、East区域不变,North、Center、South区域变宽。不一定所有的区域都有组件,如果四周的区域(West、East、North、South区域)没有组件,则由Center区域去补充,但是如果Center区域没有组件,则保持空白,其效果如下几幅图所示:
North区域缺少组件
North和Center区域缺少组件
3. GridLayout
使容器中各个组件呈网格状布局,平均占据容器的空间。
例5.6
import java.awt.*;
public class ButtonGrid {
public static void main(String args[]) {
Frame f = new Frame("GridLayout");
f.setLayout(new GridLayout(3,2));
//容器平均分成3行2列共6格
f.add(new Button("1")); //添加到第一行的第一格
f.add(new Button("2")); //添加到第一行的下一格
f.add(new Button("3")); //添加到第二行的第一格
f.add(new Button("4")); //添加到第二行的下一格
f.add(new Button("5")); //添加到第三行的第一格
f.add(new Button("6")); //添加到第三行的下一格
f.setSize(200,200);
f.setVisible(true);
}
}
1.4 LayoutManager 布局管理器(2)
4. CardLayout
CardLayout布局管理器能够帮助用户处理两个以至更多的成员共享同一显示空间,它把容器分成许多层,每层的显示空间占据整个容器的大小,但是每层只允许放置一个组件,当然每层都可以利用Panel来实现复杂的用户界面。牌布局管理器(CardLayout)就象一副叠得整整齐齐的扑克牌一样,有54张牌,但是你只能看见最上面的一张牌,每一张牌就相当于牌布局管理器中的每一层。
例5.7
import java.awt.*;
import java.awt.event.*; //事件处理机制,下一节的内容
public class ThreePages implements MousListener {
CardLayout layout=new CardLayout(); //实例化一个牌布局管理器对象
Frame f=new Frame("CardLayout");
Button page1Button;
Label page2Label; //Label是标签,实际上是一行字符串
TextArea page3Text; //多行多列的文本区域
Button page3Top;
Button page3Bottom;
public static void main(String args[])
{ new ThreePages().go(); }
Public void go()
{ f.setLayout(layout); //设置为牌布局管理器layout
f.add(page1Button=new Button("Button page"),"page1Button"); /*第二个参数"page1Button"表示的是你对这层牌所取的名字*/
page1Button.addMouseListener(this); //注册监听器
f.add(page2Label=new Label("Label page"),"page2Label");
page2Label.addMouseLisener(this); //注册监听器
Panel panel=new Panel();
panel.setLayout(new BorderLayout());
panel.add(page3Text=new TextArea("Composite page"),"Center");
page3Text.addMouseListener(this);
panel.add(page3Top=new Button("Top button") , "North");
page3Top.addMouseListener(this);
panel.add(page3Bottom=new Button("Bottom button") ,"South");
page3Bottom.addMouseListener(this);
f.add(panel,"panel");
f.setSize(200,200);
f.setVisible(true);
}
……
} 5.容器的嵌套
在复杂的图形用户界面设计中,为了使布局更加易于管理,具有简洁的整体风格,一个包含了多个组件的容器本身也可以作为一个组件加到另一个容器中去,容器中再添加容器,这样就形成了容器的嵌套。下面是一个容器嵌套的例子。
例5.8
import java.awt.*;
public class ExGui3{
private Frame f;
private Panel p;
private Button bw,bc;
private Button bfile,bhelp;
public static void main(String args[])
{
ExGui3 gui = new ExGui3();
gui.go();
}
public void go(){
f = new Frame("GUI example 3");
bw=new Button("West");
bc=new Button("Work space region");
f.add(bw,"West");
f.add(bc,"Center");
p = new Panel();
f.add(p,"North");
bfile= new Button("File");
bhelp= new Button("Help");
p.add(bfile);
p.add(bhelp);
f.pack();
f.setVisible(true);
}
}
查看运行结果
小 结:
1.Frame是一个顶级窗口。Frame的缺省布局管理器为BorderLayout。
2.Panel 无法单独显示,必须添加到某个容器中。 Panel 的缺省布局管理器为FlowLayout。
3.当把Panel 作为一个组件添加到某个容器中后,该Panel 仍然可以有自己的布局管理器。因此,可以利用Panel 使得BorderLayout 中某个区域显示多个组件,达到设计复杂用户界面的目的 。
4.如果采用无布局管理器 setLayout(null),则必须使用setLocation(),setSize(),setBounds()等方法手工设置组件的大小和位置,此方法会导致平台相关,不鼓励使用。
2 AWT事件处理模型
上一节中的主要内容是如何放置各种组件,使图形界面更加丰富多彩,但是还不能响应用户的任何操作,要能够让图形界面接收用户的操作,就必须给各个组件加上事件处理机制。在事件处理的过程中,主要涉及三类对象:
◇ Event-事件,用户对界面操作在java语言上的描述,以类的形式出现,例如键盘操作对应的事件类是KeyEvent。
◇ Event Source-事件源,事件发生的场所,通常就是各个组件,例如按钮Button。
◇ Event handler-事件处理者,接收事件对象并对其进行处理的对象
例如,如果用户用鼠标单击了按钮对象button,则该按钮button就是事件源,而java运行时系统会生成ActionEvent类的对象actionE,该对象中描述了该单击事件发生时的一些信息,然后,事件处理者对象将接收由java运行时系统传递过来的事件对象actionE并进行相应的处理。
由于同一个事件源上可能发生多种事件,因此java采取了授权处理机制(Delegation Model),事件源可以把在其自身所有可能发生的事件分别授权给不同的事件处理者来处理。比如在Canvas对象上既可能发生鼠标事件,也可能发生键盘事件,该Canvas对象就可以授权给事件处理者一来处理鼠标事件,同时授权给事件处理者二来处理键盘事件。有时也将事件处理者称为监听器,主要原因也在于监听器时刻监听着事件源上所有发生的事件类型,一旦该事件类型与自己所负责处理的事件类型一致,就马上进行处理。授权模型把事件的处理委托给外部的处理实体进行处理,实现了将事件源和监听器分开的机制。事件处理者(监听器)通常是一个类,该类如果要能够处理某种类型的事件,就必须实现与该事件类型相对的接口。例如例5.9中类ButtonHandler之所以能够处理ActionEvent事件,原因在于它实现了与ActionEvent事件对应的接口ActionListener。每个事件类都有一个与之相对应的接口。
将事件源对象和事件处理器(事件监听器)分开。如图5.2所示
打个不太恰当的比喻,比如说有一位李先生,李先生可能会发生很多法律纠纷,可能是民事法律纠纷,也可能是刑事法律纠纷,那么李先生可以请律师,他可以授权王律师负责帮他打民事法律的官司,同时也可以授权张律师帮他打刑事法律的官司。这个请律师的过程从李先生的角度来看,就是授权的过程,而从王律师和张律师的角度来看,一旦被授权,他们就得时刻对李先生负责,"监听"着李先生,一旦发生民事纠纷了,王律师就要马上去处理,而一旦发生刑事纠纷了,张律师就要马上进行处理。此时此刻,李先生就是事件源,王律师是一个事件处理者,张律师是另外一个事件处理者,民事纠纷和刑事纠纷就是不同类型的事件。
例5.9
import java.awt.*;
import java.awt.event.*;
public class TestButton {
public static void main(String args[])
{
Frame f = new Frame("Test");
Button b = new Button("Press Me!");
b.addActionListener(new ButtonHandler()); /*注册监听器进行授权,该方法的参数是事件处理者对象,要处理的事件类型可以从方法名中看出,例如本方法要授权处理的是ActionEvent,因为方法名是addActionListener。*/
f.setLayout(new FlowLayout()); //设置布局管理器
f.add(b);
f.setSize(200,100);
f.setVisible(true);
}
}
class ButtonHandler implements ActionListener {
//实现接口ActionListener才能做事件ActionEvent的处理者
public void actionPerformed(ActionEvent e)
//系统产生的ActionEvent事件对象被当作参数传递给该方法
{
System.out.println("Action occurred");
//本接口只有一个方法,因此事件发生时,系统会自动调用本方法,需要做的操作就把代码写在则个方法里。
}
}
使用授权处理模型进行事件处理的一般方法归纳如下:
1.对于某种类型的事件XXXEvent, 要想接收并处理这类事件,必须定义相应的事件监听器类,该类需要实现与该事件相对应的接口XXXListener;
2.事件源实例化以后,必须进行授权,注册该类事件的监听器,使用addXXXListener(XXXListener ) 方法来注册监听器。
2.1 事件类
与AWT有关的所有事件类都由java.awt.AWTEvent类派生,它也是EventObject类的子类。AWT事件共有10类,可以归为两大类:低级事件和高级事件。
java.util.EventObject类是所有事件对象的基础父类,所有事件都是由它派生出来的。AWT的相关事件继承于java.awt.AWTEvent类,这些AWT事件分为两大类:低级事件和高级事件,低级事件是指基于组件和容器的事件,当一个组件上发生事件,如:鼠标的进入,点击,拖放等,或组件的窗口开关等,触发了组件事件。高级事件是基于语义的事件,它可以不和特定的动作相关联,而依赖于触发此事件的类,如在TextField中按Enter键会触发ActionEvent事件,滑动滚动条会触发AdjustmentEvent事件,或是选中项目列表的某一条就会触发ItemEvent事件。
◇ 低级事件
ComponentEvent( 组件事件:组件尺寸的变化,移动)
ContainerEvent( 容器事件:组件增加,移动)
WindowEvent( 窗口事件:关闭窗口,窗口闭合,图标化)
FocusEvent( 焦点事件:焦点的获得和丢失)
KeyEvent( 键盘事件:键按下、释放)
MouseEvent( 鼠标事件:鼠标单击,移动)
◇ 高级事件(语义事件)
ActionEvent(动作事件:按钮按下,TextField中按Enter键)
AdjustmentEvent(调节事件:在滚动条上移动滑块以调节数值)
ItemEvent(项目事件:选择项目,不选择"项目改变")
TextEvent(文本事件,文本对象改变)
2.2 事件监听器
每类事件都有对应的事件监听器,监听器是接口,根据动作来定义方法。
例如,与键盘事件KeyEvent相对应的接口是:
public interface KeyListener extends EventListener {
public void keyPressed(KeyEvent ev);
public void keyReleased(KeyEvent ev);
public void keyTyped(KeyEvent ev);
}
注意到在本接口中有三个方法,那么java运行时系统何时调用哪个方法?其实根据这三个方法的方法名就能够知道应该是什么时候调用哪个方法执行了。当键盘刚按下去时,将调用keyPressed( )方法执行,当键盘抬起来时,将调用keyReleased( )方法执行,当键盘敲击一次时,将调用keyTyped( )方法执行。
又例如窗口事件接口:
public interface WindowListener extends EventListener{
public void windowClosing(WindowEvent e);
//把退出窗口的语句写在本方法中
public void windowOpened(WindowEvent e);
//窗口打开时调用
public void windowIconified(WindowEvent e);
//窗口图标化时调用
public void windowDeiconified(WindowEvent e);
//窗口非图标化时调用
public void windowClosed(WindowEvent e);
//窗口关闭时调用
public void windowActivated(WindowEvent e);
//窗口激活时调用
public void windowDeactivated(WindowEvent e);
//窗口非激活时调用
}
AWT的组件类中提供注册和注销监听器的方法:
◇ 注册监听器:
public void add<ListenerType> (<ListenerType>listener);
◇ 注销监听器:
public void remove<ListenerType> (<ListenerType>listener);
例如Button类:(查API)
public class Button extends Component {
……
public synchronized void addActionListener(ActionListener l);
public synchronized void removeActionListener(ActionListener l);
……}
2.3 AWT事件及其相应的监听器接口(1)
表5.1列出了所有AWT事件及其相应的监听器接口,一共10类事件,11个接口。下面这张表应能牢牢记住。
表5.1
事件类别 | 描述信息 | 接口名 | 方法 |
ActionEvent | 激活组件 | ActionListener | actionPerformed(ActionEvent) |
ItemEvent | 选择了某些项目 | ItemListener | itemStateChanged(ItemEvent) |
MouseEvent | 鼠标移动 | MouseMotionListener | mouseDragged(MouseEvent) mouseMoved(MouseEvent) |
鼠标点击等 | MouseListener | mousePressed(MouseEvent) mouseReleased(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mouseClicked(MouseEvent) | |
KeyEvent | 键盘输入 | KeyListener | keyPressed(KeyEvent) keyReleased(KeyEvent) keyTyped(KeyEvent) |
FocusEvent | 组件收到或失去焦点 | FocusListener | focusGained(FocusEvent) focusLost(FocusEvent) |
AdjustmentEvent | 移动了滚动条等组件 | AdjustmentListener | adjustmentValueChanged(AdjustmentEvent) |
ComponentEvent | 对象移动缩放显示隐藏等 | ComponentListener | componentMoved(ComponentEvent) componentHidden(ComponentEvent) componentResized(ComponentEvent) componentShown(ComponentEvent) |
WindowEvent | 窗口收到窗口级事件 | WindowListener | windowClosing(WindowEvent) windowOpened(WindowEvent) windowIconified(WindowEvent) windowDeiconified(WindowEvent) windowClosed(WindowEvent) windowActivated(WindowEvent) windowDeactivated(WindowEvent) |
ContainerEvent | 容器中增加删除了组件 | ContainerListener | componentAdded(ContainerEvent) componentRemoved(ContainerEvent) |
TextEvent | 文本字段或文本区发生改变 | TextListener | textValueChanged(TextEvent) |
.2.3 AWT事件及其相应的监听器接口(2)
例5.10说明事件处理模型的应用。
例5.10
import java.awt.*;
import java.awt.event.*;
public class ThreeListener implements MouseMotionListener,MouseListener,WindowListener {
//实现了三个接口
private Frame f;
private TextField tf;
public static void main(String args[])
{
ThreeListener two = new ThreeListener();
two.go(); }
public void go() {
f = new Frame("Three listeners example");
f.add(new Label("Click and drag the mouse"),"North");
tf = new TextField(30);
f.add(tf,"South"); //使用缺省的布局管理器
f.addMouseMotionListener(this); //注册监听器MouseMotionListener
f.addMouseListener(this); //注册监听器MouseListener
f.addWindowListener(this); //注册监听器WindowListener
f.setSize(300,200);
f.setVisible(true);
}
public void mouseDragged (MouseEvent e) {
//实现mouseDragged方法
String s = "Mouse dragging : X="+e.getX()+"Y = "+e.getY();
tf.setText(s);
}
public void mouseMoved(MouseEvent e){}
//对其不感兴趣的方法可以方法体为空
public void mouseClicked(MouseEvent e){}
public void mouseEntered(MouseEvent e){
String s = "The mouse entered";
tf.setText(s);
}
public void mouseExited(MouseEvent e){
String s = "The mouse has left the building";
tf.setText(s);
}
public void mousePressed(MouseEvent e){}
public void mouseReleased(MouseEvent e){ }
public void windowClosing(WindowEvent e) {
//为了使窗口能正常关闭,程序正常退出,需要实现windowClosing方法
System.exit(1);
}
public void windowOpened(WindowEvent e) {}
//对其不感兴趣的方法可以方法体为空
public void windowIconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
public void windowActivated(WindowEvent e) { }
public void windowDeactivated(WindowEvent e) {}
}
上例中有如下几个特点:
1.可以声明多个接口,接口之间用逗号隔开。
……implements MouseMotionListener, MouseListener, WindowListener;
2.可以由同一个对象监听一个事件源上发生的多种事件:
f.addMouseMotionListener(this);
f.addMouseListener(this);
f.addWindowListener(this);
则对象f 上发生的多个事件都将被同一个监听器接收和处理。
3.事件处理者和事件源处在同一个类中。本例中事件源是Frame f,事件处理者是类ThreeListener,其中事件源Frame f是类ThreeListener的成员变量。
4.可以通过事件对象获得详细资料,比如本例中就通过事件对象获得了鼠标发生时的坐标值。
public void mouseDragged(MouseEvent e) {
String s="Mouse dragging :X="+e.getX()+"Y="+e.getY();
tf.setText(s);
}
Java语言类的层次非常分明,因而只支持单继承,为了实现多重继承的能力,Java用接口来实现,一个类可以实现多个接口,这种机制比多重继承具有更简单、灵活、更强的功能。在AWT中就经常用到声明和实现多个接口。记住无论实现了几个接口,接口中已定义的方法必须一一实现,如果对某事件不感兴趣,可以不具体实现其方法,而用空的方法体来代替。但却必须所有方法都要写上。
2.4 事件适配器
Java语言为一些Listener接口提供了适配器(Adapter)类。可以通过继承事件所对应的Adapter类,重写需要方法,无关方法不用实现。事件适配器为我们提供了一种简单的实现监听器的手段, 可以缩短程序代码。但是,由于java的单一继承机制,当需要多种监听器或此类已有父类时,就无法采用事件适配器了。
1.事件适配器--EventAdapter
下例中采用了鼠标适配器:
import java.awt.*;
import java.awt.event.*;
public class MouseClickHandler extends MouseAdaper{
public void mouseClicked(MouseEvent e) //只实现需要的方法
{ ……}
}
java.awt.event包中定义的事件适配器类包括以下几个:
1.ComponentAdapter( 组件适配器)
2.ContainerAdapter( 容器适配器)
3.FocusAdapter( 焦点适配器)
4.KeyAdapter( 键盘适配器)
5.MouseAdapter( 鼠标适配器)
6.MouseMotionAdapter( 鼠标运动适配器)
7.WindowAdapter( 窗口适配器)
2. 用内部类实现事件处理
内部类(inner class)是被定义于另一个类中的类,使用内部类的主要原因是由于:
◇ 一个内部类的对象可访问外部类的成员方法和变量,包括私有的成员。
◇ 实现事件监听器时,采用内部类、匿名类编程非常容易实现其功能。
◇ 编写事件驱动程序,内部类很方便。
因此内部类所能够应用的地方往往是在AWT的事件处理机制中。
例5.11
import java.awt.* ;
import java.awt.event.*;
public class InnerClass{
private Frame f;
private TextField tf;
public InnerClass(){
f=new Frame("Inner classes example");
tf=new TextField(30);
}
public voidi launchFrame(){
Label label=new Label("Click and drag the mouse");
f.add(label,BorderLayout.NORTH);
f.add(tf,BorderLayout.SOUTH);
f.addMouseMotionListener(new MyMouseMotionListener());/*参数为内部类对象*/
f.setSize(300,200);
f.setVisible(true);
}
class MyMouseMotionListener extends MouseMotionAdapter{ /*内部类开始*/
public void mouseDragged(MouseEvent e) {
String s="Mouse dragging: x="+e.getX()+"Y="+e.getY();
tf.setText(s); }
} ;
public static void main(String args[]) {
InnerClass obj=new InnerClass();
obj.launchFrame();
}
}//内部类结束
}
3.匿名类(Anonymous Class)
当一个内部类的类声名只是在创建此类对象时用了一次,而且要产生的新类需继承于一个已有的父类或实现一个接口,才能考虑用匿名类,由于匿名类本身无名,因此它也就不存在构造方法,它需要显示地调用一个无参的父类的构造方法,并且重写父类的方法。所谓的匿名就是该类连名字都没有,只是显示地调用一个无参的父类的构造方法。
例5.12
import java.awt.* ;
import java.awt.event.*;
public class AnonymousClass{
private Frame f;
private TextField tf;
public AnonymousClass(){
f=new Frame("Inner classes example");
tf=new TextField(30);
}
public void launchFrame(){
Label label=new Label("Click and drag the mouse");
f.add(label,BorderLayout.NORTH);
f.add(tf,BorderLayout.SOUTH);
f.addMouseMotionListener(new MouseMotionAdapter(){ //匿名类开始
public void mouseDragged(MouseEvent e){
String s="Mouse dragging: x="+e.getX()+"Y="+e.getY();
tf.setText(s); }
} ); //匿名类结束
f.setSize(300,200);
f.setVisible(true);
}
public static void main(String args[]) {
AnonymousClass obj=new AnonymousClass();
obj.launchFrame();
}
}
其实大家仔细分析一下,例5.11和5.12实现的都是完全一样的功能,只不过采取的方式不同。5.11中的事件处理类是一个内部类,而5.12的事件处理类是匿名类,可以说从类的关系来说是越来越不清楚,但是程序也越来越简练。熟悉这两种方式也十分有助于大家编写图形界面的程序。
3 AWT组件库(1)
本节从应用的角度进一步介绍AWT的一些组件,目的使大家加深对AWT的理解,掌握如何用各种组件构造图形化用户界面,学会控制组件的颜色和字体。下面是一些常用的组件的介绍:
1. 按钮(Button)
按钮是最常用的一个组件,其构造方法是:Button b = new Button("Quit");
当按钮被点击后,会产生ActionEvent事件,需ActionListener接口进行监听和处理事件。
ActionEvent的对象调用getActionCommand()方法可以得到按钮的标识名,缺省按钮名为label。
用setActionCommand()可以为按钮设置组件标识符。
5.3 AWT组件库(3)
7. 文本输入区(TextArea)
TextArea可以显示多行多列的文本。使用setEditable(boolean)方法,可以将其设置为只读的。在TextArea中可以显示水平或垂直的滚动条。
要判断文本是否输入完毕,可以在TextArea旁边设置一个按钮,通过按钮点击产生的ActionEvent对输入的文本进行处理。
8. 列表(List)
列表中提供了多个文本选项,列表支持滚动条,可以浏览多项
List lst=new List(4,false); //两个参数分别表示显示的行数、是否允许多选
lst.add("Venus");
lst.add("Earth");
lst.add("JavaSoft");
lst.add("Mars");
cnt.add(lst);
9. 框架(Frame)
Frame是顶级窗口,可以显示标题,重置大小。当Frame被关闭,将产生WindowEvent事件,Frame无法直接监听键盘输入事件。
10. 对话框(Dialog)
它是Window类的子类。对话框和一般窗口的区别在于它依赖于其它窗口。对话框分为非模式(non-modal)和模式(modal)两种。
11. 文件对话框(Filedialog)
当用户想打开或存储文件时,使用文件对话框进行操作。主要代码如下:
FileDialog d=new FileDialog(ParentFr,"FileDialog");
d.setVisible(true);
String filename=d.getFile();
12. 菜单(Menu)
无法直接将菜单添加到容器的某一位置,也无法使用布局管理器对其加以控制。菜单只能被添加?quot;菜单容器"(MenuBar)中。
13. MenuBar
只能被添加到Frame对象中,作为整个菜单树的根基。
Frame fr = new Frame("MenuBar");
MenuBar mb = new MenuBar();
fr.setMenuBar(mb);
fr.setSize(150,100);
fr.setVisible(true);
14. Menu
下拉菜单。它可以被添加到MenuBar中或其它Menu中。
Frame fr = new Frame("MenuBar");
MenuBar mb = new MenuBar();
fr.setMenuBar(mb);
Menu m1 = new Menu("File");
Menu m2 = new Menu("Edit");
Menu m3 = new Menu("Help");
mb.add(m1);
mb.add(m2);
mb.setHelpMenu(m3);
fr.setSize(200,200);
fr.setVisible(true);
查看运行结果
15. MenuItem
MenuItem是菜单树中的"叶子节点"。MenuItem通常被添加到一个Menu中。对于MenuItem对象可以添加ActionListener,使其能够完成相应的操作。
Menu m1 = new Menu("File");
MenuItem mi1 = new MenuItem("Save");
MenuItem mi2 = new MenuItem("Load");
MenuItem mi3 = new MenuItem("Quit");
m1.add(mi1);
m1.add(mi2);
m1.addSeparator();
m1.add(mi3);
查看运行结果
MenuBar和Menu都没有必要注册监听器,只需要对MenuItem添加监听器ActionListener,完成相应操作。
16. 组件与监听器的对应关系
下表中列出了各个组件与所有的监听器的对应关系,打上""表明该组件可以注册此种监听器。
表5.2
监听器接口 | Act | Adj | Cmp | Cnt | Foc | Itm | Key | Mou | MM | Text | Win |
Button | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | |||||
Canvas | ![]() | ![]() | ![]() | ![]() | ![]() | ||||||
Checkbox | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | |||||
CheckboxMenuItem | ![]() | ||||||||||
Choice | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | |||||
Component | ![]() | ![]() | ![]() | ![]() | ![]() | ||||||
Container | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | |||||
Dialog | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ||||
Frame | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ||||
Label | ![]() | ![]() | ![]() | ![]() | ![]() | ||||||
List | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ||||
MenuItem | ![]() | ||||||||||
Panel | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | |||||
Scrollbar | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | |||||
ScrollPane | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | |||||
TextArea | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | |||||
TextField | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ||||
Window | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() |
Act=ActionListener Adj=AdjustmentListener Cmp=ComponentListener
Cnt=ConatainerListener Foc=FocusListener Itm=ItemListener
Key=KeyListener Mou=MouseListener MM=MouseMotionListener
Text=TextListener Win=WindowListener
【本讲小结】
用AWT来生成图形化用户界面时,组件和容器的概念非常重要。组件是各种各样的类,封装了图形系统的许多最小单位,例如按钮、窗口等等;而容器也是组件,它的最主要的作用是装载其它组件,但是象Panel这样的容器也经常被当作组件添加到其它容器中,以便完成杂的界面设计。布局管理器是java语言与其它编程语言在图形系统方面较为显著的区别,容器中各个组件的位置是由布局管理器来决定的,共有5种布局管理器,每种布局管理器都有自己的放置规律。事件处理机制能够让图形界面响应用户的操作,主要涉及到事件源、事件、事件处理者等三方,事件源就是图形界面上的组件,事件就是对用户操作的描述,而事件处理者是处理事件的类。因此,对于AWT中所提供的各个组件,我们都需要了解该组件经常发生的事件以及处理该事件的相应的监听器接口。
2.复选框 (Checkbox)
复选框提供简单的"on/off"开关,旁边显示文本标签。
构造方法如下:
setLayout(new GridLayout(3,1));
add(new Checkbox("one",null,true));
add(new Checkbox("two"));
add(new Checkbox("three"));
复选框用ItemListener 来监听ItemEvent事件,当复选框状态改变时用getStateChange()获取当前状态。使用getItem()获得被修改复选框的字符串对象。
例5.13
class Handler implements ItemListener {
public void itemStateChanged(ItemEvent ev){
String state = "deselected";
if (ev.getStateChange() = = ItemEvent.SELECTED){
state = "selected"
}
System.out.println(ev.getItem()+" "+state);
}
}
3.复选框组(CheckboxGroup)
使用复选框组,可以实现单选框的功能。方法如下:
setLayout(new GridLayout(3, 1));
CheckboxGroup cbg = new CheckboxGroup();
add(new Checkbox("one", cbg, true));
add(new Checkbox("two", cbg, false));
add(new Checkbox("three", cbg, false));
查看运行结果
3 AWT组件库(2)
4. 下拉式菜单(Choice)
下拉式菜单每次只能选择其中的一项,它能够节省显示空间,适用于大量选项。
Choice Colorchooser=new Choice();
Colorchooser.add("Green");
Colorchooser.add("Red");
Colorchooser.add("Blue");
Choice 用ItemListener接口来进行监听
5. Canvas
一个应用程序必须继承Canvas类才能获得有用的功能,比如创建一个自定义组件。如果想在画布上完成一些图形处理,则Canvas类中的paint()方法必须被重写。
Canvas组件监听各种鼠标,键盘事件。当在Canvas组件中输入字符时,必须先调用requestFocus()方法。
例5.14
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class MyCanvas implements KeyListener, MouseListener {
Canvas c; //声明一个画布对象
String s ="";
public static void main(String args[]) {
Frame f=new Frame("Canvas");
MyCanvas mc=new MyCanvas();
mc.c=new Canvas();
f.add("Center",mc.c);
f.setSize(150,150);
mc.c.addMouseListener(mc); //注册监听器
mc.c.addKeyListener(mc); //注册监听器
f.setVisible(true);
}
public void mouseClicked(MouseEvent ev){
System.out.println("MouseClicked");
c.requestFocus();//获得焦点,表示该窗口将接收用户的键盘和鼠标输入
}
public void keyTyped(KeyEvent ev) {
System.out.println("KeyTyped");
s+=ev.getKeyChar(); //获取每个输入的字符,依次添加到字符串s中
c.getGraphics().drawString(s,0,20); //显示字符串s
}
public void keyPressed(KeyEvent ev) { System.out.println("KeyPressed"); }
public void keyReleased(KeyEvent ev) { System.out.println("KeyReleased"); }
public void mousePressed(MouseEvent ev) {System.out.println("MousePressed"); }
public void mouseReleased(MouseEvent ev) {System.out.println("MouseReleased"); }
public void mouseEntered(MouseEvent ev) {System.out.println("MouseEntered"); }
public void mouseExited(MouseEvent ev) {System.out.println("MouseExited"); }
}
6. 单行文本输入区(TextField)
只能显示一行,当回车键被按下时,会发生ActionEvent事件,可以通过ActionListener中的actionPerformed()方法对事件进行相应处理。可以使用setEditable(boolean)方法设置为只读属性。
单行文本输入区构造方法如下:
TextField tf1,tf2,tf3,tf4:
tf1=new TextField();
tf2=new TextField("",20); //显示区域为20列
tf3=new TextField("Hello!"); //按文本区域大小显示
tf4=new TextField("Hello!",30); //初始文本为Hello!, 显示区域为30列