Topic: Java 2 有了个新的焦点子系统 |
Print this page |
1.Java 2 有了个新的焦点子系统 | Copy to clipboard |
Posted by: scottding Posted on: 2003-01-23 14:01 http://www-900.ibm.com/developerWorks/cn/java/j-awtfocus/index.shtml 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)。 新的遍历策略类 FocusTraversalPolicy 类定义了组件遍历的顺序。该类应该提供不同的方法分别确定前一个、后一个、第一个、最后一个组件及缺省组件。它负责调遣关键事件、焦点事件和焦点相关的窗口事件。 Java.awt.FocusTraversalPolicy 是所有遍历策略类的抽象类。它有下列必需的方法,这些方法可以由子类覆盖: * getComponentAfter() * getComponentBefore() * getDefaultComponent() * getFirstComponent() * getLastComponent() AWT 提供了非抽象的 ContainerOrderFocusTraversalPolicy,它的方法返回的只是可见、可显示、可启用和可定焦的组件。 DefaultFocusTraversalPolicy 是 ContainerOrderFocusTraversalPolicy 的子类。它永远不会返回带有非可定焦同类事物的组件,而且,它还是所有 AWT 容器(Panel、Window 和 ScrollPane)的缺省策略类。Swing 提供了一个有趣的附加策略,即 LayoutFocusTraversalPolicy,它按组件的几何属性对组件进行排序,形成行和列。 请注意,创建您自己的焦点遍历策略是一项复杂的任务。与其从头开始设计策略,我建议您倒不如用现有的缺省策略(它应该可以满足大部分需求)、同时覆盖一些方法来适应您的特定需求。在设计 Merlin 的这个新的焦点策略类时我们就已经想到这一点了。 如果您一直在用 Swing 的 DefaultFocusManager 类,正如 API 文档中强烈推荐的,您应该改用新的 Merlin FocusTraversalPolicy 了。(关于该文档的链接,请参阅参考资料)。 迁移技巧 下面是一些简单的 — 但基本的 — 更改,这样,您将可以充分利用改进了的 AWT 焦点子系统。 使用 Component.requestFocusInWindow() 因为某些平台允许跨窗口的焦点更改而其它平台不允许,所以 Component.requestFocus() 在各个平台之间是不一致的。Component.requestFocusInWindow 通过提出任何平台都不允许跨窗口的焦点遍历解决了这个问题。此外, requestFocusInWindow() 返回一个布尔值:false,意味着该焦点请求将失败;true,则意味着该焦点请求可能通过。请注意,您不应该假定组件在调用 requestFocus() 或 requestFocusInWindow() 后收到了焦点。而应该给组件安装一个焦点侦听器,并查找 FOCUS_GAINED 事件。 使用 java.awt.KeyboardFocusManager 如果您曾经把 javax.swing.FocusManager 用于获取 Swing 组件的焦点更改的全局视图,那么,您可能已经注意到它的局限性了。 FocusManager 无法了解由鼠标激活的焦点更改,而且更重要的是,它只能处理 Swing 组件。因为 KeyboardFocusManager 是个 AWT 类,它可以了解所有组件发生的事,而不仅仅是那些属于 Swing 的组件的事。此外,KeyboardFocusManager 具有一个改进了的 API 的特征。 使用 Swing 组件 因为 AWT 遵循本机平台的需求,所以应用程序的行为在不同平台上可以不一样。 例如,让我们设想您定义了一个 myComponent 类,它继承 Component 并覆盖 isFocusTraversable() 方法以返回 false。换句话说,该类的任何实例都是不可遍历的。现在,让我们来设想某个应用程序把一个 myComponent 的实例添加到某个框架,还添加了未覆盖 isFocusTraversable() 方法的其它组件(按这个顺序)。当应用程序启动时哪个组件得到了初始的焦点呢?文档中没有明确定义。 虽然,下一个焦点可遍历的可见组件获取初始的焦点也是合理的,但这的确依赖于本机焦点管理器。您会在某些平台上看到上面描述过的行为,但是,什么组件都没有得到焦点的情况也会发生。这导致了 isFocusTraversable() 的“可以用 Tab 或 Shift-Tab 遍历”被重定义为“可以被定焦”。这只是 AWT 很大程度上依赖本机焦点管理器的许多示例之一。正如当 AWT 的行为在新的发行版中逐渐变得一致时,Swing 也将会更好的确保跨平台的一致性。 如何使用这个新的 API:一个能工作的示例 我们将用一个生动的代码样本来结束这篇文章。下面的清单中使用了文中描述的新的类和方法。 TextField 用焦点侦听者来演示焦点移动到该示例组件和从该组件移走时发生的变化。当您运行这个程序(压缩文件在参考资料中)时,您能够看到 TextField 得到了最初的焦点。最后,在调用 clearGlobalFocusOwner() 后,任何组件都没有焦点了。 把本文中的描述和讨论以及下面这个示例一起作为学习这个新的 AWT 焦点子系统的出发点。请参阅参考资料以获取相关文档和更深入的知识。 清单 1. 一个学术上的焦点管理示例 import java.awt.*; import java.awt.event.*; public class MerlinFocus extends Frame implements FocusListener { DefaultKeyboardFocusManager myFocusmgr = new DefaultKeyboardFocusManager(); Button bt; TextField tf; MerlinFocus() { bt = new Button("myButton"); tf = new TextField("myTextField"); tf.addFocusListener(this); setLayout(new GridLayout()); add(tf); add(bt); } /* Focus Listeners */ public void focusLost(FocusEvent e) { System.out.println("myTextField: Focus lost"); System.out.println("The opposite component " + "in this focus traversal is " + e.getOppositeComponent()+"."); } public void focusGained(FocusEvent e) { System.out.println("myTextField: Focus gained"); } public void printDefaultSettings(DefaultKeyboardFocusManager fm) { /* print the four default traversal keys */ System.out.println("Default keys " + "for the DefaultFocusManager class:"); System.out.println(" FORWARD_TRAVERSAL_KEYS: " + fm.getDefaultFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)); System.out.println(" BACKWARD_TRAVERSAL_KEYS: " + fm.getDefaultFocusTraversalKeys( KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)); System.out.println(" UP_CYCLE_TRAVERSAL_KEYS: " + fm.getDefaultFocusTraversalKeys( KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS)); System.out.println(" DOWN_CYCLE_TRAVERSAL_KEYS: " + fm.getDefaultFocusTraversalKeys( KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS)); } public void changeFocusOwner(DefaultKeyboardFocusManager fm) { /* programmatically change the focus owner */ System.out.println("The current focus owner is " + fm.getFocusOwner()); System.out.println("The current focus cycle root is: " + fm.getCurrentFocusCycleRoot()); System.out.println("forwarding focus to the next component..."); fm.focusNextComponent(); System.out.println("Now, the focus owner is " + fm.getFocusOwner()); System.out.println("going up one focus cycle..."); fm.upFocusCycle(); System.out.println("Now, the focus owner is " + fm.getFocusOwner()); System.out.println("going down one focus cycle..."); fm.downFocusCycle(); System.out.println("Now, the focus owner is " + fm.getFocusOwner()); System.out.println("clearing the global focus owner ..."); fm.clearGlobalFocusOwner(); System.out.println("Now, the focus owner is " + fm.getFocusOwner()); } public static void main(String args[]) { MerlinFocus mf = new MerlinFocus(); mf.setTitle("Merlin Focus"); mf.pack(); mf.setVisible(true); mf.printDefaultSettings(mf.myFocusmgr); mf.changeFocusOwner(mf.myFocusmgr); } } |
2.Re:Java 2 有了个新的焦点子系统 [Re: scottding] | Copy to clipboard |
Posted by: blueview Posted on: 2003-02-12 09:51 好东西 |
3.Re:Java 2 有了个新的焦点子系统 [Re: scottding] | Copy to clipboard |
Posted by: floater Posted on: 2003-02-12 11:38 KeyEventDispatcher and KeyboardFocusManager too. |
4.Re:Java 2 有了个新的焦点子系统 [Re: scottding] | Copy to clipboard |
Posted by: terence Posted on: 2003-02-13 12:16 Very good! Thank you for java what you are doing something! |
Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1 客服电话 18559299278 客服信箱 714923@qq.com 客服QQ 714923 |