最新的 Java 标准版,1.4 发行版中,Java 2 平台有了个全新的 AWT 焦点子系统,我们对此进行了独家报导。Java 语言工程师 Bertrand Portier 对新的类和方法进行了初步研究(包括基本的 KeyboardFocusManager),这些类和方法中包含该 AWT 焦点子系统。他还提供了迁移到新的 API 时调整编程工作的技巧,一个能工作代码样本示例及其它更多内容。请在讨论论坛与作者和其它读者分享您关于本文的想法。
Java 2 标准版,1.4(也称 Merlin)引入许多人们期待已久并为之雀跃的对 Java 平台的更改。其中一个就是对 AWT 焦点治理子系统的更改。这个 AWT 焦点子系统的新实现与旧的完全不同。实际上,许多代码都被完全重新编写过了,这样做在某些情况下是以牺牲向下兼容为代价的。我们认为这些措施是必需的,原因在于旧的 AWT 焦点子系统不完善。
改进了的焦点模型的中心是新的 KeyboardFocusManager 类,它由几个增加的 Swing 类和 AWT 类支持。本文的绝大部分用于让我们学习这些更改,并讨论它们会如何影响您的 Java 编程工作。在本文的结束部分,我将提供一些技巧和一个亲身实践的示例来帮助您将当前的应用程序和新的 API 相集成。
请注重,本文假定您了解与 AWT 焦点子系统以前的实现相关的使用和术语。
KeyboardFocusManager 简介
KeyboardFocusManager 类用于治理与新的 AWT 焦点子系统的焦点相关的任务。它负责活动的和已定焦的窗口以及当前的焦点所有者。它的任务是让客户机代码可以启动焦点的更改并调度与焦点相关的所有事件。
KeyboardFocusManager 给 AWT 焦点子系统带来许多新功能。其中的一些功能如下:
用 Shift-Tab 将焦点转到 tab 组的前一个组件。
跟踪由鼠标引起的焦点遍历行为。
确定当前的焦点所有者。
KeyboardFocusManager 有四个字段:
FORWARD_TRAVERSAL_KEYS:通常是 Tab(或 Ctrl-Tab)键
BACKWARD_TRAVERSAL_KEYS:通常是 Shift+Tab(或 Ctrl-Shift-Tab)
UP_CYCLE_TRAVERSAL_KEYS:无缺省值
DOWN_CYCLE_TRAVERSAL_KEYS:无缺省值
我们将在下面几节中讨论其中的一些字段。
KeyboardFocusManager 是个抽象类,我们能够用它全局的请求焦点信息。例如,KeyboardFocusManager.getFocusOwner() 返回当前的焦点所有者。DefaultKeyboardFocusManager 类对 AWT 应用程序是作为缺省提供的。您当然可以选用自己的 KeyboardFocusManager 类来替代该焦点模型。但是,倘若本机的焦点策略复杂的话,建议您还是建立 KeyboardFocusManager 或 DefaultKeyboardFocusManager 的子类吧。
AWT 焦点子系统的总体改进
AWT 焦点子系统的前一个发行版因其轻量级的或重量级的组件类型和拥有 Java 虚拟机平台的不一致行为而受挫。因为重量级的组件实施使用一个独立的本机窗口(AWT 组件),对于本机焦点系统它们是可信赖的。轻量级的(基于 Swing 的)组件在不同平台上的外观和感觉都相同。KeyboardFocusManager 解决了这种不一致,确保了轻量级和重量级组件所有和焦点相关的动作和查询都成为可能。
确定当前的焦点所有者和焦点窗口
当前的焦点所有者是新的 AWT 焦点模型中的一个要害元素。所有遍历操作都从当前的焦点所有者或从另一个被虚拟的认作焦点所有者的组件开始。每个给定时间只能有一个当前的焦点所有者。当前的焦点所有者是个已收到 FOCUS_GAINED 事件 — 但还未收到 FOCUS_LOST 事件的组件(有关这些事件的更多信息,请参阅下面的内容)。
您使用 KeyboardFocusManager.getFocusOwner() 来确定当前的焦点所有者。假如该焦点所有者未和调用线程处于同一个上下文中,那么该方法会返回空。因此,您应该在代码中使用 KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner()。这样,只有当焦点被设置成“无组件(no component)”时才会返回空。此外,该 Component 类提供了 isFocusOwner() 方法,假如该组件是焦点所有者,那么这个方法就返回真。
同样,拥有焦点的窗口就是包含当前的焦点所有者的窗口。KeyboardFocusManager.getFocusedWindow() 返回的拥有焦点的窗口和调用线程处在相同的上下文中。再说一遍,您应该使用 KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow() 来确保该拥有焦点的窗口和调用线程处在相同的上下文中。
确定焦点更改中的对立组件
一些窗口和组件的焦点事件含有相应的对立事件。这样的事件集有:
FOCUS_LOST 和 FOCUS_GAINED
WINDOW_ACTIVATED 和 WINDOW_DEACTIVATED
WINDOW_LOST_FOCUS 和 WINDOW_GET_FOCUS
当调遣其中的一个事件时,总是同时调遣了这个事件的对立事件。焦点更改的对立组件只是个获取对立事件的组件。例如,当一个组件获得了焦点时,其对立组件就是那个失去该焦点的组件。
FocusEvent.getOppositeComponent() 方法和 WindowEvent.getOppositeWindow() 方法返回焦点更改所涉及的对立组件或窗口。 上面列出的六个焦点事件和这两个方法密切相关。当一个窗口得到一个 WINDOW_DEACTIVATED 事件时,getOppositeWindow() 方法查找获取 WINDOW_ACTIVATED 事件的窗口。假如其对立组件或窗口处于一个不同的上下文,这些方法中每一个都会返回空。
通过编程来实现焦点遍历
正如 AWT 焦点子系统规范中所定义的,一个焦点遍历循环是一组被定义过的组件,组中的每个组件(组外没有组件)会在前序或后序焦点遍历中被遍历到。
焦点遍历循环中每个组件都有自己的上一个组件和下一个组件。KeyboardFocusManager 类提供了在给定的遍历循环中传递焦点,或者甚至更改焦点遍历循环的方法。
focusNextComponent() 在遍历循环中把焦点传递给下一个组件。该方法可以把另一个组件作为它的参数,然后把焦点传递给焦点遍历循环中紧接着的那个组件。 focusPreviousComponent() 对于遍历循环中前一个组件的处理与之类似。
Container 一般既起到一个焦点遍历循环的成员的作用,也起到另一个焦点遍历循环的根的作用。这让我们理解了焦点遍历层次结构的概念,从而明白了向上和向下的焦点循环的概念。 upFocusCycle() 把焦点从当前的焦点所有者向上移动一个焦点遍历循环。它可以把一个组件作为它的参数,从而把焦点向上移动一个遍历循环到该组件的遍历循环。 downFocusCycle() 的处理方法相同,但只在组件是焦点遍历循环的根的情况下使用。
在下面的代码样本中,您可以看到一个框架包含了两个组件;调用这两个组件其中之一的 upFocusCycle() 会使框架获得焦点。请注重,假如没有可以定焦的其他组件,焦点所有者就不会改变。
通过调用 KeyboardFocusManager.clearGlobalFocusOwner(),您可以把焦点所有者设置成“无组件(no component)”。这导致了当前的焦点所有者的 FOCUS_LOST 事件。直到用户交互或代码明确地设置了焦点所有权为非凡的组件,才丢弃所有的键事件。这不仅对 Java 组件是有效的,对本机级别的组件也有效。
Componet 类、Container 类和 Windows 类中的新方法
在 Merlin 的 Component 和 Container 类中,已经添加了新的与焦点相关的方法。您可以把他们作为 KeyboardFocusManager 方法的备用方法使用它们,从而设置或获取这些类的实例的焦点属性。例如, Component.isFocusable() 传达了组件是否可以定焦。所有组件缺省的返回值都为真,因为所有组件在缺省情况下都是可定焦的。这不同于前几个发行版 — 轻量级组件缺省情况下不可定焦。Component.setFocusable(boolean) 设置组件是否可定焦。
setFocusable(false) 会导致随后调用 requestFocus() 和 requestFocusInWindow() 的失败。请注重,Component 类和 Container 类的一些方法和 KeyboardFocusManager 中的一些方法有相同的功能并且是等价的。例如,Component.setFocusTraversalKeys() 可以用来覆盖一个应用程序中特定组件的遍历键,该应用程序有它自己的策略和由 KeyboardFocusManager.setDefaultFocusTraversalKeys() 定义的焦点遍历键集。
Window.setFocusableWindow(boolean) 答应您通过程序来防止窗口或它的任何子组件成为焦点所有者。所有窗口在缺省情况下都是可定焦的,但在特定情况下需要把窗口设置成不可定焦。一个明显的示例是输入法合成窗口。输入法用来输入不能由标准 102 键键盘完全插入的文本字符。很明显,您希望焦点遍历循环不包括输入法合成窗口(如下所示),因此使用 Window.setFocusableWindow(false)。
图 1. TurboLinux 中文输入法合成窗口的屏幕截图
[[The No.2 Picture.]]
新的遍历策略类
FocusTraversalPolicy 类定义了组件遍历的顺序。该类应该提供不同的方法分别确定前一个、后一个、第一个、最后一个组件及缺省组件。它负责调遣要害事件、焦点事件和焦点相关的窗口事件。 Java.awt.FocusTraversalPolicy 是所有遍历策略类的抽象类。它有下列