47.java编程思想——创建窗口和程序片 AWT
在Java 1.0 中,图形用户接口(GUI)库最初的设计目标是让程序员构建一个通用的GUI,使其在所有平台上都能正常显示。
但遗憾的是,这个目标并未达到。事实上,Java 1.0 版的“抽象Windows 工具包”(AWT)产生的是在各系统看来都同样欠佳的图形用户接口。除此之外,它还限制我们只能使用四种字体,并且不能访问操作系统中现有的高级GUI 元素。同时,Jave1.0 版的AWT编程模型也不是面向对象的,极不成熟。这类情况在Java1.1版的AWT 事件模型中得到了很好的改进,例如:更加清晰、面向对象的编程、遵循JavaBeans 的范例,以及一个可轻松创建可视编程环境的编程组件模型。Java1.2 为老的Java 1.0 AWT 添加了Java 基础类(AWT),这是一个被称为“Swing”的GUI的一部分。丰富的、易于使用和理解的Java Beans 能经过拖放操作(像手工编程一样的好),创建出能使程序员满意的GUI。软件业的“3 次修订版”规则看来对于程序设计语言也是成立的(一个产品除非经过第3 次修订,否则不会尽如人意)。
Java 的主要设计目的之一是建立程序片,也就是建立运行在WEB 浏览器上的小应用程序。由于它们必须是安全的,所以程序片在运行时必须加以限制。无论怎样,它们都是支持客户端编程的强有力的工具,一个重要的应用便是在Web 上。
在一个程序片中编程会受到很多的限制,我们一般说它“在沙箱内”,这是由于Java 运行时一直会有某个东西——即Java 运行期安全系统——在监视着我们。Jave 1.1 为程序片提供了数字签名,所以可选出能信赖的程序片去访问主机。不过,我们也能跳出沙箱的限制写出可靠的程序。在这种情况下,我们可访问操作系统中的其他功能。AWT 也能用来为可靠的程序建立GUI 接口。
先学习使用老的AWT 工具,会与许多支持和使用AWT 的代码程序样本相遇。尽管这有一些困难,但却是必须的,因为我们必须用老的AWT 来维护和阅读传统的Java 代码。有时甚至需要我们编写AWT 代码去支持不能从Java1.0 升级的环境。
大多数的例程都将展示程序片的建立,这并不仅仅是因为这非常的容易,更因为这是AWT 的主要作用。另外,当用AWT 创建一个可靠的程序时,我们将看到处理程序的不同之处,以及怎样创建能在命令行和浏览器中运行的程序。
1 AWT
对于“老式”AWT,它最严重的缺点就是它无论在面向对象设计方面,还是在GUI 开发包设计方面,都有不尽如人意的表现。它使我们回到了程序设计的黑暗年代(换成其他话就是“拙劣的”、“可怕的”、“恶劣的”等等)。必须为执行每一个事件编写代码,包括在其他环境中利用“资源”即可轻松完成的一些任务。
许多象这样的问题在Java 1.1 里都得到了缓解或排除,因为:
(1)Java 1.1 的新型AWT 是一个更好的编程模型,并向更好的库设计迈出了可喜的一步。而Java Beans 则是那个库的框架。
(2)“GUI 构建器”(可视编程环境)将适用于所有开发系统。在我们用图形化工具将组件置入窗体的时候,Java Beans 和新的AWT 使GUI构建器能帮我们自动完成代码。其它组件技术如ActiveX 等也将以相同的形式支持。
既然如此,为什么还要学习使用老的AWT 呢?原因很简单,因为它的存在是个事实。就目前来说,这个事实对我们来说显得有些不利,它涉及到面向对象库设计的一个宗旨:一旦我们在库中公布一个组件,就再不能去掉它。如去掉它,就会损害别人已存在的代码。另外,当我们学习Java 和所有使用老AWT 的程序时,会发现有许多原来的代码使用的都是老式AWT。
AWT 必须能与固有操作系统的GUI 组件打交通,这意味着它需要执行一个程序片不可能做到的任务。一个不被信任的程序片在操作系统中不能作出任何直接调用,否则它会对用户的机器做出不恰当的事情。一个不被信任的程序片不能访问重要的功能。例如,“在屏幕上画一个窗口”的唯一方法是通过调用拥有特殊接口和安全检查的标准Java 库。Sun 公司的原始模型创建的信任库将仅仅供给Web 浏览器中的Java 系统信任关系自动授权器使用,自动授权器将控制怎样进入到库中去。
Java 1.1 版中的新模型是“信任代码”或“签名代码”,因此一个特殊服务器将校验我们下载的、由规定的开发者使用的公共密钥加密系统的代码。这样我们就可知道代码从何而来,那真的是Bob 的代码,还是由某人伪装成Bob 的代码。这并不能阻止Bob 犯错误或作某些恶意的事,但能防止Bob 逃避匿名制造计算机病毒的责任。一个数字签名的程序片——“被信任的程序片”——在Java1.1 版能进入我们的机器并直接控制它,正像一些其它的应用程序从信任关系自动授权机中得到“信任”并安装在我们的机器上。
这是老AWT 的所有特点。老的AWT 代码将一直存在,新的Java 编程者在从旧的书本中学习时将会遇到老的AWT 代码。同样,老的AWT 也是值得去学习的,例如在一个只有少量库的例程设计中。老的AWT 所包括的范围在不考虑深度和枚举每一个程序和类,取而代之的是给了我们一个老AWT 设计的概貌。
2 基本程序片
库通常按照它们的功能来进行组合。一些库,例如使用过的,便中断搁置起来。标准的Java 库字符串和矢量类就是这样的一个例子。其他的库被特殊地设计,例如构建块去建立其它的库。库中的某些类是应用程序的框架,其目的是协助我们构建应用程序,在提供类或类集的情况下产生每个特定应用程序的基本活动状况。
然后,为我们定制活动状况,必须继承应用程序类并且废弃程序的权益。应用程序框架的默认控制结构将在特定的时间调用我们废弃的程序。应用程序的框架是“分离、改变和中止事件”的好例子,因为它总是努力去尝试集中在被废弃的所有特殊程序段。
程序片利用应用程序框架来建立。我们从类中继承程序片,并且废弃特定的程序。大多数时间我们必须考虑一些不得不运行的使程序片在WEB 页面上建立和使用的重要方法。这些方法是:
方法 作用
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(字串),并指定它在程序片作图区域的起点。这个位置用像素表示,所以它在不同的机器上看起来是不同的,但至少是可以移植的。
2.1 代码
import java.awt.*;
import java.applet.*;
public class Applet1 extends Applet {
public voidpaint(Graphics g){
g.drawString("First applet", 10, 10);
}
} /// :~
2.2 执行
如下图1:
这个程序片不需要有一个main()。所有内容都封装到应用程序框架中;我们将所有启动代码都放在init()里。
将这个程序放到一个Web 页中运行,而只能在支持Java 的Web 浏览器中才能看到此页。为了将一个程序片置入Web 页,需要在那个Web 页的代码中设置一个特殊的标记,以指示网页装载和运行程序片。这就是applet 标记,它在Applet1 中的样子如下:
<applet
code=Applet1
width=200
height=200>
</applet>
其中,code 值指定了.class文件的名字,程序片就驻留在那个文件中。width 和height指定这个程序片的初始尺寸(如前所述,以像素为单位)。还可将另一些东西放入applet 标记:用于在因特网上寻找其他.class 文件的位置(codebase)、对齐和排列信息(align)、使程序片相互间能够通信的一个特殊标识符(name)以及用于提供程序片能接收的信息的参数。参数采取下述形式:
<Paramname=标识符 value="信息">
可根据需要设置任意多个这样的参数。
在简单的程序片中,我们要做的唯一事情是按上述形式在Web 页中设置一个程序片标记(applet),令其装载和运行程序片。
在最新的ECLIPSE开发工具上可以直接调试 APPLET的。
3 程序片的测试
我们可在不必建立网络连接的前提下进行一次简单的测试,方法是启动我们的Web 浏览器,然后打开包含了程序片标签的HTML 文件(Sun 公司的JDK 同样包括一个称为“程序片观察器”的工具,它能挑出html 文件的<applet> 标记,并运行这个程序片,不必显示周围的HTML 文本)。html 文件载入后,浏览器会发现程序片的标签,并查找由code 值指定的.class 文件。当然,它会先在CLASSPATH(类路径)中寻找,如果在CLASSPATH 下找不到类文件,就在WEB 浏览器状态栏给出一个错误信息,告知不能找到.class 文件。
由于程序片观察器会忽略除APPLET 标记之外的任何东西,所以可将那些标记作为注释置入Java 源码:
// <appletcode=MyApplet.class width=200 height=100></applet>
这样就可直接执行“appletviewer MyApplet.java”,不必再创建小的HTML 文件来完成测试。
若想在Web 站点上试验,还会碰到另一些麻烦。首先,我们必须有一个Web 站点,这对大多数人来说都意味着位于远程地点的一家服务提供商(ISP)。然后必须通过某种途径将HTML 文件和.class 文件从自己的站点移至ISP 机器上正确的目录(WWW 目录)。这一般是通过采用“文件传输协议”(FTP)的程序来做成的,网上可找到许多这样的免费程序。所以我们要做的全部事情似乎就是用FTP协议将文件移至ISP 的机器,然后用自己的浏览器连接网站和HTML文件;假如程序片正确装载和执行,就表明大功告成。但真是这样吗?
但这儿我们可能会受到愚弄。假如Web 浏览器在服务器上找不到.class 文件,就会在你的本地机器上搜寻CLASSPATH。所以程序片或许根本不能从服务器上正确地装载,但在你看来却是一切正常的,因为浏览器在你的机器上找到了它需要的东西。但在其他人访问时,他们的浏览器就无法找到那些类文件。所以在测试时,必须确定已从自己的机器删除了相关的.class 文件,以确保测试结果的真实。
4 图形化的例子
4.1 代码2
import java.awt.*;
import java.applet.*;
public class Applet2 extends Applet {
public voidpaint(Graphics g){
g.drawString("Second applet", 10, 15);
g.draw3DRect(0, 0, 100, 20, true);
}
} /// :~
4.2 执行
如图2:
这个程序用一个方框将字符串包围起来。当然,所有数字都是“硬编码”的(指数字固定于程序内部),并以像素为基础。所以在一些机器上,框会正好将字串围住;而在另一些机器上,也许根本看不见这个框,因为不同机器安装的字体也会有所区别。
对Graphic 类而言,可在帮助文档中找到另一些有趣的内容。大多数涉及图形的活动都是很有趣的。
5 框架方法的演示
观看框架方法的实际运作是相当有趣的。下面的程序片将跟踪这些方法调用的次数,并用paint()将其显示出来:
5.1 代码3
import java.awt.*;
import java.applet.*;
public class Applet3 extends Applet {
String s;
int inits= 0;
int starts= 0;
int stops= 0;
public voidinit() {
inits++;
}
public voidstart() {
starts++;
}
public voidstop() {
stops++;
}
public voidpaint(Graphics g){
s = "inits:" + inits + ", starts: " + starts+ ", stops: " + stops;
g.drawString(s, 10, 10);
}
} /// :~
5.2 执行
正常情况下,当我们过载一个方法时,需检查自己是否需要调用方法的基础类版本,这是十分重要的。例如,使用init()时可能需要调用super.init()。然而,Applet 文档特别指出init()、start()和stop()在Applet 中没有用处,所以这里不需要调用它们。
试验这个程序片时,会发现假如最小化WEB 浏览器,或者用另一个窗口将其覆盖,那么就不能再调用stop()和start()(这一行为会随着不同的实现方案变化;可考虑将Web 浏览器的行为同程序片观察器的行为对照一下)。调用唯一发生的场合是在我们转移到一个不同的Web 页,然后返回包含了程序片的那个页时。