对话框关闭后字段焦点错误

Wrong field focussed after dialog closes

我有一个应用程序,如果用户操作需要一定的时间,则会显示一个对话框,表明界面正在处理该操作。通常当操作完成后,对话框关闭,用户可以继续做他之前正在做的事情。这似乎几乎总是有效,但有时焦点会从焦点组件转移到焦点链中位于其后 2 位的组件。

我无法创建一个小的示例程序来展示到底发生了什么,但我已经能够将问题调试到我希望有人能帮助我的地步。

似乎发生的事情是这样的:

  1. A sun.awt.TimedWindowEvent[WINDOW_GAINED_FOCUS, {main window}...] 事件由对话框处理并传递给 DefaultKeyboardFocusManager (DKFM),它将尝试恢复焦点到主要 window.
  2. DKFM 告诉 MostRecentFocusOwner 自己聚焦
  3. 该组件将自己注册为 MostRecentFocusOwner 并尝试通过调用 peer.requestFocus.
  4. 来实际聚焦自己
  5. *在 WComponentPeer 中,框架被聚焦但由于某种原因它 returns false
  6. 该组件发现它没有获得焦点,所以它 return 也是错误的
  7. DKFM 发现该组件未获得焦点,因此它尝试关注下一个组件
  8. 步骤 3-5
  9. 另一个焦点事件触发,再次触发第 1-7 步,但因为第 3 步将新组件注册为 MostRecentFocusOwner,所以循环遍历 6 的组件和下一个组件
  10. 另一个焦点事件触发了第 1 步中的逻辑,但现在第 4 步 return 为真。显示了主要 window,现在聚焦了错误的组件。

对于 4*:在它工作和不工作的情况下似乎有所不同的是 WComponentPeer 在对 parentWindow 的焦点请求之后进行检查,在正确的情况下 parentWindow.isFocused() 为真,在失败的情况下 parentWindow.isFocused() 将 return 为假。当我打开焦点日志记录时显示的日志记录是 "rejectFocusRequestHelper [...] Waiting for asynchronous processing of the request"。
这似乎表明 WComponentPeer 知道必须异步处理焦点请求的可能性,但 DKFM 不知道。

相关stacktrace如下:

KeyboardFocusManager.setMostRecentFocusOwner(Window, Component) line: 1814  
KeyboardFocusManager.setMostRecentFocusOwner(Component) line: 1801  
TheComponent(Component).requestFocusHelper(boolean, boolean, CausedFocusEvent$Cause) line: 7618 (3)
TheComponent(Component).requestFocusInWindow(CausedFocusEvent$Cause) line: 7533 
DefaultKeyboardFocusManager.doRestoreFocus(Component, Component, boolean) line: 172 (6) 
DefaultKeyboardFocusManager.restoreFocus(Window, Component, boolean) line: 151  (2)
DefaultKeyboardFocusManager.restoreFocus(WindowEvent) line: 134 
DefaultKeyboardFocusManager.dispatchEvent(AWTEvent) line: 302   
ProgressDialog(Component).dispatchEventImpl(AWTEvent) line: 4731 (1)    

所以我的问题是,我看到的是 Java 错误吗?

此外,文档提到您不应该假设更改焦点是同步完成的,但它没有解释何时同步以及何时不同步,所以是什么触发了 Java 中的焦点系统来处理某些异步关注请求而同步关注其他请求?

编辑:它也可能是一个事件排序的东西。 在正确的情况下查看 KeyboardFocusManager 中的 focusedWindow 时,它是这样的:

  1. 主框架 -> 空
  2. 空 -> 对话框
  3. 焦点改变(我希望对话)
  4. 对话框 -> 空
  5. null -> 主框架
  6. 焦点变化(到 lastFocussedComponent

在失败的情况下是这样的:

  1. 主框架 -> 空
  2. 4x 焦点变化(到主框架上的下一个可聚焦组件)
  3. null -> 主框架

似乎对话框在实际显示之前被删除了。将主框架清除为活动 window 的事件在这两种情况下都是 WINDOW_FOCUS_LOST TimedWindowEvent,对话框与之相反。

据我所知,这是由 Java 错误引起的。因为我注意到对话框从未成为焦点,所以我能够在一个小的 Java 应用程序中重现它:

public class TestFocusStuff extends JFrame implements ActionListener
{
  private final MyDialog mDialog = new MyDialog();

  public static void main(String[] args)
  {
    TestFocusStuff tfs = new TestFocusStuff();
    tfs.setVisible(true);
  }

  public TestFocusStuff()
  {
    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

    Container c = getContentPane();
    c.setLayout(new FlowLayout());
    for (int i = 0; i < 10; i++)
    {
      JTextField tf = new JTextField(10);
      c.add(tf);
      tf.addActionListener(this);
    }
    pack();
  }

  @Override
  public void actionPerformed(ActionEvent e)
  {
    mDialog.showQuickly();
  }

  private static class MyDialog extends JDialog
  {
    public void showQuickly()
    {
      setVisible(true);
      setVisible(false);
    }
  }
}

在任何文本字段中按 Enter 键将弹出对话框并立即关闭。焦点将像按 Tab 键两次一样改变。

我希望如果我添加一个焦点侦听器并仅在收到该事件后关闭对话框,这个问题应该得到解决。

编辑:我现在可以确认在关闭对话框之前等待 WindowFocusEvent.windowGainedFocus 确实有助于防止错误。