Java Swing如何让JFrame获得焦点

huangapple go评论89阅读模式
英文:

Java Swing how to let the JFrame gain focus

问题

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

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

Provider provider = Provider.getCurrentProvider(true);
// 绑定快捷键
provider.register(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), x -> {
    homePage.setVisible(!homePage.isVisible());
    if(homePage.isVisible()){
        // TODO: 2020/10/1 在这里请求焦点
        homePage.requestFocusInWindow();
        homePage.requestFocus();

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

        log.info("是否有焦点? " + homePage.hasFocus());
        log.info("是否活跃? " + 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(),但仍然不起作用。

英文:

I'm developing a java swing application. I wish to achieve such result: press shortcut to let the app window show up. press shortcut again or click otherwhere to let the app hide.

I use jkeymaster to register to listen the event of global shortcut and it works well.
But when the user focus on some other window, like chrome, office... the focus window will be other application. Then if the user use the shortcut. My app windows will still show up but cannot gain focus. Could anyone help me to solve it. Here's my code snippet.

    Provider provider = Provider.getCurrentProvider(true);
    // bind shortcut
    provider.register(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), x -&gt; {
        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(&quot;hasFocus? &quot; + homePage.hasFocus());
            log.info(&quot;isActive? &quot; + homePage.isActive());
        }
    });

What I have tried:

  1. requestFocus and requestFocusInWindow, doesn't work
  2. I read the source code of requestFocus. It says
     * This method cannot be used to set the focus owner to no Component at
     * all. Use &lt;code&gt;KeyboardFocusManager.clearGlobalFocusOwner()&lt;/code&gt;
     * instead.

So I tried focusManager.clearGlobalFocusOwner() but it still doesn't work.

答案1

得分: 3

通常在希望在父容器(如 JPanelJFrame 等)中获取焦点时,通过在其子组件之一中调用 requestFocusInWindow() 方法来请求焦点。

我建议您不要在 JFrame 中请求焦点。而是在其组件之一中调用 requestFocusInWindow()

现在从用户体验(UX)的角度来看,我建议将焦点返回到最后一个获得焦点的组件。为了获取 JFrame 的获得焦点的组件,我们调用 jframe.getFocusOwner() 方法。然而,如果 JFrame 处于 MINIMIZED 状态或位于后台,由于它没有焦点,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);
        });

    }
}
英文:

Usually when you want to gain focus in a parent container (such as JPanel, JFrame, etc) you request the focus by calling requestFocusInWindow() method into one of its children.

I suggest you, to not asking for focus in the JFrame. Call requestFocusInWindow() in one of its components.

Now in terms of UX, I would give the focus back to last component that was focused. In order to get the focused component of a JFrame we call jframe.getFocusOwner(). However, if the JFrame is in state MINIMIZED or is in background, since it does not have focus, getFocusOwner returns null.

So, in order to find the last component that was focused before calling your global key listener, you can register a global AWT listener for focus events as well:

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

Listener will be fired every time the focus changes. So you get the actual last component with focus safely, eliminating the chance to be null since it is the one that fires the event.

All you have to do after it, is to call requestFocusInWindow to it:

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

A full example that works (you can confirm your self that the focus is restored):

public class FocusExample {
	private static Component lastFocusedComponent;

	public static void main(String[] args) {
		SwingUtilities.invokeLater(() -&gt; {

			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 -&gt; {
				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 -&gt; {
				frame.setVisible(!frame.isVisible());
				if (frame.isVisible())
					SwingUtilities.invokeLater(lastFocusedComponent::requestFocusInWindow);
			});

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

	}
}

答案2

得分: -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{
            // 等待 100 毫秒以避免机器人动作的错序
            Thread.sleep(100);
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }
        robot.mousePress(mask);
        robot.mouseRelease(mask);
        robot.mouseMove((int)mouseInitPosition.getX(), (int)mouseInitPosition.getY());
    }
}
英文:

I tried an ugly solution using robot to simulate the user click to gain the focus. The cursor will disappear for a moment when the click method invoked. But it actually works (gain focus successfully)

What I did is:

1.

RobotTool.click((int)windowLocation.getX(), (int)windowLocation.getY());

2.

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());
    }
}

huangapple
  • 本文由 发表于 2020年10月1日 23:09:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/64158223.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定