【问题标题】:Detect focuslost with many subcomponents检测带有许多子组件的焦点丢失
【发布时间】:2021-03-29 05:43:33
【问题描述】:

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

有没有简单的方法

  • 在焦点移出任何子组件时获取 focuslost 事件
  • 或者查询面板的任何子组件是否还有焦点?

或者我是否需要在每个子组件上注册/检查焦点(-事件)? (因为这应该是一个通用的可用面板,所以我需要一个独立于子组件的特定层次结构的通用方法。)

【问题讨论】:

  • 我的猜测是,您必须监视 JFrame 中每个 Swing 组件的焦点,包括扩展 JPanel 的内部和外部。否则,您怎么知道焦点在扩展 JPanel 之外的组件上?我会在每个 JPanel 的基础上进行此监控。 JPanel getChildren 方法应该很有用。

标签: java swing user-interface focus


【解决方案1】:

根据the official tutorials,将焦点侦听器分组到多个Components 上的推荐方法是在当前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);
    }
}

根据教程:

每个涉及焦点的更改都会通知属性更改侦听器...

如何使用上面的例子:

  1. 运行它,您可以单击每个JTextField 使其获得焦点。
  2. 如果当前聚焦的JTextField 的父级有一个我们指定的Component(在这种情况下,它是所需JTextFields 的父级JPanel,命名为focusables),那么该父级变为绿色,否则变为红色。但是你显然可以实现你的逻辑而不是改变背景颜色。

上面的例子是如何工作的:

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

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

还要注意,为了简单起见,上面示例中Components 的层次结构只有一层(即所需的可聚焦Components 是focusables 面板的直接子级),但我们可以将其更改为我们想要的任何深度,只要所需的Components 在其路径上都有相同的父级(必须与非所需的父级不同)。


我能想到的唯一例外是,如果您通常有混合父级Containers,例如JPanels,其直系子级同时具有期望的Component 和非期望的Component。在这种情况下,您必须在PorpertyChangeListener 中单独检查每个Component,或者为每个所需的Component 实现FocusListener。关于如何实现FocusListener的官方教程可以找到here

【讨论】:

  • 如果您想使用FocusListener 选项,请告诉我。
  • 谢谢,这是一种非常优雅的方法!是的,我想要的组件都在一个父级中,所以这很合适。感谢您提供好的示例代码!
猜你喜欢
  • 2012-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-26
  • 1970-01-01
  • 1970-01-01
  • 2017-11-07
相关资源
最近更新 更多