Java Swing如何让JFrame获得焦点

3

我正在开发一个Java Swing应用程序。我希望实现以下效果:按快捷键显示应用程序窗口,再次按快捷键或单击其他地方可隐藏应用程序。

我使用jkeymaster来注册全局快捷键事件监听器,并且它运行良好。但是当用户聚焦于其他窗口(如Chrome、Office等)时,焦点窗口将是其他应用程序。然后如果用户使用快捷键,我的应用程序窗口仍将显示出来,但无法获得焦点。有人能帮我解决这个问题吗?以下是我的代码片段。

    Provider provider = Provider.getCurrentProvider(true);
    // bind shortcut
    provider.register(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), x -> {
        homePage.setVisible(!homePage.isVisible());
        if(homePage.isVisible()){
            // TODO: 2020/10/1 request focus here
            homePage.requestFocusInWindow();
            homePage.requestFocus();

            KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
            focusManager.clearGlobalFocusOwner();

            log.info("hasFocus? " + homePage.hasFocus());
            log.info("isActive? " + homePage.isActive());
        }
    });

我的尝试:

  1. 使用requestFocus和requestFocusInWindow方法,但没有效果
  2. 我阅读了requestFocus的源代码,它指出
     * This method cannot be used to set the focus owner to no Component at
     * all. Use <code>KeyboardFocusManager.clearGlobalFocusOwner()</code>
     * instead.

我尝试了focusManager.clearGlobalFocusOwner(),但仍然无效。


也许 grabFocus() 会起作用? - camickr
@camickr 谢谢您的建议,但JFrame没有grabFocus方法。我尝试了homePage.getRootPane().grabFocus(),但仍然不起作用。 - recluse
2个回答

3

通常情况下,当你想要在父容器(如JPanelJFrame等)中获取焦点时,可调用其中一个子组件的requestFocusInWindow()方法来请求获取焦点。

建议您不要在JFrame中请求焦点。最好在其组件中调用requestFocusInWindow()

就用户体验而言,应该将焦点返回到上一次获得焦点的组件上。为了得到JFrame中处于活动状态的组件,需要使用jframe.getFocusOwner()方法。但是,如果JFrame处于“最小化”状态或者被置于后台,因为它没有焦点,getFocusOwner方法会返回null

因此,在处理全局按键事件之前,为了查找最后一个获得焦点的组件,可以同时注册一个AWT全局焦点事件监听器:

Toolkit.getDefaultToolkit().addAWTEventListener(e -> {
    if (e.getID() == FocusEvent.FOCUS_LOST) {
        if (e.getSource() instanceof Component) {
            lastFocusedComponent = (Component) e.getSource();
        }
    }
}, FocusEvent.FOCUS_EVENT_MASK);

每次焦点改变时都会触发监听器。因此,您可以安全地获取具有焦点的实际上一个组件,从而消除了null的可能性,因为它是触发事件的那个组件。
在此之后,您所需要做的就是调用requestFocusInWindow方法。
provider.register(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0, 0), e -> {
    frame.setVisible(!frame.isVisible());
    if (frame.isVisible())
        SwingUtilities.invokeLater(lastFocusedComponent::requestFocusInWindow);
});

一个完整的示例(您可以自行确认焦点已恢复):
public class FocusExample {
    private static Component lastFocusedComponent;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {

            JFrame frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            JTextField leftField = new JTextField(10);
            JTextField rightField = new JTextField(10);

            frame.setLayout(new FlowLayout());
            frame.add(leftField);
            frame.add(rightField);

            Toolkit.getDefaultToolkit().addAWTEventListener(e -> {
                if (e.getID() == FocusEvent.FOCUS_LOST) {
                    if (e.getSource() instanceof Component) {
                        lastFocusedComponent = (Component) e.getSource();
                    }
                }
            }, FocusEvent.FOCUS_EVENT_MASK);

            Provider provider = Provider.getCurrentProvider(true);

            provider.register(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0, 0), e -> {
                frame.setVisible(!frame.isVisible());
                if (frame.isVisible())
                    SwingUtilities.invokeLater(lastFocusedComponent::requestFocusInWindow);
            });

            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        });

    }
}

谢谢您的建议。实际上它确实可以达到您所说的结果,但这不是我想要的。我希望JFrame能够获取焦点,因为另一个需求是“当JFrame失去焦点时,JFrame应该自动隐藏”。如果JFrame在显示时无法获得焦点,我将无法监视其focuslost事件。我认为在不同平台上的实现可能会有所不同。您能否给我一些进一步的建议? - recluse

-1
我尝试了一个丑陋的解决方案,使用机器人模拟用户点击来获得焦点。当调用点击方法时,光标会短暂消失。但实际上它确实有效(成功获得焦点)。
我所做的是:
1.
RobotTool.click((int)windowLocation.getX(), (int)windowLocation.getY());
public class RobotTool {
    private static Robot robot = Singleton.get(Robot.class);

    public static void click(int x, int y){

        Point mouseInitPosition = MouseInfo.getPointerInfo().getLocation();
        int mask = InputEvent.BUTTON1_MASK;
        robot.mouseMove(x, y);
        try{
            // wait 100ms to avoid the robot action out of order
            Thread.sleep(100);
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }
        robot.mousePress(mask);
        robot.mouseRelease(mask);
        robot.mouseMove((int)mouseInitPosition.getX(), (int)mouseInitPosition.getY());
    }
}

RobotTool 是一个 Java 类吗?我对它不熟悉。 - Abra
@Abra 对不起,那是我的自定义工具。我刚刚更新了代码。 - recluse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接