简介
开放源码 Eclipse 项目是 java 领域中最有趣的新近开发项目之一。Eclipse 把自己描述成“一种通用的工具平台 — 开放的可扩展 IDE,可用于任何用途且没有非凡之处”。它的两个主要组件是名为 SWT 的图形库和与其匹配的名为 JFace 的实用程序框架。
SWT 是一个窗口构件集和图形库,它集成于本机窗口系统但有独立于 OS 的 API。
JFace 是用 SWT 实现的 UI 工具箱,它简化了常见的 UI 编程任务。JFace 在其 API 和实现方面都是独立于窗口系统的,它旨在使用 SWT 而不隐藏它。图 1 演示了 Eclipse、JFace 和 SWT 之间的关系。
图 1. Eclipse Workbench、JFace 和 SWT
Hello, World
让我们从我能想到的最简单的 JFace 程序开始,逐步扩充它,将其构建为最常见的“Hello, World”程序。
清单 1. Hello(版本 1)
import org.eclipse.jface.window.*;
import org.eclipse.swt.widgets.*;
public class Hello
{
public static void main(String[] args)
{
applicationWindow w = new ApplicationWindow(null);
w.setBlockOnOpen(true);
w.open();
Display.getCurrent().dispose();
}
}
这里我们创建了一个名为 Hello 的类,其中 main 方法仅仅创建了一个 ApplicationWindow,然后打开它。setBlockOnOpen() 使 open() 阻塞,直到窗口关闭为止。在窗口已关闭之后,我们获取当前的 Display 并除去它。这会释放在操作系统中用到的资源。当您运行该程序时,您会看到类似图 2 的窗口:
图 2. Hello(版本 2)
就是如此。它甚至没有说“Hello, World”。在修正它之前,让我们把话题转到 JFace 窗口。
JFace 应用程序窗口
窗口是顶级窗口(换句话说,由 OS 窗口治理器治理的窗口)的 JFace 类。JFace 窗口实际上不是顶级窗口的 GUI 对象(SWT 已经提供了一个,名为 Shell)。相反,JFace 窗口是助手对象,它知道对应的 SWT Shell 对象,并提供代码来帮助创建/编辑它,以及侦听它的事件等。图 3 演示了您的代码、JFace 和 SWT 之间的关系。
图 3. 您的代码、JFace Window 和 SWT Shell 之间的关系
事实上,这一模型是理解 JFace 如何工作的要害。它并不真的是 SWT 之上的层,而且它没有试图向您隐藏 SWT。相反,JFace 意识到有几种使用 SWT 的常用模式,而且它提供了一些实用程序代码,以帮助您更方便地对这些模式编程。为了做到这一点,JFace 提供可使用的对象,或提供可将其子类化的类(有时它两者都提供)。
尽管我们仅仅直接使用了一个 ApplicationWindow,但实际上它们被设计为可以子类化也可以加入特定行为。它们有现成的菜单栏、工具栏、供您插入特定于应用程序的内容的区域和状态栏 — 全都是可选的。图 4 用 JFace File EXPlorer 示例本身演示了这些区域。
图 4. 应用程序窗口的各个部分
让我们改进 Hello,使它成为 ApplicationWindow 的子类。更改的行在清单 2 中突出显示。
清单 2. Hello(版本 2)
import org.eclipse.jface.window.*;
import org.eclipse.swt.widgets.*;
public class Hello extends ApplicationWindow
{
public Hello()
{
super(null);
}
public static void main(String[] args)
{
Hello w = new Hello();
w.setBlockOnOpen(true);
w.open();
Display.getCurrent().dispose();
}
}
您编写的构造函数必须调用超类构造函数(如往常一样)。让我们暂时不考虑该构造函数的参数。运行该程序的结果与前一个程序没有任何不同。缺省情况下,程序不会为我们显示任何装饰性的东西。我们的程序要创建一个带有文本“Hello, World”的按钮。这个按钮要显示在内容(Contents)区域。要做到这一点,我们必须实现 Control createContents(Composite parent) 方法。
ApplicationWindow 将在所有其它窗口构件已经创建之后但窗口在屏幕上显示之前调用该方法。参数 parent 是代表内容区域的复合窗口构件。这里的想法是您创建一个复合窗口构件,将其添加到 parent,然后添加您的窗口构件,并返回您创建的复合窗口构件。图 5 演示了实例层次结构。
图 5. Application Window 的实例层次结构
我们的内容目前非常简单:parent 下的单一按钮,如清单 3 所示。
清单 3. Hello(版本 3)
import org.eclipse.jface.window.*;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
public class Hello extends ApplicationWindow
{
public Hello()
{
super(null);
}
PRotected Control createContents(Composite parent)
{
Button b = new Button(parent, SWT.PUSH);
b.setText("Hello World");
return b;
}
public static void main(String[] args)
{
Hello w = new Hello();
w.setBlockOnOpen(true);
w.open();
Display.getCurrent().dispose();
}
}
结果是图 6
图 6. Hello(版本 3)
这就是我们要实现的。我们使用 JFace 创建的第一个“Hello, World”程序:包含单一按钮的窗口。现在让我们继续讨论文件资源治理器这一话题。首先,我们将创建显示文件夹层次结构的树查看器。使用 TreeViewer 和 ApplicationWindow 一样,TreeViewer 不是真正的 SWT 窗口构件,它也没有打算向您隐藏 SWT 窗口构件。它使用 SWT 树窗口构件来显示各项,并且还使用许多其它对象来协助它。不象 ApplicationWindow,JFace TreeViewer 并不旨在被子类化。
这里的想法是 TreeViewer 知道要显示的树的根元素。当然,您必须告诉它那个对象是什么:TreeViewer: void setInput(Object rootElement)
为了开始显示,树查看器向根元素请求子元素并显示它们。然后,当用户展开其中的一个子元素时,树查看器向该节点请求子元素,以此类推。实际上,并不完全是那样。TreeViewer 并不直接使用域对象 — 而是使用另一个名为 ContentProvider 的对象,这个对象才使用域对象,如图 7 所示。
图 7. TreeViewer、ContentProvider 和域对象
当然,您必须实现 ContentProvider。对于 TreeViewer,您的类必须实现 ITreeContentProvider 接口。实现 TreeContentProvider
有六个方法需要实现。实际上我们不用做全部的工作,只需实现其中的三个就行,因此,本着“即时满足(instant gratification)”的精神,让我们暂时只考虑那几个方法吧。下面的代码演示了树查看器如何向内容提供程序请求正好位于根元素下的顶级元素:
ITreeContentProvider: public Object[] getElements(Object element)
随后,每当它需要特定元素的子元素时,它就使用以下方法:
ITreeContentProvider: public Object[] getChildren(Object element)
为了知道某个节点是否有子元素(有的话会将小加号放到它旁边),树查看器只需请求该节点的子元素,然后会询问有多少子元素。万一您的代码需要更快捷的方法来做到这一点,则您必须实现另一个方法:
public boolean hasChildren(Object element)
正如您所见,内容提供程序不持有对任何域对象的引用。持有对这些域对象的引用的是树查看器本身,它把这些域对象作为参数传递给内容提供程序中的各个方法。在我们的例子中,节点是 File 对象。为获取子元素,我们使用 listFiles()。我们必须记得要检查 listFiles() 是否返回 null,然后使其变成空数组。为了获取顶级元素(正好位于根元素之下),我们只需重用 getChildren() 方法。
getParent() 方法被用来实现 reveal(Object element) 方法,后者使树查看器滚动其 SWT 树窗口构件,以便显示树中特定的节点。问题是:假如此刻实际上并没有显示那个节点,那么应该在哪里显示它?JFace 会寻找其父元素,以及父元素的父元素等等,直到它达到已显示的节点,然后它再次回头寻找,直到目标节点已显示。
hasChildren() 方法只是做了显而易见(未优化)的事情,最后我们有了清单 4 中所示的代码。
清单 4. FileTreeContentProvider(版本 1)
import java.io.*;
import java.util.*;
import org.eclipse.jface.viewers.*;
public class FileTreeContentProvider implements ITreeContentProvider
{
public Object[] getChildren(Object element)
{
Ob