基于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() 方法时,您应该在脑海里有第一/最后组件这样一个概念,但是初始组件未必就是第一组件,这些方法允许您显式地设置哪个组件是第一组件,哪个组件又是最后组件。
该系统包括5种内建的焦点遍历策略,其中后3种策略是特定于Swing的:
-
ContainerOrderFocusTraversalPolicy -
DefaultFocusTraversalPolicy -
InternalFrameFocusTraversalPolicy -
SortingFocusTraversalPolicy -
LayoutFocusTraversalPolicy
ContainerOrderFocusTraversalPolicy 通过使用容器固有的组件排序方式(它的
getComponents() 数组)来工作,这个顺序通常就是将组件添加到容器中时的顺序――除非您是用
add() 方法将组件添加到特定的位置的。除了
FocusTraversalPolicy 的这6个方法,
ContainerOrderFocusTraversalPolicy 还支持隐式地将焦点向下转移到一个容器中,并提供了一个
accept() 方法,您可以重载这个方法,以便定义对于一个要获得焦点的组件来说什么是可接受的选择。
DefaultFocusTraversalPolicy 的工作方式与
ContainerOrderFocusTraversalPolicy 非常相似,只有一点不同――它依赖于AWT的对等组件来检查一个组件是否能够获得焦点。对等组件的可聚焦性(focusability)是依赖于实现的,因此
您又要回过头来面对现有焦点管理子系统所存在的问题。而当您使用Swing
GUI组件时,就不必担心对等组件的可聚焦性。
InternalFrameFocusTraversalPolicy 是为
JInternalFrame 而提供的策略。假设
JInternalFrame 不是重量级的窗口,那么当它初次显示时需要处理一些特有的行为。
SortingFocusTraversalPolicy 允许您定义一个
Comparator 来控制焦点遍历策略。
这里是创建一个
comparator 来处理排序方式,而不是使用大块的 if-else语句块或者
Map 查找
。
还有一个专门的
SortingFocusTraversalPolicy ,它提供了一个默认的Comparator:
LayoutFocusTraversalPolicy 。这里,组件是按照大小、位置和方位来排序的,而不是按照
getComponents() 数组中的顺序排序的。
这里也提供了第六个策略:
LegacyGlueFocusTraversalPolicy ,但它不是公共的。当旧式的代码使用像
JComponent.setNextFocusableComponent() 这样的方法时,就会使用这个策略。
接下来,我们将创建一个简单的实例,用这个实例来演示对我们自己的焦点遍历策略的使用。我们将创建一个
FocusTraversalPolicy 实现,该实现带有一个数组,并按照数组中组件的顺序来决定焦点遍历的顺序。示例窗口采用的是典型的
BorderLayout 样式,在这种样式下,组件按照North、South、East、West和Center来定位和显示。遍历顺序是先按顺时针方向访问外围的组件,然后再访问中间的组件。图 1 展示了这个程序:
图 1. 一个BorderLayout窗口
清单 1 显示了完整的顺时针遍历清单:
清单 1. 顺时针遍历
import java.awt.*;
import javax.swing.*;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class BorderFocus {
public static void main(String args[]) {
JFrame frame = new JFrame("Focus Cycling");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane = frame.getContentPane();
JButton north = new JButton("North");
contentPane.add(north, BorderLayout.NORTH);
JButton south = new JButton("South");
contentPane.add(south, BorderLayout.SOUTH);
JButton east = new JButton("East");
contentPane.add(east, BorderLayout.EAST);
JButton west = new JButton("West");
contentPane.add(west, BorderLayout.WEST);
JButton center = new JButton("Center");
contentPane.add(center, BorderLayout.CENTER);
contentPane.setFocusable(false);
final Component order[] =
new Component[] {north, east, south, west, center};
FocusTraversalPolicy policy = new FocusTraversalPolicy() {
List list = Arrays.asList(order);
public Component getFirstComponent(Container focusCycleRoot) {
return order[0];
}
public Component getLastComponent(Container focusCycleRoot) {
return order[order.length-1];
}
public Component getComponentAfter(Container focusCycleRoot,
Component aComponent) {
int index = list.indexOf(aComponent);
return order[(index + 1) % order.length];
}
public Component getComponentBefore(Container focusCycleRoot,
Component aComponent) {
int index = list.indexOf(aComponent);
return order[(index - 1 + order.length) % order.length];
}
public Component getDefaultComponent(Container focusCycleRoot) {
return order[0];
}
};
frame.setFocusTraversalPolicy(policy);
frame.pack();
frame.show();
}
}
|
作为额外的练习,您可能想重写这个实例,以便亲身体验对
SortingFocusTraversalPolicy 的使用。别忘了分别试试对容器进行前向遍历和反向遍历。
Java平台1.4版中的焦点子系统修正了早期版本中存在的大量与焦点相关的问题。
FocusTraversalPolicy 只是其中的一个改进。别忘了阅读
参考资料部分中
引用的Focus规范说明书,以找到该规范其他部分的内容,包括新的
KeyboardFocusManager ,
requestFocus() 和
requestFocusInWindow 之间的不同,以及如何调整一个组件的焦点遍历键。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 参与有关本文的
论坛(您也可以单击文章顶部或底部的
讨论来访问论坛)
。
- 参见
AWT
焦点子系统规范说明书,学习全部细节。
- Bertrand Portier 在他的文章“
Java
2 得到一个新的焦点子系统”(
developerWorks, 2001年10月)中更详细地探讨了焦点子系统。
- 阅读
FocusTraversalPolicy类 javadoc 。
- 学习“
利用Swing开发可访问的GUI” (
developerWorks,
2002年12月)中的技巧,别忘了处理焦点问题。
- 从Paul Monday的教程“
Java事件传输技术” (
developerWorks,
2002年2月)中学习更多有关事件处理的细节。
- 细读John Zukowski的
全套
Merlin
的魔力
。
- 您或许还想查看
developerWorks
IBM 开发者解决方案
,以获得更多有关焦点的专题。
- 在
developerWorks
Java 技术专区
,可以找到数百篇有关Java编程各方面的文章。
John Zukowski是 JZ Ventures公司的顾问,为该公司提供战略性Java的咨询,并致力研究用于开发下一代移动电话平台的 SavaJe 技术。他的最新著作有 Java Collections (APress, 2001年4月) 和 Definitive Guide to Swing for Java 2, Second Edition (Apress, 2000年1月)。可以通过 jaz@zukowski.net与他联系。