1、第7章 AWT图形用户界面,AWT与Swing,Java1.0的出现带来了抽象窗口工具箱(AWT)。设计目标是希望构建一个通用的GUI,使得利用它编写的程序能够运行在所有的平台上,以实现Sun公司提出的口号“一次编写,随处运行”。 在Java1.2中,Sun公司推出了新的用户界面库:Swing。相对AWT来说,Swing功能更强大、使用更方便,它的出现使得Java的图形用户界面上了一个台阶。 但是,Swing并没有代替AWT。在Java1.1中,AWT事件处理模型有了根本改变。Swing使用的仍然是Java1.1的事件处理模型。,AWT在实际的运行过程中是调用所在平台的图形系统,因此同样一段A
2、WT程序在不同的操作系统平台下运行所看到的图形系统是不一样的。例如在windows下运行,则显示的窗口是windows风格的窗口;而在UNIX下运行时,则显示的是UNIX风格的窗口。 Swing是由100%纯Java实现的,Swing组件是用Java实现的轻量级( light-weight)组件,没有本地代码,不依赖操作系统的支持。,AWT,抽象窗口工具包AWT (Abstract Window Toolkit) 是 API为Java 程序提供的建立图形用户界面GUI 工具集 AWT可用于Java的applet和applications中。 它支持图形用户界面编程的功能包括: 用户界面组件;
3、事件处理模型; 图形和图像工具,包括形状、颜色和字体类; 布局管理器,可以进行灵活的窗口布局而与特定窗口的尺寸和屏幕分辨率无关; 数据传送类,可以通过本地平台的剪贴板来进行剪切和粘贴。,java.awt包中提供了GUI设计所使用的类和接口。 java.awt包提供了基本的java程序的GUI设计工具。主要包括下述三个概念: 组件-Component 容器-Container 布局管理器-LayoutManager,组件、容器、布局管理器,Java的图形用户界面的最基本组成部分是组件 组件是一个可以以图形化的方式显示在屏幕上并能与用户进行交互的对象,例如一个按钮,一个标签等。 基本组件不能独立地
4、显示出来,必须将组件放在一定的容器中才可以显示出来。 容器本身也是一个组件,具有组件的所有性质,但是它的主要功能是容纳其它组件和容器。 布局管理器(LayoutManager):每个容器都有一个布局管理器,当容器需要对某个组件进行定位或判断其大小尺寸时,就会调用其对应的布局管理器。,7.1 AWT容器,Container:用来装其他组件 Window:不依赖其他容器,独立存在(一个简单的窗口框) Frame(窗体):有标题,可调整大小等 Dialog(对话框):可以移动,但不能调整大小 Panel:不能独立存在,需嵌入其他容器 Applet 容器常用方法 add remove setLayou
5、t setFont,Frame类,一般我们要生成一个窗口,不直接用到Window类。 用Window的子类Frame来进行实例化。 也可以创建自己的Frame类(要从Frame继承),进行实例化。 Frame的外观就像我们平常在windows系统下见到的窗口,有标题、边框、菜单、大小等等。 构造方法 public Frame(); public Frame(String title); 每个Frame的对象实例化以后,都是没有大小和不可见的,因此必须调用setSize( )来设置大小,调用setVisible(true)来设置该窗口为可见的。,Frame中比较重要的成员方法 setBounds
6、(int x, int y, int width, int height) 设置窗体位置和大小,x、y是左上角坐标,width、height 为宽度和高度。 setSize(int width, int height) 设置窗体大小。 setLocation(int x, int y) 设置窗体位置。 setVisable(boolean b) 设置窗体是否可见。 setBackGround(Color c) 设置窗体背景颜色。,import java.awt.*; public class Test public static void main(String args ) Frame fr
7、 = new Frame(“Hello Out There!“); fr.setSize(200,200); fr.setBackground(Color.red); fr.setVisible(true); ,【例7-1】Frame中若干重要方法示例 import java.awt.*; public class Test7_1 public static void main(String args) MultiFrame f1 = new MultiFrame(50,50,100,100,Color.BLUE);MultiFrame f2 = new MultiFrame(150,50,1
8、00,100,Color.YELLOW);MultiFrame f3 = new MultiFrame(50,150,100,100,Color.GREEN);MultiFrame f4 = new MultiFrame(150,150,100,100,Color.MAGENTA); class MultiFrame extends Framestatic int id = 0;MultiFrame(int x,int y,int w,int h,Color color)super(“Frame “ + (+id);/设置Frame的标题setBackground(color); /设置背景色
9、 setLayout(null);/设置布局管理器为nullsetBounds(x,y,w,h);/设置Frame窗体位置和大小 setVisible(true);/设置Frame可见 ,Panel,Panel本身不能独立存在 Panel对象创建后必须使用其容器的add方法,将其加入某个容器对象(Frame对象) Panel p = new Panel()创建Panel类对象,Applet是Panel类的子类,因此不能独立存在,Applet在运行时要么通过appletviewer查看器运行,要么需要嵌套在html文档中在Web浏览器中运行。,【例7-2】Panel类使用示例 import ja
10、va.awt.*; public class Test7_2 public static void main(String args) Frame f = new Frame(“包含Panel的Frame“);/创建Panel对象,参数null为布局管理器设置Panel p = new Panel(null);f.setLayout(null);/设置Frame的布局管理器为nullf.setBounds(200,200,300,300);f.setBackground(Color.BLUE);/设置Frame背景色p.setBounds(50,50,200,200);p.setBackgro
11、und(Color.CYAN);/设置Panel背景色f.add(p);/将Panel类对象添加到Frame中f.setVisible(true); ,7.2 布局管理器,java为了实现跨平台的特性并且获得动态的布局效果,java将容器内的所有组件安排给一个“布局管理器”负责管理,如:排列顺序,组件的大小、位置,当窗口移动或调整大小后组件如何变化等功能授权给对应的容器布局管理器来管理,不同的布局管理器使用不同算法和策略,容器可以通过选择不同的布局管理器来决定布局。FlowLayout(流式布局) BorderLayout(边界布局) GridLayout(网格布局) CardLayout(卡
12、片布局) GridBagLayout,7.2.1 FlowLayout 流布局管理器,FlowLayout是最简单的布局管理器,该类是Object类的直接子类。 FlowLayout的布局策略是按照组件的添加次序依次将它们从左到右放置到容器中。当一行排满之后就转到下一行继续从左到右排列,且每一行中的组件都居中排列。 FlowLayout是Applet缺省使用的布局策略。 创建布局管理器对象:将容器设置为布局管理器 setLayout ( LayoutManager mgr ) 将组件加入容器 add(Component comp),【例7-3】使用FlowLayout布局管理器示例 impor
13、t java.awt.*; class FlowLayoutTest extends Framepublic FlowLayoutTest()this.setBounds(50,50,200,200); /设置布局管理器为FlowLayout类型this.setLayout( new FlowLayout() ); for(int i=0;i5;i+) this.add ( new Button(“Button“+(i+1) ); this.setVisible(true); public class Test7_3 public static void main(String args) n
14、ew FlowLayoutTest(); ,【例7-4】使用pack方法让系统管理Frame的大小 import java.awt.*; class FlowLayoutTest extends Framepublic FlowLayoutTest()/设置布局管理器为FlowLayout类型this.setLayout(new FlowLayout(); for(int i=0;i5;i+) this.add(new Button(“Button“+(i+1);pack();this.setVisible(true);/设置可见 public class Test7_3 public sta
15、tic void main(String args) new FlowLayoutTest(); ,7.2.2 BorderLayout 边框布局管理器,布局策略:提供了5个区域,而每个区域只允许添加一个组件。 在加入组件的时候,需要指明其加入的位置 BorderLayout.NORTH BorderLayout.SOUTH BorderLayout.WEST BorderLayout.EAST BorderLayout.CENTER也可以使用字符串“North”、“South”、“West”、“East”和“Center”来标明。 BorderLayout是Frame和Dialog的默认布局
16、管理器。,创建布局管理器对象:将容器设置为布局管理器 将组件加入容器 在向设置为BorderLayout的布局管理策略的容器中添加组件时,必须指明添加位置,如果未指明,则默认添加到Center区域,新添加的组件会覆盖原来的组件。add(Component comp,int index),【例7-4】使用BorderLayout布局管理器示例 import java.awt.*; class BorderLayoutTest extends Frame public BorderLayoutTest( ) super(“BorderLayoutTest“); this.setBounds(50,
17、50,300,200); /创建布局对象,并将容器进行布局设置this.setLayout( new BorderLayout() ); /将组件加入容器this.add(new Button(“Button1“),BorderLayout.NORTH);this.add(new Button(“Button2“),BorderLayout.SOUTH);this.add(new Button(“Button3“),BorderLayout.WEST);this.add(new Button(“Button4“),BorderLayout.EAST);this.add(new Button(“
18、Button5“),BorderLayout.CENTER);this.setVisible(true); public class Test7_4 public static void main(String args) new BorderLayoutTest(); ,BorderLayout的布局策略中只提供了5个区域,而每个区域只允许添加一个组件,因此若想添加多于5个组件到使用了该布局策略的容器中,则必须使用容器的嵌套或改用其他的布局策略。 不一定所有的区域都有组件,如果四周的区域(West、East、North、South区域)没有组件,则由Center区域去补充,但是如果Cente
19、r区域没有组件,则保持空白。,7.2.3 CardLayout 卡片布局管理器,CardLayout布局管理器布局策略:它把容器分成许多层,每层的显示空间占据整个容器的大小,但是每层只允许放置一个组件。 布局对象创建:设置容器布局管理器,将组件加入容器add(Component comp,Object constraints)其中comp表示要要添加的组件,constraints表示组件在CardLayout类对象中的名字,也就是卡片的名字(字符串)。 如果未使用constraints参数,那么会产生异常IllegalArgumentException。 CardLayout方法 show(C
20、ontainer parent,String name)指定显示哪张卡片。参数parent表示父容器,参数name表示指定卡片的名字。 按顺序显示卡片,【例7-5】使用CardLayoutLayout布局管理器示例 import java.awt.*; class CardLayoutTest extends Framepublic CardLayoutTest() super(“CardLayoutTest“);this.setBounds(50,50,200,150);/设置显示位置及大小CardLayout card = new CardLayout(); this.setLayout(
21、card); for(int i=0;i5;i+)this.add( new Button(“Button“+(i+1), “Button“+(i+1) ); /card.show(this,”Button2”); card.next(this); this.setVisible(true); public class Test7_5 public static void main(String args) CardLayoutTest c=new CardLayoutTest(); ,7.2.4 GridLayout 网格布局管理器,布局策略:将容器划分成若干行列的网格。在容器上添加组件时,
22、组件会按照从左到右、从上到下的顺序在网格中排列。 GridLayout布局管理器总是忽略组件的最佳大小,所有单元的宽度是相同的,是根据单元数对可用宽度进行平分而定的。同样的,所有单元的高度是相同的,是根据行数对可用高度进行平分而定的。 创建布局管理器对象:,【例7-6】使用CardLayoutLayout布局管理器示例。 import java.awt.*; class GridLayoutTest extends Framepublic GridLayoutTest()super(“GridLayoutTest“);this.setBounds(50,50,200,150); GridLay
23、out card = new GridLayout(2,3,5,5); this.setLayout(card); for(int i=0;i5;i+)this.add( new Button(“Button“+(i+1) ); this.setVisible(true); public class Test7_6 public static void main(String args) new GridLayoutTest(); ,7.2.4 GridBagLayout网格包布局管理器,GridBagLayout在网格的基础上提供更为复杂的布局。 和GridLayout不同,GridBagL
24、ayout允许容器中各个组件的大小各不相同,还允许单个组件所在的显示区域占多个网格。 GridBagLayout提供了强大的控制功能,不过它也是最复杂的布局管理器,一般很难理解。 它的目的主要是辅助GUI构造工具(它可能使用GridBagLayout而不是绝对位置来控制布局)自动生成代码。如果你发现自己的设计非常复杂,以至于需要使用GridBagLayout,那么你应该使用专门的集成开发工具来完成这个设计。,7.2.5 取消布局管理器,确信自己的程序不需要跨平台,完全可以使用绝对坐标的方式来指定组件的位置和大小。 调用容器的setLayout(null)方法取消该容器的默认布局管理器 然后在添
25、加组件时调用每个组件的setLayout()、setSize()或者setBounds()方法来控制各个组件的显示位置,本章7.1节中的几个例子就是这种情况。 但若使用布局管理器时,布局管理器负责各个组件的大小和位置,因此用户无法在这种情况下设置组件的大小和位置属性,如果试图使用setLocation、setSize等方法,则都会被布局管理器覆盖。,思考:,计算器的布局,7.3 事件处理机制,前面的主要内容是如何放置各种组件,使图形界面更加丰富多彩,但是还不能响应用户的任何操作,要能够让图形界面接收用户的操作,就必须给各个组件加上事件处理机制。 事件源(产生事件的对象(组件,如按钮) 事件(事
26、件对象:如按下按钮事件,事件相关信息 ) 事件处理器(处理事件的对象),如果用户用鼠标单击了按钮对象button,则该按钮button就是事件源, 而java运行时系统会生成ActionEvent事件类的对象,该对象中描述了该单击事件发生时的一些信息, 然后,事件处理者对象将接收由java运行时系统传递过来的事件对象并进行相应的处理。,7.3.1事件委托处理模式,从JDK1.1开始,事件处理采用委托模式。 在该模式下,每一个可以触发事件的组件都被当做事件源(如按钮、文本框、下拉框等) 每一种事件都对应专门的监听器,监听器负责接收和处理这种事件。 一个事件源可能触发多种事件(如按钮可以发生单击事
27、件,双击事件等),如果它注册了某种事件的监听器,那么这种事件就会被处理。 在这种模式下,事件源本身并不处理事件,而是将处理事件的任务委托给相应的事件监听器来处理,这也是委托模式的由来。,在Java中事件以及事件监听器接口的命名非常规范 所有事件类的名字都是XXXEvent, 所有事件监听器接口的名字都是XXXListener; 同时,事件监听器接口的名字和事件的名字也是对应的,如ActionEvent和ActionListener,MouseEvent和MouseListener等。,7.3.2实现监听器接口的方式,自身类作为事件监听器 外部类作为事件监听器 匿名内部类作为事件监听器 命名内部
28、类作为事件监听器,以按钮点击事件处理为例: 事件监听器(类对象):用于处理特定种类的事件,如ActionEvent必须实现相应的接口,如ActionListener完成接口中事件处理方法,如ActionPerformed(ActionEvent e) 事件监听器注册:事件监听器对象必须在其处理的事件源上注册,方法如事件源.addActionListener(事件监听器对象),import java.awt.*; import java.awt.event.*; class ButtonClickTest extends Frame private Button button = new But
29、ton(“确定”); private TextArea textArea= new TextArea(); private static int n=1; public ButtonClickTest() add(button,BorderLayout.NORTH); add(textArea,BorderLayout.CENTER); setBounds(50,50,300,200); setVisible(true); public class Test7_7 public static void main(String args) new ButtonClickTest(); ,界面,i
30、mport java.awt.*; import java.awt.event.*; class ButtonClickTest extends Frame implements ActionListenerprivate Button button = new Button(“确定”); private TextArea textArea= new TextArea(); private static int n=1; public ButtonClickTest() button.addActionListener ( this ); add(button,BorderLayout.NOR
31、TH); add(textArea,BorderLayout.CENTER); setBounds(50,50,300,200); setVisible(true); public void actionPerformed(ActionEvent e ) String temp= textArea.getText (); textArea.setText(“按钮第”+n+“次被按下n”+temp); n+; public class Test7_7 public static void main(String args) new ButtonClickTest(); ,自身类作为事件监听器,i
32、mport java.awt.*; import java.awt.event.*; class ButtonClickTest extends Frame private Button button = new Button(“确定“); private TextArea textArea= new TextArea(); private static int n=1; public ButtonClickTest() button.addActionListener( new MyActionListener( textArea ) );add(button,BorderLayout.NO
33、RTH); add(textArea,BorderLayout.CENTER); setBounds(50,50,300,200); setVisible(true); ,外部类作为事件监听器,class MyActionListener implements ActionListenerprivate TextArea textArea;private static int n=1;public MyActionListener( TextArea textArea )this.textArea=textArea;public void actionPerformed(ActionEvent
34、 e) String temp= this.textArea.getText(); this.textArea.setText(“按钮第“+n+“次被按下n“+temp); n+; public class Test7_8 public static void main(String args) new ButtonClickTest(); ,外部类作为事件监听器的实现方式 优点:可以使处理事件的代码与创建GUI界面的代码分离,便于维护 缺点:由于事件处理和事件源不在同一类中,用于事件处理的监听器类无法直接访问产生事件的组件。 对于无法直接访问事件源的情况,可以利用如下两种方式来解决: 将处理
35、过程中需要使用的组件通过参数传递到监听器中 监听器类中设置数据成员(组件引用) 类的构造方法中初始化数据成员(组件引用),class MyActionListener implements ActionListenerprivate TextArea textArea;private static int n=1;public MyActionListener( TextArea textArea )this.textArea=textArea;,可以通过事件处理方法的形参,获得事件源的信息。,在事件处理器中有这样一个方法 public void actionPerformed (Action
36、Event e) 这里的参数e就是前面提到过的封装了事件基本信息的事件对象。 可以通过e.getSource()的方式获得事件源,对于本例来说,就是触发单击事件的button按钮。,匿名内部类作为事件监听器,import java.awt.*; import java.awt.event.*; class ButtonClickTest extends Frame private Button button = new Button(“确定”); private TextArea textArea= new TextArea(); private static int n=1; public
37、ButtonClickTest() button.addActionListener ( new ActionListener() public void actionPerformed(ActionEvent e) String temp=textArea.getText(); textArea.setText(“按钮第“+n+“次被按下n“+temp);n+; ); add(button,BorderLayout.NORTH); add(textArea,BorderLayout.CENTER); setBounds(50,50,300,200); setVisible(true); ,p
38、ublic class Test7_9 public static void main(String args) new ButtonClickTest(); ,匿名内部类方式好处是书写方便,内部类可以直接访问外部类(包含匿名内部类的类)的数据成员和成员方法,但也要注意它的不足: (1)由于事件处理代码会随着组件一起分散到代码中的各个部分,不够集中,这样会导致代码阅读与维护上的不便。 (2)各事件的处理全部由嵌套的程序块组成,视觉上显得有些散乱。如果事件处理代码很长,也会导致阅读与维护上的不便。 (3)对于工具栏、菜单栏等可以复用事件处理的界面组件,使用匿名内部类的写法将无法复用事件处理代码。
39、,import java.awt.*; import java.awt.event.*; class ButtonClickTest extends Frame private Button button = new Button(“确定“); private TextArea textArea= new TextArea(); private static int n=1; public ButtonClickTest() button.addActionListener( new MyActionListener() ); add(button,BorderLayout.NORTH); a
40、dd(textArea,BorderLayout.CENTER); setBounds(50,50,300,200); setVisible(true);private class MyActionListener implements ActionListenerpublic void actionPerformed(ActionEvent e) String temp=textArea.getText(); textArea.setText(“按钮第“+n+“次被按下n“+temp); n+; ,public class Test7_10 public static void main(S
41、tring args) new ButtonClickTest(); ,采用命名内部类的方式,可以解决匿名内部类存在的问题。 首先,事件处理代码都集中在一起,并且事件处理类具有了有意义的名字,程序代码更容易阅读与维护; 另外,单个事件处理程序也可以被工具栏、菜单栏等重用。,关于事件处理器实现监听接口的四种实现方式,做一总结: 匿名内部类方式写起来非常方便,但不适合事件代码较长的情形(一般多于10行就算代码较长了); 命名内部类方式从代码书写、阅读、维护以及程序的可扩展性角度来看都值得推荐; 外部类方式主要是为了代码重用才考虑使用,如果多个事件源用到相同的事件处理代码,外部类方式就派上用场了;
42、对于自身类作为监听器方式,虽然从实现上看简洁紧凑,易于理解,但本身存在许多不合理的地方,因此,一般不建议使用。,7.3.3事件适配器,有的监听器接口有多个抽象方法,实现接口的类就必须实现其所有的抽象方法。 Java语言为一些Listener接口提供了适配器(Adapter)类。可以通过继承实现接口的Adapter类,重写需要方法,无关方法不用实现。 事件适配器为我们提供了一种简单的实现监听器的手段, 可以缩短程序代码。 但是,由于java的单一继承机制,当需要多种监听器或此类已有父类时,就无法采用事件适配器了。,如: ActionListener接口中只有一个ActionPerformed抽象方法 WindowListener接口中的抽象方法:,