比直接请求关注初始组件更有趣的是自定义 FocusTraversalPolicy 实现了查找/配置默认值的通用机制。基本概念已经在其 api 中可用,具有返回初始/默认组件的方法,f.i.:
公共抽象组件getDefaultComponent(Container aContainer)
将默认组件返回为焦点。该组件将是
向下遍历到新的焦点遍历时首先接收焦点
循环植根于 aContainer。
默认实现返回循环中的第一个组件,自定义实现可能会寻找自定义指标,f.i。客户财产。简单的用法会将该策略设置为在其 contentPane 中有标记的框架,例如:
// a custom policy checking for the property
FocusTraversalPolicy policy = new LayoutFocusTraversalPolicy() {
@Override
public Component getDefaultComponent(Container aContainer) {
if (aContainer instanceof JComponent) {
JComponent parent = (JComponent) aContainer;
if (parent.getClientProperty("defaultFocus") instanceof Component) {
return (Component) parent.getClientProperty("defaultFocus");
}
}
if (aContainer instanceof RootPaneContainer) {
RootPaneContainer root = (RootPaneContainer) aContainer;
JComponent parent = (JComponent) root.getContentPane();
if (parent.getClientProperty("defaultFocus") instanceof Component) {
return (Component) parent.getClientProperty("defaultFocus");
}
}
return super.getDefaultComponent(aContainer);
}
};
JFrame frame = ... // create and fill
JTextField field = new JTextField(20);
frame.add(field, BorderLayout.SOUTH);
// set the client property
((JComponent) frame.getContentPane()).putClientProperty("defaultFocus", field);
// set the custom policy
frame.setFocusTraversalPolicy(policy);
更优雅一点的是默认使用这样的策略 - 可以通过将其安装为 KeyboardFocusManager 的默认策略来实现。有了它,使用将被简化为
- 在应用程序生命周期的早期安装一次自定义默认值
- 标记初始焦点组件(无需手动设置框架的策略)
代码:
// very early in the application code
initializeDefaultFocusTraversalPolicy();
// default in any frame
JFrame frame = ... // create and fill
JTextField field = new JTextField(20);
frame.add(field, BorderLayout.SOUTH);
// set the client property
((JComponent) frame.getContentPane())
.putClientProperty(DelegatingFocusTraversalPolicy.DEFAULT_FOCUS_KEY, field);
Swing 不会是 Swing 它没有捕获,但是:
- 框架的默认策略是在其实例化时在内部设置的 - 因此在创建框架之前配置keyboardFocusManager 也就不足为奇了
- UIManager 对管理器的默认值有自己的想法,实际上在创建第一个帧时无条件配置它(这会覆盖我们自己的默认值)
出路是创建一个dummy frame,然后设置策略:
public static void initializeDefaultFocusTraversalPolicy() {
// a custom default policy is overwritten by the UIManager the
// very first time it sees a top-level container created
// it does so unconditionally depending on a flag in LAFState
// see UIManager.maybeInitializeFocusPolicy
// so we tricks him into doing it for a fake frame
new JFrame();
// and set our custom default afterwards
FocusTraversalPolicy p = KeyboardFocusManager.getCurrentKeyboardFocusManager()
.getDefaultFocusTraversalPolicy();
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.setDefaultFocusTraversalPolicy(new CustomFocusTraversalPolicy(p));
}
/**
* Basically the same custom policy as the ad-hoc above, just with
* delegating
*/
public static class CustomFocusTraversalPolicy extends FocusTraversalPolicy {
public static final String DEFAULT_FOCUS_KEY = "defaultFocus";
private FocusTraversalPolicy delegate;
public CustomFocusTraversalPolicy(FocusTraversalPolicy delegate) {
this.delegate = Contract.asNotNull(delegate, "the delegate must not be null");
}
@Override
public Component getDefaultComponent(Container container) {
if (container instanceof JComponent) {
JComponent parent = (JComponent) container;
if (parent.getClientProperty(DEFAULT_FOCUS_KEY) instanceof Component) {
return (Component) parent.getClientProperty(DEFAULT_FOCUS_KEY);
}
}
if (container instanceof RootPaneContainer) {
RootPaneContainer root = (RootPaneContainer) container;
JComponent parent = (JComponent) root.getContentPane();
if (parent.getClientProperty(DEFAULT_FOCUS_KEY) instanceof Component) {
return (Component) parent.getClientProperty(DEFAULT_FOCUS_KEY);
}
}
return delegate.getDefaultComponent(container);
}
@Override
public Component getComponentAfter(Container aContainer,
Component aComponent) {
return delegate.getComponentAfter(aContainer, aComponent);
}
@Override
public Component getComponentBefore(Container aContainer,
Component aComponent) {
return delegate.getComponentBefore(aContainer, aComponent);
}
@Override
public Component getFirstComponent(Container aContainer) {
return delegate.getFirstComponent(aContainer);
}
@Override
public Component getLastComponent(Container aContainer) {
return delegate.getLastComponent(aContainer);
}
}