10.1 简史
Java 1.0刚出现时,包含了一个用于基本GUI程序设计的类库—抽象窗口工具包(Abstract Window Toolkit,AWT)。
AWT将处理用户界面元素的任务委托给各个目标平台(Windows、Solaris、Macintosh等)上的原生GUI 工具包,由其负责用户界面元素的创建和行为。例如,如果使用最初的AWT在Java窗口中放置一个文本框,就会有一个底层的“对等”文本框具体处理文本输入。从理论上说,所得到的程序可以运行在任何平台上,而且有目标平台的观感(look and feel)。
对于简单的应用,这种基于“对等元素”的方法是可行的。但是,要想编写依赖于原生用户界面元素的高质量、可移植的图形库,显然极其困难。例如,在不同的平台上,菜单、滚动条和文本域这些用户界面元素的操作行为存在着一些微妙的差别。而且,有些图形环境(如X11/Motif)并没有像Windows或Macintosh这样丰富的用户界面组件集合。这就进一步限制了基于“最小公分母”方法实现的可移植库。因此,使用AWT构建的GUI应用程序看起来没有原生的Windows或Macintosh应用那么漂亮,也没有提供那些平台用户所期望的功能。更加糟糕的是,不同平台上的AWT用户界面库中存在着不同的bug。研发人员必须在每一个平台上测试应用程序,因此人们嘲弄地把这种做法称为“一次编写,到处调试”
1996年,Netscape创建了一种称为IFC ( Internet Foundation Class)的GUI库,它采用了与AWT完全不同的工作方式。它将按钮、菜单等用户界面元素绘制在空白窗口上。底层窗口系统所需的唯一功能就是能够显示一个窗口,并在这个窗口中绘制。因此,不论程序在哪个平台上运行,Netscape的IFC组件都有着相同的外观和行为。Sun公司与Netscape合作完善了这种方法, 创建了一个名为 Swing 的用户界面库。
Swing 不是完全替代AWT,而是构建在AWT架构之上。提供了更加强大的用户界面组件。还是在使用AWT的基本机制,特别是事件处理。
10.2 显示窗体
10.2.1 创建窗体
首先,所有的Swing组件必须由事件分派线程( event dispatch thread)配置,这是控制线程,它将鼠标点击和按键等事件传递给用户接口组件。下面的代码段用来执行事件分派线程中的语句:
EventQueue.invokeLater(()->
{
statements
});
很多Swing程序并没有在事件分派线程中初始化用户界面。原先完全可以接受在主线程中完成初始化。随着Swing组件变得越来越复杂,JDK开发人员无法保证这种方式的安全性。虽然发生错误的概率非常小,但这种间歇性问题十分烦人。最好采用正确的做法,即使代码看起来有些难懂。
在初始化语句结束后,main方法退出。并没有终止程序,终止的只是主线程。事件分派线程会保持程序处于激活状态,直到关闭窗体或调用System.exit方法终止程序。
// 创建屏幕中心的窗口
var tool = Toolkit.getDefaultToolkit();
var screen = tool.getScreenSize();
int x = screen.width - DEFAULT_WIDTH;
int y = screen.height - DEFAULT_HEIGHT;
setBounds(x/2,y/2,DEFAULT_WIDTH,DEFAULT_HEIGHT);
10.2.2 在组建中显示信息
JFrame的结构相当复杂。在图10-4中给出了JFrame的组成。可以看到,在JFrame中有四层窗格。其中的根窗格、层级窗格和玻璃窗格人们并不太关心;它们要用来组织菜单栏和内容窗格以及实现观感。
Swing程序员最关心的是内容窗格(contentpane)。添加到窗体的所有组件都会自动添加到内容窗格中:
在这里,我们打算将一个组件添加到窗体中,消息将绘制在这个组件上。要在一个组件上绘制,需要定义一个扩展JComponent的类,并覆盖其中的paintComponent方法。 paintComponent方法有一个Graphics类型的参数,Graphics对象保存着用于绘制图像和文本的一组设置,例如,你设置的字体或当前的颜色。在Java中,所有的绘制都必须通过Graphics对象完成,其中包含了绘制图案、图像和文本的方法。 可以如下创建一个能够进行绘制的组件:
class MyComponent extends JComponent{
public void paintComponent(Graphics g){
code for drawing
}
}
无论何种原因,只要窗口需要重新绘制,事件处理器就会通知组件,从而引发执行所有组件的 paintComponent方法。 绝对不要自己调用paintComponent方法。只要应用的某个部分需要重新绘制,就会自动调用这个方法,不要人为地干预这个自动的处理过程。 哪些动作会触发这个自动响应呢? 例如,用户扩大窗口时,或者极小化窗口后又恢复窗口的大小时,就会引发绘制。如果用户弹出了另外一个窗口,并且这个窗口覆盖了一个已有的窗口,然后让这个上层窗口消失,此时被覆盖的那个窗口已被破坏,需要重新绘制(图形系统不保存下层的像素)。当然,窗口第一次显示时需要处理一些代码,指定如何绘制以及在哪里绘制初始的元素。