第八章 建立GUIs   本模块讲述图形用户界面的建立及布局。它介绍了抽象视窗工具包,一种建立GUIs的类包。 第一节 相关问题   讨论—下述问题与本模块中出现的材料相关。 Java编程语言是一个具有独立平台的编程语言。GUI环境通常是从属平台。那么,为了使GUI平台独立,Java技术是如何接近这个主题的呢? 第二节 目 的 完成本模块学习时,将能: 描述AWT包及其组件 定义Container、Component及Layout Manager等术语,以及它们是如何在一起来建立GUI的 使用Layout Manager 使用Flow、Border、Gird及Card布局管理器来获得期望的动态布局 增加组件到Container 正确运用Frame及Panel容器 描述如何使用嵌套容器来完成复杂的布局 在Java软件程序中,确认如下内容: 容器 相关布局管理器 所有组件的布局层次 第三节 AWT AWT 提供基本的GUI组件,用在所有的Java applets及应用程序中 具有可以扩展的超类,它们的属性是继承的,类也可被抽象化 确保显示在屏幕上的每个GUI组件都是抽象类组件的子类 Contaner,它是一个Component的抽象子类,而且包括两个子类 Panel window   AWT提供用于所有Java applets及应用程序中的基本GUI组件,还为应用程序提供与机器的界面。这将保证一台计算机上出现的东西与另一台上的相一致。 在学AWT之前,简单回顾一下对象层次。记住,超类是可以扩展的,它们的属性是可继承的。而且,类可以被抽象化,这就是说,它们是可被分成子类的模板,子类用于类的具体实现。 显示在屏幕上的每个GUI组件都是抽象类组件的子类。也就是说,每个从组件类扩展来的图形对象都与允许它们运行的大量方法和实例变量共享。 Container是Component的一个抽象子类,它允许其它的组件被嵌套在里面。这些组件也可以是允许其它组件被嵌套在里面的容器,于是就创建了一个完整的层次结构。在屏幕上布置GUI组件,容器是很有用的。Panel是Container的最简单的类。Container的另一个子类是Window。 第四节 Java.awt包 Java.awt包包含生成WIDGETS和GUI组件的类。该包的基本情况如下图所示。黑体字的类表明该模块的要点。 第五节 建立图形用户界面 8.5.1 Container Container Container的两个主要类型是Window和Panel Window是Java.awt.Window.的对象 Panel是Java.awt.Panel的对象   Container有两个主要类型:Window和Panel Window是Java.awt.Window.的对象。Window是显示屏上独立的本机窗口,它独立于其它容器。 Window有两种形式:Frame(框架)和Dialog(对话框)。Frame和Dialog是Window的子类。Frame是一个带有标题和缩放角的窗口。对话没有菜单条。尽管它能移动,但它不能缩放。 Panel是Java.awt.Panel的对象。Panel包含在另一个容器中,或是在Web浏览器的窗口中。Panel确定一个四边形,其它组件可以放入其中。Panel必须放在Window之中(或Window的子类中)以便能显示出来。 注—容器不但能容纳组件,还能容纳其它容器,这一事实对于建立复杂的布局是关键的,也是基本的。   滚动块也是Window的一个子类。它在模块10“AWT组件集”里讨论。 8.5.2 定位组件 定位组件 容器里的组件的位置和大小是由布局管理器决定的。 可以通过停用布局管理器来控制组件的大小或位置。 然后必须用组件上的setLocation(),setSize(),或setBounds()来定位它们在容器里的位置。   容器里的组件的位置和大小是由布局管理器决定的。容器对布局管理器的特定实例保持一个引用。当容器需要定位一个组件时,它将调用布局管理器来做。当决定一个组件的大小时,同样如此。布局管理器完全控制容器内的所有组件。它负责计算并定义上下文中对象在实际屏幕中所需的大小。 8.5.3 组件大小 因为布局管理器负责容器里的组件的位置和大小,因此不需要总是自己去设定组件的大小或位置。如果想这样做(使用setLocation(),setSize()或setBounds()方法中的任何一种),布局管理器将覆盖你的决定。 如果必须控制组件的大小或位置,而使用标准布局管理器做不到,那就可能通过将下述方法调用发送到容器中来中止布局管理器: setLayout(null); 做完这一步,必须对所有的组件使用setLocation(),setSize()或setBounds(),来将它们定位在容器中。请注意,由于窗口系统和字体大小之间的不同,这种办法将导致从属于平台的布局。更好的途径是创建布局管理器的新子类。 第六节 Frames Frames 是Window的子类 具有标题和缩放角 从组件继承并以add方式添加组件 能以字符串规定的标题来创建不可见框架对象 能将Border Layout当做缺省布局管理器 用setLayout方式来改变缺省布局管理器   Frames是Window的一个子类。它是带有标题和缩放角的窗口。它继承于Java.awt.Container,因此,可以用add()方式来给框架添加组件。框架的缺省布局管理器就是Border Layout。它可以用setLayout()方式来改变。 框架类中的构造程序 Frame(String)用由String规定的标题来创建一个新的不可见的框架对象。当它还处于不可见状态时,将所有组件添加到框架中。 import java.awt.*; public class MyFrame extends Frame { public static void main (String args[]) { MyFrame fr = new MyFrame("Hello Out There!"); fr.setSize(500,500); fr.setBackground(Color.blue); fr.setVisible(true); } public MyFrame (String str) { super(str); } } 上述程序创建了下述框架,它有一个具体的标题、大小及背景颜色。 注—在框架显示在屏幕上之前,必须做成可见的(通过调用程序setVisible(true)),而且其大小是确定的(通过调用程序setSize()或pack())。   第七节 Panels Panels 为组件提供空间 允许子面板拥有自己的布局管理器 以add方法添加组件   象Frames一样,Panels提供空间来连接任何GUI组件,包括其它面板。每个面板都可以有它自己的布管理程序。 一旦一个面板对象被创建,为了能看得见,它必须添加到窗口或框架对象上。用Container类中的add()方式可以做到这一点。 下面的程序创建了一个小的黄色面板,并将它加到一个框架对象上: import java.awt.*; public class FrameWithPanel extends Frame { // Constructor 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.blue); fr.setLayout(null); //override default layout mgr pan.setSize (100,100); pan.setBackground(Color.yellow); fr.add(pan); fr.setVisible(true); } .... 第八节 容器布局(Container Layout) 容器布局(Container Layout) 流程布局(Flow Layout) 边框布局(Border Layout) 网格布局(Grid Layout) 卡布局(Card Layout) 网格包布局(GridBag Layout)   容器中组件的布局通常由布局管理器控制。每个Container(比如一个Panel或一个Frame)都有一个与它相关的缺省布局管理器,它可以通过调用setLayout()来改变。 布局管理器负责决定布局方针以及其容器的每一个子组件的大小。 第九节 布局管理器 下面的布局管理器包含在Java编程语言中: Flow Layout—Panel和Applets的缺省布局管理器 Border Layout—Window、Dialog及Frame的缺省管理程序 Grid Layout Card Layout GridBag Layout GridBag布局管理器在本模块中不深入讨论。 Flow Layout的一个简单例子 这个样板代码阐述了几个要点,将在下一节讨论。 import java.awt.*; public class ExGui { private Frame f; private Button b1; private Button b2; public static void main(String args[]) { ExGui guiWindow = new ExGui(); guiWindow.go(); } public void go() { f = new Frame("GUI example"); f.setLayout(new FlowLayout()); b1 = new Button("Press Me"); b2 = new Button("Don't Press Me"); f.add(b1); f.add(b2); f.pack(); f.setVisible(true); } } main()方法 本例中第8行main()方法有两个作用。首先,它创建了ExGui对象的一个实例。回想一下,直到一个实例存在,还没有被称做f,b1和b2的真实数据项可以使用。第二,当数据空间被创建时,main()在该实例的上下文中调用实例方法go()。在go()中,真正的运行才开始。 new Frame (“GUI Example”) 这个方法创建Java.awt.Frame类的一个实例。根据本地协议,在Java编程语言中,Frame是顶级窗口,带有标题条—在这种情况下,标题条由构造程序参数“GUI Example”定义—缩放柄,以及其它修饰。 f.setLayout (new FlowLayout()) 这个方法创建Flow布局管理器的一个实例,并将它安装在框架中。对于每个Frame、Border布局来说,都有一个布局管理器,但本例中没有使用。Flow布局管理器在AWT中是最简单的,它在某种程度上象一个页面中的单词被安排成一行一行的那样来定位组件。请注意,Flow布局缺省地将每一行居中。 new Button(“Press Me”) 这个方法创建Java.awt.Button类的一个实例。按钮是从本地窗口工具包中取出的一个标准按钮。按钮标签是由构造程序的字符串参数定义的。 f.add(b1) 这个方法告诉框架f(它是一个容器),它将包容组件b1。b1的大小和位置受从这一点向前的Frame布局管理器的控制。 f.pack() 这个方法告诉框架来设定大小,能恰好密封它所包含的组件。为了确定框架要用多大,f.pack()询问布局管理器,在框架中哪个负责所有组件的大小和位置。 f.setVisible(true) 这个方法使框架以及其所有的内容变成用户看得见的东西。 第190页代码的最终结果是: 8.9.1 Flow布局管理器 第190页例子中所用的Flow布局对组件逐行地定位。每完成一行,一个新行便又开始。 与其它布局管理器不一样,Flow布局管理器不限制它所管理的组件的大小,而是允许它们有自己的最佳大小。 如果想将组件设定缺省居中的话,Flow布局构造程序参数允许将组件左对齐或右对齐。 如果想在组件之间创建一个更大的最小间隔,可以规定一个界限。 当用户对由Flow布局管理的区域进行缩放时,布局就发生变化。如: 下面的例子就是如何用类容器的setLayout()方法来创建Flow布局对象并安装它们。 setLayout(new FlowLayout(int align,int hgap, int vgap)); 对齐的值必须是FlowLayout.LEFT, FlowLayout.RIGHT,或 FlowLayout.CENTER。例如: setLayout(new FlowLayout(FlowLayout.RIGHT, 20, 40)); 下述程序构造并安装一个新Flow布局,它带有规定好的对齐方式以及一个缺省的5单位的水平和垂直间隙。对齐的值必须是FlowLayout.LEFT, FlowLayout.RIGHT,或 FlowLayout.CENTER。 setLayout(new FlowLayout(int align); setLayout(new FlowLayout(FlowLayout.LEFT)); 下述程序构造并安装一个新Flow布局,它带有规定好的居中对齐方式和一个缺省的5单位的水平和垂直间隙。 setLayout(new FlowLayout()); 这个模块代码将几个按钮添加到框架中的一个Flow布局中: import java.awt.*; public class MyFlow { private Frame f; private Button button1, button2, button3; public static void main (String args[]) { MyFlow mflow = new MyFlow (); mflow.go(); } public void go() { f = new Frame ("Flow Layout"); f.setLayout(new FlowLayout()); button1 = new Button("Ok"); button2 = new Button("Open"); button3 = new Button("Close"); f.add(button1); f.add(button2); f.add(button3); f.setSize (100,100); f.setVisible(true); } } 8.9.2 Border布局管理器 Border布局管理器为在一个Panel或Window中放置组件提供一个更复杂的方案。Border布局管理器包括五个明显的区域:东、南、西、北、中。 北占据面板的上方,东占据面板的右侧,等等。中间区域是在东、南、西、北都填满后剩下的区域。当窗口垂直延伸时,东、西、中区域也延伸;而当窗口水平延伸时,东、西、中区域也延伸。 Border布局管理器是用于Dialog和Frame的缺省布局管理器。下例的代码是在第193页上: 注—当窗口缩放时,按钮相应的位置不变化,但其大小改变。 下面的代码对前例进行了修改,表示出了Border布局管理器的特性。可以用从Container类继承的setLayout()方法来将布局设定为Border布局。 import java.awt.*; public class ExGui2 { private Frame f; private Button bn, bs, bw, be, bc; public static void main(String args[]) { ExGui2 guiWindow2 = new ExGui2(); guiWindow2.go(); } public void go() { f = new Frame("Border Layout"); bn = new Button("B1"); bs = new Button("B2"); be = new Button("B3"); bw = new Button("B4"); bc = new Button("B5"); f.add(bn, BorderLayout.NORTH); f.add(bs, BorderLayout.SOUTH); f.add(be, BorderLayout.EAST); f.add(bw, BorderLayout.WEST); f.add(bc, BorderLayout.CENTER); f.setSize (200, 200); f.setVisible(true); } } 下面这一行: setLayout(new BorderLayout()); 构造并安装一个新Border布局,在组件之间没有间隙。 这一行 setLayout(new BorderLayout(int hgap, int vgap); 构造并安装一个Border布局,在由hgap和 vgap 规定的组件之间有规定的间隙。 在布局管理器中组件必须被添加到指定的区域,而且还看不见。区域名称拼写要正确,尤其是在选择不使用常量(如add(button,”Center”))而使用add(button,BorderLayout.CENTER)时。拼写与大写很关键。 可以使用Border布局管理器来产生布局,且带有在缩放时在一个方向、另一方向或双方向上都延伸的元素。 注—如果窗口水平缩放,南、北、中区域变化;如果窗口垂直缩放,东、西、中区域变化;   如果离开一个Border布局未使用的区域,,好象它的大小为0。中央区域即使在不含组件的情况下仍然呈现为背景。 可以仅将单个组件添加到Border布局管理器五个区域的每一个当中。如果添加不止一个,只有最后一个看得见。后面的模块将演示如何用中间容器来允许不止一个组件被放在单个Border布局管理器区域的空间里。 注—布局管理器给予南、北组件最佳高度,并强迫它们与容器一样宽。但对于东、西组件,给予最佳宽度,而高度受到限制。   8.9.3 Grid布局管理器 Grid布局管理器为放置组件提供了灵活性。用许多行和栏来创建管理程序。然后组件就填充到由管理程序规定的单元中。比如,由语句new GridLayout(3,2)创建的有三行两栏的Grid布局能产生如下六个单元: 因为有Border布局管理器,组件相应的位置不随区域的缩放而改变。只是组件的大小改变。 Grid布局管理器总是忽略组件的最佳大小。所有单元的宽度是相同的,是根据单元数对可用宽度进行平分而定的。同样地,所有单元的高度是相同的,是根据行数对可用高度进行平分而定的。 将组件添加到网格中的命令决定它们占有的单元。单元的行数是从左到右填充,就象文本一样,而页是从上到下由行填充。 行 setLayout(new GridLayout()); 创建并安装一个Grid布局,每行中的每个组件有一个栏缺省。 行 setLayout(new GridLayout(int rows, int cols)); 创建并安装一个带有规定好行数和栏数的Grid布局。对布局中所有组件所给的大小一样。 下一行: setLayout(new GridLayout(int rows, int cols, int hgap, int vgap); 创建并安装一个带有规定好行数和栏数的网格布局。布局中所有组件所给的大小一样。hgap和vgap规定组件间各自的间隙。水平间隙放在左右两边及栏与栏之间。垂直间隙放在顶部、底部及每行之间。 注—行和栏中的一个,不是两个同时,可以为0。这就是说,任何数量的对象都可以放在一个行或一个栏中。   第8—27页上所示的应用程序代码如下: import java.awt.*; public class GridEx { private Frame f; private Button b1, b2, b3, b4, b5, b6; public static void main(String args[]) { GridEx grid = new GridEx(); grid.go(); } public void go() { f = new Frame("Grid example"); f.setLayout (new GridLayout (3, 2)); b1 = new Button("1"); b2 = new Button("2"); b3 = new Button("3"); b4 = new Button("4"); b5 = new Button("5"); b6 = new Button("6"); f.add(b1); f.add(b2); f.add(b3); f.add(b4); f.add(b5); f.add(b6); f.pack(); f.setVisible(true); } } 8.9.4 Card布局管理器 Card布局管理器能将界面看作一系列的卡,其中的一个在任何时候都可见。用add()方法来将卡添加到Card布局中。Card布局管理器的show()方法应请求转换到一个新卡中。下例就是一个带有5张卡的框架。 鼠标点击左面板将视图转换到右面板,等等。 用来创建上图框架的代码段如下所示: import java.awt.*; import java.awt.event.*; public class CardTest implements MouseListener { Panel p1, p2, p3, p4, p5; Label l1, l2, l3, l4, l5; // Declare a CardLayout object, // to call its methods CardLayout myCard; Frame f; public static void main (String args[]) { CardTest ct = new CardTest (); ct.init(); } public void init () { f = new Frame ("Card Test"); myCard = new CardLayout(); f.setLayout(myCard); // create the panels that I want // to use as cards p1 = new Panel(); p2 = new Panel(); p3 = new Panel(); p4 = new Panel(); p5 = new Panel(); // create a label to attach to each panel, and // change the color of each panel, so they are // easily distinguishable l1 = new Label("This is the first Panel"); p1.setBackground(Color.yellow); p1.add(l1); l2 = new Label("This is the second Panel"); p2.setBackground(Color.green); p2.add(l2); l3 = new Label("This is the third Panel"); p3.setBackground(Color.magenta); p3.add(l3); l4 = new Label("This is the fourth Panel"); p4.setBackground(Color.white); p4.add(l4); l5 = new Label("This is the fifth Panel"); p5.setBackground(Color.cyan); p5.add(l5); // Set up the event handling here .... √ // add each panel to my CardLayout f.add(p1, "First"); f.add(p2, "Second"); f.add(p3, "Third"); f.add(p4, "Fourth"); f.add(p5, "Fifth"); 62. 63. // display the first panel 64. myCard.show(f, "First"); 65. f.setSize (200, 200); f.setVisible(true); } 8.9.5 GridBag布局管理器 GridBag布局管理器 复杂布局可以放在网格中 单个组件可以采用其最佳大小 一个组件能扩展成不止一个单元   除了Flow、Border、Grid和Card布局管理器外,核心Java.awt也提供GridBag布局管理器。 GridBag布局管理器在网格的基础上提供复杂的布局,但它允许单个组件在一个单元中而不是填满整个单元那样地占用它们的最佳大小。网格包布局管理器也允许单个组件扩展成不止一个单元。 第十节 创建面板及复杂布局 下面的程序使用一个面板,允许在一个Border布局的北部地区放置两个按钮。这种嵌套法对复杂布局来说是基本的。请注意,就框架本身而论,面板被看作象另一个组件。 import java.awt.*; public class ExGui3 { 4. 5. private Frame f; 6. private Panel p; 7. 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, BorderLayout.WEST ); f.add(bc, BorderLayout.CENTER ); p = new Panel(); bfile = new Button("File"); bhelp = new Button("Help"); p.add(bfile); p.add(bhelp); f.add(p, BorderLayout.NORTH ); f.pack(); f.setVisible(true); } } 当这个例子运行时,其结果如下所示: 如果缩放窗口,它看起来如下: 请注意,现在,Border布局的北部地区有效地保持着两个按钮。事实上,它保持的只是单个面板,但这个面板含有这两个按钮。 面板的大小和位置是由Border布局管理器决定的,一个面板的最佳大小是由该面板中组件的最佳大小来决定的。面板中按钮的大小和位置受Flow布局的控制,该布局是由缺省与面板相关联的。 练习:建立Java GUIs 练习目的—在这个实验中,开发两个图形用户界面。 一、准备 为了成功地完成这个实验,必须理解图形用户界面的目的,掌握如何用布局管理器来创建图形用户界面。 二、任务 一级实验:创建计算器GUI 创建下述GUI: 三级实验:创建帐目GUI 创建一个GUI,它能给模块5中创建的Teller.Java应用程序提供前端用户界面。需要研究一些本模块中没有描述的组件。(它们将在本课程的后面讲解)。 三、练习总结 讨论—花几分钟讨论在实验练习中的经验、问题或发现。 经验 解释 总结 应用 四、检查进步情况 在继续下一个模块前,检查一下,你确实能: 描述AWT包及其组件 定义Container组件及布局管理器等术语,以及它们如何一起工作来建立GUI的 使用布局管理器 使用Flow、Border、Grid和Card布局管理器来获得所需的动态布局 添加组件到容器 正确使用框架和面板容器 描述复杂布局与嵌套容器是如何工作的 在Java程序中,确认下列: 容器 相关的布局管理器 所有组件的布局层次 五、思考 现在知道如何在计算机屏幕上显示一个GUI了,那么,要使GUI有用,还需要什么呢?