第13章 创建窗口和程序片.docx
《第13章 创建窗口和程序片.docx》由会员分享,可在线阅读,更多相关《第13章 创建窗口和程序片.docx(71页珍藏版)》请在冰豆网上搜索。
第13章创建窗口和程序片
第十三章创建窗口和程序片
在Java1.0中,图形用户接口(GUI)库最初的设计目标是让程序员构建一个通用的GUI,使其在所有平台上都能正常显示。
但遗憾的是,这个目标并未达到。
事实上,Java1.0版的“抽象Windows工具包”(AWT)产生的是在各系统看来都同样欠佳的图形用户接口。
除此之外,它还限制我们只能使用四种字体,并且不能访问操作系统中现有的高级GUI元素。
同时,Jave1.0版的AWT编程模型也不是面向对象的,极不成熟。
这类情况在Java1.1版的AWT事件模型中得到了很好的改进,例如:
更加清晰、面向对象的编程、遵循JavaBeans的范例,以及一个可轻松创建可视编程环境的编程组件模型。
Java1.2为老的Java1.0AWT添加了Java基础类(AWT),这是一个被称为“Swing”的GUI的一部分。
丰富的、易于使用和理解的JavaBeans能经过拖放操作(像手工编程一样的好),创建出能使程序员满意的GUI。
软件业的“3次修订版”规则看来对于程序设计语言也是成立的(一个产品除非经过第3次修订,否则不会尽如人意)。
Java的主要设计目的之一是建立程序片,也就是建立运行在WEB浏览器上的小应用程序。
由于它们必须是安全的,所以程序片在运行时必须加以限制。
无论怎样,它们都是支持客户端编程的强有力的工具,一个重要的应用便是在Web上。
在一个程序片中编程会受到很多的限制,我们一般说它“在沙箱内”,这是由于Java运行时一直会有某个东西——即Java运行期安全系统——在监视着我们。
Jave1.1为程序片提供了数字签名,所以可选出能信赖的程序片去访问主机。
不过,我们也能跳出沙箱的限制写出可靠的程序。
在这种情况下,我们可访问操作系统中的其他功能。
在这本书中我们自始至终编写的都是可靠的程序,但它们成为了没有图形组件的控制台程序。
AWT也能用来为可靠的程序建立GUI接口。
在这一章中我们将先学习使用老的AWT工具,我们会与许多支持和使用AWT的代码程序样本相遇。
尽管这有一些困难,但却是必须的,因为我们必须用老的AWT来维护和阅读传统的Java代码。
有时甚至需要我们编写AWT代码去支持不能从Java1.0升级的环境。
在本章第二部分,我们将学习Java1.1版中新的AWT结构并会看到它的事件模型是如此的优秀(如果能掌握的话,那么在编制新的程序时就可使用这最新的工具。
最后,我们将学习新的能像类库一样加入到Java1.1版中的JFC/Swing组件,这意味着不需要升级到Java1.2便能使用这一类库。
大多数的例程都将展示程序片的建立,这并不仅仅是因为这非常的容易,更因为这是AWT的主要作用。
另外,当用AWT创建一个可靠的程序时,我们将看到处理程序的不同之处,以及怎样创建能在命令行和浏览器中运行的程序。
请注意的是这不是为了描述类的所有程序的综合解释。
这一章将带领我们从摘要开始。
当我们查找更复杂的内容时,请确定我们的信息浏览器通过查找类和方法来解决编程中的问题(如果我们正在使用一个开发环境,信息浏览器也许是内建的;如果我们使用的是SUN公司的JDK则这时我们要使用WEB浏览器并在Java根目录下面开始)。
附录F列出了用于深入学习库知识的其他一些参考资料。
13.1为何要用AWT?
对于本章要学习的“老式”AWT,它最严重的缺点就是它无论在面向对象设计方面,还是在GUI开发包设计方面,都有不尽如人意的表现。
它使我们回到了程序设计的黑暗年代(换成其他话就是“拙劣的”、“可怕的”、“恶劣的”等等)。
必须为执行每一个事件编写代码,包括在其他环境中利用“资源”即可轻松完成的一些任务。
许多象这样的问题在Java1.1里都得到了缓解或排除,因为:
(1)Java1.1的新型AWT是一个更好的编程模型,并向更好的库设计迈出了可喜的一步。
而JavaBeans则是那个库的框架。
(2)“GUI构建器”(可视编程环境)将适用于所有开发系统。
在我们用图形化工具将组件置入窗体的时候,JavaBeans和新的AWT使GUI构建器能帮我们自动完成代码。
其它组件技术如ActiveX等也将以相同的形式支持。
既然如此,为什么还要学习使用老的AWT呢?
原因很简单,因为它的存在是个事实。
就目前来说,这个事实对我们来说显得有些不利,它涉及到面向对象库设计的一个宗旨:
一旦我们在库中公布一个组件,就再不能去掉它。
如去掉它,就会损害别人已存在的代码。
另外,当我们学习Java和所有使用老AWT的程序时,会发现有许多原来的代码使用的都是老式AWT。
AWT必须能与固有操作系统的GUI组件打交通,这意味着它需要执行一个程序片不可能做到的任务。
一个不被信任的程序片在操作系统中不能作出任何直接调用,否则它会对用户的机器做出不恰当的事情。
一个不被信任的程序片不能访问重要的功能。
例如,“在屏幕上画一个窗口”的唯一方法是通过调用拥有特殊接口和安全检查的标准Java库。
Sun公司的原始模型创建的信任库将仅仅供给Web浏览器中的Java系统信任关系自动授权器使用,自动授权器将控制怎样进入到库中去。
但当我们想增加操作系统中访问新组件的功能时该怎么办?
等待Sun来决定我们的扩展被合并到标准的Java库中,但这不一定会解决我们的问题。
Java1.1版中的新模型是“信任代码”或“签名代码”,因此一个特殊服务器将校验我们下载的、由规定的开发者使用的公共密钥加密系统的代码。
这样我们就可知道代码从何而来,那真的是Bob的代码,还是由某人伪装成Bob的代码。
这并不能阻止Bob犯错误或作某些恶意的事,但能防止Bob逃避匿名制造计算机病毒的责任。
一个数字签名的程序片——“被信任的程序片”——在Java1.1版能进入我们的机器并直接控制它,正像一些其它的应用程序从信任关系自动授权机中得到“信任”并安装在我们的机器上。
这是老AWT的所有特点。
老的AWT代码将一直存在,新的Java编程者在从旧的书本中学习时将会遇到老的AWT代码。
同样,老的AWT也是值得去学习的,例如在一个只有少量库的例程设计中。
老的AWT所包括的范围在不考虑深度和枚举每一个程序和类,取而代之的是给了我们一个老AWT设计的概貌。
13.2基本程序片
库通常按照它们的功能来进行组合。
一些库,例如使用过的,便中断搁置起来。
标准的Java库字符串和矢量类就是这样的一个例子。
其他的库被特殊地设计,例如构建块去建立其它的库。
库中的某些类是应用程序的框架,其目的是协助我们构建应用程序,在提供类或类集的情况下产生每个特定应用程序的基本活动状况。
然后,为我们定制活动状况,必须继承应用程序类并且废弃程序的权益。
应用程序框架的默认控制结构将在特定的时间调用我们废弃的程序。
应用程序的框架是“分离、改变和中止事件”的好例子,因为它总是努力去尝试集中在被废弃的所有特殊程序段。
程序片利用应用程序框架来建立。
我们从类中继承程序片,并且废弃特定的程序。
大多数时间我们必须考虑一些不得不运行的使程序片在WEB页面上建立和使用的重要方法。
这些方法是:
591页表
方法作用
init()程序片第一次被创建,初次运行初始化程序片时调用
start()每当程序片进入Web浏览器中,并且允许程序片启动它的常规操作时调用(特殊的程序片被stop()关闭);同样在init()后调用
paint()基础类Component的一部分(继承结构中上溯三级)。
作为update()的一部分调用,以便对程序片的画布进行特殊的描绘
stop()每次程序片从Web浏览器的视线中离开时调用,使程序片能关闭代价高昂的操作;同样在调用destroy()前调用
destroy()程序片不再需要,将它从页面中卸载时调用,以执行资源的最后清除工作
现在来看一看paint()方法。
一旦Component(目前是程序片)决定自己需要更新,就会调用这个方法——可能是由于它再次回转屏幕,首次在屏幕上显示,或者是由于其他窗口临时覆盖了你的Web浏览器。
此时程序片会调用它的update()方法(在基础类Component中定义),该方法会恢复一切该恢复的东西,而调用paint()正是这个过程的一部分。
没必要对paint()进行过载处理,但构建一个简单的程序片无疑是方便的方法,所以我们首先从paint()方法开始。
update()调用paint()时,会向其传递指向Graphics对象的一个句柄,那个对象代表准备在上面描绘(作图)的表面。
这是非常重要的,因为我们受到项目组件的外观的限制,因此不能画到区域外,这可是一件好事,否则我们就会画到线外去。
在程序片的例子中,程序片的外观就是这界定的区域。
图形对象同样有一系列我们可对其进行的操作。
这些操作都与在画布上作图有关。
所以其中的大部分都要涉及图像、几何菜状、圆弧等等的描绘(注意如果有兴趣,可在Java文档中找到更详细的说明)。
有些方法允许我们画出字符,而其中最常用的就是drawString()。
对于它,需指出自己想描绘的String(字串),并指定它在程序片作图区域的起点。
这个位置用像素表示,所以它在不同的机器上看起来是不同的,但至少是可以移植的。
根据这些信息即可创建一个简单的程序片:
592页上程序
注意这个程序片不需要有一个main()。
所有内容都封装到应用程序框架中;我们将所有启动代码都放在init()里。
必须将这个程序放到一个Web页中才能运行,而只能在支持Java的Web浏览器中才能看到此页。
为了将一个程序片置入Web页,需要在那个Web页的代码中设置一个特殊的标记(注释①),以指示网页装载和运行程序片。
这就是applet标记,它在Applet1中的样子如下:
592页下程序
①:
本书假定读者已掌握了HTML的基本知识。
这些知识不难学习,有许多书籍和网上资源都可以提供帮助。
其中,code值指定了.class文件的名字,程序片就驻留在那个文件中。
width和height指定这个程序片的初始尺寸(如前所述,以像素为单位)。
还可将另一些东西放入applet标记:
用于在因特网上寻找其他.class文件的位置(codebase)、对齐和排列信息(align)、使程序片相互间能够通信的一个特殊标识符(name)以及用于提供程序片能接收的信息的参数。
参数采取下述形式:
可根据需要设置任意多个这样的参数。
在简单的程序片中,我们要做的唯一事情是按上述形式在Web页中设置一个程序片标记(applet),令其装载和运行程序片。
13.2.1程序片的测试
我们可在不必建立网络连接的前提下进行一次简单的测试,方法是启动我们的Web浏览器,然后打开包含了程序片标签的HTML文件(Sun公司的JDK同样包括一个称为“程序片观察器”的工具,它能挑出html文件的
html文件载入后,浏览器会发现程序片的标签,并查找由code值指定的.class文件。
当然,它会先在CLASSPATH(类路径)中寻找,如果在CLASSPATH下找不到类文件,就在WEB浏览器状态栏给出一个错误信息,告知不能找到.class文件。
②;由于程序片观察器会忽略除APPLET标记之外的任何东西,所以可将那些标记作为注释置入Java源码:
//
这样就可直接执行“appletviewerMyApplet.java”,不必再创建小的HTML文件来完成测试。
若想在Web站点上试验,还会碰到另一些麻烦。
首先,我们必须有一个Web站点,这对大多数人来说都意味着位于远程地点的一家服务提供商(ISP)。
然后必须通过某种途径将HTML文件和.class文件从自己的站点移至ISP机器上正确的目录(WWW目录)。
这一般是通过采用“文件传输协议”(FTP)的程序来做成的,网上可找到许多这样的免费程序。
所以我们要做的全部事情似乎就是用FTP协议将文件移至ISP的机器,然后用自己的浏览器连接网站和HTML文件;假如程序片正确装载和执行,就表明大功告成。
但真是这样吗?
但这儿我们可能会受到愚弄。
假如Web浏览器在服务器上找不到.class文件,就会在你的本地机器上搜寻CLASSPATH。
所以程序片或许根本不能从服务器上正确地装载,但在你看来却是一切正常的,因为浏览器在你的机器上找到了它需要的东西。
但在其他人访问时,他们的浏览器就无法找到那些类文件。
所以在测试时,必须确定已从自己的机器删除了相关的.class文件,以确保测试结果的真实。
我自己就遇到过这样的一个问题。
当时是将程序片置入一个package(包)中。
上载了HTML文件和程序片后,由于包名的问题,程序片的服务器路径似乎陷入了混乱。
但是,我的浏览器在本地类路径(CLASSPATH)中找到了它。
这样一来,我就成了能够成功装载程序片的唯一一个人。
后来我花了一些时间才发现原来是package语句有误。
一般地,应该将package语句置于程序片的外部。
13.2.2一个更图形化的例子
这个程序不会太令人紧张,所以让我们试着增加一些有趣的图形组件。
594页程序
这个程序用一个方框将字符串包围起来。
当然,所有数字都是“硬编码”的(指数字固定于程序内部),并以像素为基础。
所以在一些机器上,框会正好将字串围住;而在另一些机器上,也许根本看不见这个框,因为不同机器安装的字体也会有所区别。
对Graphic类而言,可在帮助文档中找到另一些有趣的内容。
大多数涉及图形的活动都是很有趣的,所有我将更多的试验留给读者自己去进行。
13.2.3框架方法的演示
观看框架方法的实际运作是相当有趣的(这个例子只使用init(),start()和stop(),因为paint()和destroy()非常简单,很容易就能掌握)。
下面的程序片将跟踪这些方法调用的次数,并用paint()将其显示出来:
595页程序
正常情况下,当我们过载一个方法时,需检查自己是否需要调用方法的基础类版本,这是十分重要的。
例如,使用init()时可能需要调用super.init()。
然而,Applet文档特别指出init()、start()和stop()在Applet中没有用处,所以这里不需要调用它们。
试验这个程序片时,会发现假如最小化WEB浏览器,或者用另一个窗口将其覆盖,那么就不能再调用stop()和start()(这一行为会随着不同的实现方案变化;可考虑将Web浏览器的行为同程序片观察器的行为对照一下)。
调用唯一发生的场合是在我们转移到一个不同的Web页,然后返回包含了程序片的那个页时。
13.3制作按钮
制作一个按钮非常简单:
只需要调用Button构建器,并指定想在按钮上出现的标签就行了(如果不想要标签,亦可使用默认构建器,但那种情况极少出现)。
可参照后面的程序为按钮创建一个句柄,以便以后能够引用它。
Button是一个组件,象它自己的小窗口一样,会在更新时得以重绘。
这意味着我们不必明确描绘一个按钮或者其他任意种类的控件;只需将它们纳入窗体,以后的描绘工作会由它们自行负责。
所以为了将一个按钮置入窗体,需要过载init()方法,而不是过载paint():
596页程序
但这还不足以创建Button(或其他任何控件)。
必须同时调用Appletadd()方法,令按钮放置在程序片的窗体中。
这看起来似乎比实际简单得多,因为对add()的调用实际会(间接地)决定将控件放在窗体的什么地方。
对窗体布局的控件马上就要讲到。
13.4捕获事件
大家可注意到假如编译和运行上面的程序片,按下按钮后不会发生任何事情。
必须进入程序片内部,编写用于决定要发生什么事情的代码。
对于由事件驱动的程序设计,它的基本目标就是用代码捕获发生的事件,并由代码对那些事件作出响应。
事实上,GUI的大部分内容都是围绕这种事件驱动的程序设计展开的。
经过本书前面的学习,大家应该有了面向对象程序设计的一些基础,此时可能会想到应当有一些面向对象的方法来专门控制事件。
例如,也许不得不继承每个按钮,并过载一些“按钮按下”方法(尽管这显得非常麻烦有有限)。
大家也可能认为存在一些主控“事件”类,其中为希望响应的每个事件都包含了一个方法。
在对象以前,事件控制的典型方式是switch语句。
每个事件都对应一个独一无二的整数编号;而且在主事件控制方法中,需要专门为那个值写一个switch。
Java1.0的AWT没有采用任何面向对象的手段。
此外,它也没有使用switch语句,没有打算依靠那些分配给事件的数字。
相反,我们必须创建if语句的一个嵌套系列。
通过if语句,我们需要尝试做的事情是侦测到作为事件“目标”的对象。
换言之,那是我们关心的全部内容——假如某个按钮是一个事件的目标,那么它肯定是一次鼠标点击,并要基于那个假设继续下去。
但是,事件里也可能包含了其他信息。
例如,假如想调查一次鼠标点击的像素位置,以便画一条引向那个位置的线,那么Event对象里就会包含那个位置的信息(也要注意Java1.0的组件只能产生有限种类的事件,而Java1.1和Swing/JFC组件则可产生完整的一系列事件)。
Java1.0版的AWT方法串联的条件语句中存在action()方法的调用。
虽然整个Java1.0版的事件模型不兼容Java1.1版,但它在还不支持Java1.1版的机器和运行简单的程序片的系统中更广泛地使用,忠告您使用它会变得非常的舒适,包括对下面使用的action()程序方法而言。
action()拥有两个自变量:
第一个是事件的类型,包括所有的触发调用action()的事件的有关信息。
例如鼠标单击、普通按键按下或释放、特殊按键按下或释放、鼠标移动或者拖动、事件组件得到或丢失焦点,等等。
第二个自变量通常是我们忽略的事件目标。
第二个自变量封装在事件目标中,所以它像一个自变量一样的冗长。
需调用action()时情况非常有限:
将控件置入窗体时,一些类型的控件(按钮、复选框、下拉列表单、菜单)会发生一种“标准行动”,从而随相应的Event对象发起对action()的调用。
比如对按钮来说,一旦按钮被按下,而且没有再多按一次,就会调用它的action()方法。
这种行为通常正是我们所希望的,因为这正是我们对一个按钮正常观感。
但正如本章后面要讲到的那样,还可通过handleEvent()方法来处理其他许多类型的事件。
前面的例程可进行一些扩展,以便象下面这样控制按钮的点击:
598页程序
为了解目标是什么,需要向Event对象询问它的target(目标)成员是什么,然后用equals()方法检查它是否与自己感兴趣的目标对象句柄相符。
为所有感兴趣的对象写好句柄后,必须在末尾的else语句中调用super.action(evt,arg)方法。
我们在第7章已经说过(有关多形性的那一章),此时调用的是我们过载过的方法,而非它的基础类版本。
然而,基础类版本也针对我们不感兴趣的所有情况提供了相应的控制代码。
除非明确进行,否则它们是不会得到调用的。
返回值指出我们是否已经处理了它,所以假如确实与一个事件相符,就应返回true;否则就返回由基础类event()返回的东西。
对这个例子来说,最简单的行动就是打印出到底是什么按钮被按下。
一些系统允许你弹出一个小消息窗口,但Java程序片却防碍窗口的弹出。
不过我们可以用调用Applet方法的getAppletContext()来访问浏览器,然后用showStatus()在浏览器窗口底部的状态栏上显示一条信息(注释③)。
还可用同样的方法打印出对事件的一段完整说明文字,方法是调用getAppletConext().showStatus(evt+"")。
空字串会强制编译器将evt转换成一个字符串。
这些报告对于测试和调试特别有用,因为浏览器可能会覆盖我们的消息。
③:
ShowStatus()也属于Applet的一个方法,所以可直接调用它,不必调用getAppletContext()。
尽管看起来似乎很奇怪,但我们确实也能通过event()中的第二个参数将一个事件与按钮上的文字相配。
采用这种方法,上面的例子就变成了:
599页程序
很难确切知道equals()方法在这儿要做什么。
这种方法有一个很大的问题,就是开始使用这个新技术的Java程序员至少需要花费一个受挫折的时期来在比较按钮上的文字时发现他们要么大写了要么写错了(我就有这种经验)。
同样,如果我们改变了按钮上的文字,程序代码将不再工作(但我们不会得到任何编译时和运行时的信息)。
所以如果可能,我们就得避免使用这种方法。
13.5文本字段
“文本字段”是允许用户输入和编辑文字的一种线性区域。
文本字段从文本组件那里继承了让我们选择文字、让我们像得到字符串一样得到选择的文字,得到或设置文字,设置文本字段是否可编辑以及连同我们从在线参考书中找到的相关方法。
下面的例子将证明文本字段的其它功能;我们能注意到方法名是显而易见的:
600-601页程序
有几种方法均可构建一个文本字段;其中之一是提供一个初始字符串,并设置字符域的大小。
按下按钮1是得到我们用鼠标选择的文字就是得到字段内所有的文字并转换成字符串S。
它也允许字段被编辑。
按下按钮2放一条信息和字符串s到Textfields,并且阻止字段被编辑(尽管我们能够一直选择文字)。
文字的可编辑性是通过setEditable()的真假值来控制的。
13.6文本区域
“文本区域”很像文字字段,只是它拥有更多的行以及一些引人注目的更多的功能。
另外你能在给定位置对一个文本字段追加、插入或者修改文字。
这看起来对文本字段有用的功能相当不错,所以设法发现它设计的特性会产生一些困惑。
我们可以认为如果我们处处需要“文本区域”的功能,那么可以简单地使用一个线型文字区域在我们将另外使用文本字段的地方。
在Java1.0版中,当它们不是固定的时候我们也得到了一个文本区域的垂直和水平方向的滚动条。
在Java1.1版中,对高级构建器的修改允许我们选择哪个滚动条是当前的。
下面的例子演示的仅仅是在Java1.0版的状况下滚动条一直打开。
在下一章里我们将看到一个证明Java1.1版中的文字区域的例程。
601-602页程序
程序中有几个不同的“文本区域”构建器,这其中的一个在此处显示了一个初始字符串和行号和列号。
不同的按钮显示得到、追加、修改和插入文字。
13.7标签
标签准确地运作:
安放一个标签到窗体上。
这对没有标签的TextFields和Textareas来说非常的重要,如果我们简单地想安放文字的信息在窗体上也能同样的使用。
我们能像本章中第一个例程中演示的那样,使用drawString()里边的paint()在确定的位置去安置一个文字。
当我们使用的标签允许我们通过布局管理加入其它的文字组件。
(在这章的后面我们将进入讨论。
)
使用构建器我们能创建一条包括初始化文字的标签(这是我们典型的作法),一个标签包括一行CENTER(中间)、LEFT(左)和RIGHT(右)(静态的结果取整定义在类标签里)。
如果我们忘记了可以用getText()和getalignment()读取值,我们同样可以用setText()和setAlignment()来改变和调整。
下面的例子将演示标签的特点:
603-604页程序
首先是标签的最典型的用途:
标记一个文本字段或文本区域。
在例程的第二部分,当我们按下“test1”按钮通过setText()将一串空的空格插入到的字段里。
因为空的空格数不等于同样的字符数(在一个等比例间隔的字库里),当插入文字到标签里时我们会看到文字将被省略掉。
在例子的第三部分保留的空的空格在我们第一次按下“test2”会发现标签是空的(trim()删除了每个字符串结尾部分的空格)并且在开头的左列插入了一个短