5.1 用AWT生成图形化用户界面 抽象窗口工具包AWT (Abstract Window Toolkit) 是 API为Java 程序提供的建立图形用户界面GUI (Graphics User Interface)工具集,AWT可用于Java的applet和applications中。它支持图形用户界面编程的功能包括: 用户界面组件;事件处理模型;图形和图像工具,包括形状、颜色和字体类;布局管理器,可以进行灵活的窗口布局而与特定窗口的尺寸和屏幕分辨率无关;数据传送类,可以通过本地平台的剪贴板来进行剪切和粘贴。5.1.1 java.awt包 java.awt包中提供了GUI设计所使用的类和接口,可从中看到主要类之间的关系。 java.awt包提供了基本的java程序的GUI设计工具。主要包括下述三个概念: 组件--Component 容器--Container 布局管理器--LayoutManager
5.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);
5.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风格的窗口。
5.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); } }
5.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()等方法手工设置组件的大小和位置,此方法会导致平台相关,不鼓励使用。5.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.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 ) 方法来注册监听器。