使用许多子组件检测焦点丢失

Detect focuslost with many subcomponents

我想创建一个可扩展面板(为特定任务容纳多个 UI 元素)。面板可以通过用户操作展开,只要面板中的任何子组件具有焦点,它就应该保持展开状态。当用户将焦点放在面板外的某处时,它应该关闭。

有没有简单的方法

或者我是否需要 register/check 关注每个子组件? (因为这应该是一个通用的可用面板,所以我需要一种独立于子组件的特定层次结构的通用方法。)

根据 the official tutorials,将焦点侦听器分组到多个 Component 的推荐方法是在当前 KeyboardFocusManager 上安装一个 PropertyChangeListener。例如:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class Main {
    
    private static void createAndShowGUI() {
        
        final int rows = 5, cols = 5;
        final JPanel focusables = new JPanel(new GridLayout(0, cols, 10, 10));
        for (int row = 0; row < rows; ++row)
            for (int col = 0; col < cols; ++col)
                focusables.add(new JTextField("Focusable", 10));
        
        final JPanel nonFocusable = new JPanel(); //FlowLayout.
        nonFocusable.add(new JTextField("Click here to change focus"));
         
        final JPanel contents = new JPanel(new BorderLayout());
        contents.add(focusables, BorderLayout.CENTER);
        contents.add(nonFocusable, BorderLayout.LINE_END);
        
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", new PropertyChangeListener() {
            @Override
            public void propertyChange(final PropertyChangeEvent evt) {
                final Object newValue = evt.getNewValue();
                if (newValue instanceof Component
                        && SwingUtilities.isDescendingFrom((Component) newValue, focusables)) //If 'focusables' is a parent of newValue...
                    focusables.setBackground(Color.GREEN); //Then focus is back on our components...
                else
                    focusables.setBackground(Color.RED); //Else the focus is not on our components (it may even be another application).
            }
        });
        
        final JFrame frame = new JFrame("Multi focus lost");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(contents);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(Main::createAndShowGUI);
    }
}

根据教程:

The property change listener is notified of every change involving the focus...

如何使用上面的例子:

  1. 运行 它,您可以在每个 JTextField 中单击以使其成为焦点。
  2. 如果当前聚焦的 JTextField 的 parent 有一个我们指定的 Component(即所需 [= 的 parent JPanel 14=]s 在这种情况下,命名为 focusables) 然后 parent 的背景颜色变为绿色,否则为红色。但是你显然可以实现你的逻辑而不是改变背景颜色。

以上示例的工作原理:

  1. 我们在当前 KeyboardFocusManager 上为 属性 focusOwner 安装了一个 PropertyChangeListener,这将使我们仅在任何 Component应用程序更改其 focus-owner 属性(即更改焦点 Component 会触发此类事件)。我们可以安装一个 PropertyChangeListener 这样我们就可以收到各种事件的通知,但是 focusOwner 是我们唯一感兴趣的,所以我们让系统知道。有关您可以通过焦点事件跟踪的所有属性的列表,请滚动到 bottom of the tutorial.
  2. PropertyChangeListener 实现中,我们只需要检查新的焦点所有者是否为其 parent 设置了 focusables 面板。我们可以通过从焦点所有者向上遍历 Component 层次结构来检查这一点,或者只调用 SwingUtilities#isDescendingFrom 这是用于此目的的便捷方法。如果我们正在监听所有焦点事件,那么我们还必须手动过滤 属性 focusOwner(通过检查 PropertyChangeEvent#getPropertyName return)。

请注意,当我们离开应用程序时,我们想要的 Component 的焦点会丢失(但会在 return 时返回)。

另请注意,为简单起见,上例中 Component 的层次结构只有一层深(即,所需的可聚焦 Component 是 [= 的直接 children 19=] 面板),但我们可以将其更改为我们想要的任何深度,只要所需的 Component 沿着它们的路径都具有相同的 parent(必须不同于 parent non-desired 个)。


我能想到的唯一例外是,如果您通常混合使用 parent Container,例如 JPanel 直接 children一个想要的 Component 和一个 non-desired。在这种情况下,您必须在 PorpertyChangeListener 中单独检查每个 Component 或为每个所需的 Component 实施 FocusListener。关于如何实现 FocusListener 的官方教程可以在 here.

找到