基于Swing的GUI还有一些遗留问题,包括如何管理焦点(哪个组件有接收键盘输入的优先权),如何判断哪个组件拥有焦点,以及如何将焦点从一个组件遍历到下一个组件。由于Swing建立在抽象窗口工具包(AWT)之上,对组件焦点的管理便依赖于AWT中的底层焦点管理。Java平台过去的版本依赖于本地的窗口管理器来协助进行焦点管理,所以尽管有些开发者会认为焦点控制是在他们的应用程序内部进行的,而实际情况并非如此。由于对底层的本地焦点系统的依赖性,因而出现了许多平台不相容的现象。
至于Merlin,它为您提供了一个全新的、AWT级的焦点子系统。这个子系统有其优点,也有其缺点。这种新模型的出发点是创建一种能够跨平台工作的系统,它带有一个集中式的 KeyboardFocusManager ,用以管理活动的并且拥有焦点的窗口,以及当前焦点的属主。缺点是,它与前面的版本之间存在一些不兼容性,从而导致有些程序在较新的版本中不能正常运行。作为一个开发者,当您创建任何新程序时,您需要清楚新的焦点遍历方式。
新的焦点子系统相当大,在本期话题中,我们只关注其中的一项新特性―― FocusTraversalPolicy ――并向您展示如何管理单个容器中的焦点遍历。要获得关于其他特性的信息,参见 参考资料以链接到Sun的文档以及其他一些重要的指南。
什么,没有接口?
我们首先来看一下 FocusTraversalPolicy 类。是的,它 是一个类,而不是一个接口。不过,它是一个抽象类,因而需要被细分类。FocusTraversalPolicy 类可以控制在一个特定的焦点循环根中的焦点遍历顺序。焦点循环根是一种容器,它的 focusCycleRoot 属性被设置为 true 。在默认情况下,窗口和框架被设置为 true ,其他的容器则被设置为 false ,不过它们也可以被设置为 true 。将属性设置为 true 意味着当焦点来回转移时,这个焦点将一直呆在焦点循环根内的一个循环组件之中。
FocusTraversalPolicy 类由6个方法组成:
getDefaultComponent(Container focusCycleRoot)
getInitialComponent(Window window)
getComponentBefore(Container focusCycleRoot, Component aComponent)
getComponentAfter(Container focusCycleRoot, Component aComponent)
getFirstComponent(Container focusCycleRoot)
getLastComponent(Container focusCycleRoot)
所有这6个方法都返回一个 Component 对象。在这6个方法之中,有5个方法是抽象的,只有 getInitialComponent() 是具体的方法。
顾名思义, getDefaultComponent() 方法返回默认的组件,当相关的焦点循环根获得焦点的时候,这个默认组件将获得焦点。想象一下沿着某个容器进行焦点切换以及将焦点切换到那个容器里面的一个容器中的情景。这种情况叫做 向下焦点循环(down focus cycle)。当焦点进入那个子容器时, getDefaultComponent() 需要返回应该获得焦点的初始组件。
getInitialComponent() 方法返回当一个窗口第一次显示时应该获得焦点的那个初始组件。在默认情况下,该方法只返回这个窗口的默认组件――即调用 getDefaultComponent() 方法时返回的结果。
getComponentBefore() 和 getComponentAfter() 方法是成对的。根据特定的焦点循环根(比如容器)中的某个组件,这两个方法将返回该组件之前或者之后的一个组件。通常情况下,您通过按 Shift+Tab键反向移动到前一个组件,或者按 Tab键前向移动到下一个组件,但是,不同的环境可能允许使用不同的键序列来利用键盘移动焦点。通常情况下,这些方法中的代码都有一个大型的if-else语句块或者一个 Map 查找。
getFirstComponent() 和 getLastComponent() 方法也是成对的。虽然在编写 getFirstComponent() 和 getLastComponent() 方法时,您应该在脑海里有第一/最后组件这样一个概念,但是初始组件未必就是第一组件,这些方法允许您显式地设置哪个组件是第一组件,哪个组件又是最后组件。