在 Java 中没有焦点的情况下监听输入

Posted

技术标签:

【中文标题】在 Java 中没有焦点的情况下监听输入【英文标题】:Listening for input without focus in Java 【发布时间】:2010-10-28 10:10:50 【问题描述】:

我正在使用 Robot 类用 Java 编写一个小程序。程序接管鼠标。在调试过程中,如果它以我不希望的方式开始运行,则很难退出程序,因为我无法将鼠标移到 Eclipse 中的终止按钮上,也无法使用热键点击它是因为鼠标在另一个窗口中不断单击,而是给该窗口焦点。

我想做的只是连接一个 keylistener 以便当我点击 q 时我可以退出程序,但我知道如何做到这一点的唯一方法是制作一个窗口,并且该窗口需要捕获焦点输入。有没有办法从任何地方监听键盘或鼠标输入,而不管焦点是什么?

【问题讨论】:

【参考方案1】:

(如@MasterID 所述,并在JNativeHook's documentation 上显示本机键盘输入检测main GitHub project here),

这段代码应该足以在没有应用焦点的情况下监听任何键(按下和/或释放)

>>记得在你的项目中添加 jnativehook 库,以便能够使用它的所有实用程序。

public class yourClass implements NativeKeyListener //<-- Remember to add the jnativehook library

public void nativeKeyPressed(NativeKeyEvent e) 
    System.out.println("Key Pressed: " + NativeKeyEvent.getKeyText(e.getKeyCode()));


public void nativeKeyReleased(NativeKeyEvent e) 
        System.out.println("Key Released: " + NativeKeyEvent.getKeyText(e.getKeyCode()));


public void nativeKeyTyped(NativeKeyEvent e) 
        System.out.println("Key Typed: " + NativeKeyEvent.getKeyText(e.getKeyCode()));


public static void main(String args[])
    //Just put this into your main:
    try 
        GlobalScreen.registerNativeHook();
    
    catch (NativeHookException ex) 
        System.err.println("There was a problem registering the native hook.");
        System.err.println(ex.getMessage());
        System.exit(1);
    
    
    GlobalScreen.addNativeKeyListener(new yourClass());
    //Remember to include this^                     ^- Your class


对于这个特殊问题,使用 nativeKeyPressed 方法,如下所示:

public void nativeKeyPressed(NativeKeyEvent e) 
    System.out.println("Key Pressed: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
    if (e.getKeyCode() == NativeKeyEvent.VC_Q)
        System.exit(1);
    

请注意,JNativeHook 默认情况下会在您的控制台中显示很多您可能不想要的内容,要更改它,只需将其添加到您在 main 函数中使用的 try-catch 之前显示(这也将关闭警告和错误消息,更多信息here):

//(From here)
Logger logger = Logger.getLogger(GlobalScreen.class.getPackage().getName());
logger.setLevel(Level.OFF);
logger.setUseParentHandlers(false);
//(To there-^)
try 
    GlobalScreen.registerNativeHook();

catch (NativeHookException ex) 
    System.err.println("There was a problem registering the native hook.");
    System.err.println(ex.getMessage());
    System.exit(1);

免责声明:我知道这个问题在几年前就已经解决了,我只是希望有人发现它更容易找到/使用。

【讨论】:

【参考方案2】:

有一个库可以为您完成艰苦的工作: https://github.com/kwhat/jnativehook

【讨论】:

这个答案应该被标记为正确的。显然,Java 生态系统确实提供了一种优雅地做到这一点的简单方法。 @CollinBell 该库使用每个操作系统的本机方法来完成所述任务。还有其他库做类似的事情。我认为说 Java 生态系统确实提供了一种简单的方法来做到这一点是不准确的。有人特意制作了一个第三方库来完成这项任务,它不是任何标准 Java 包的一部分。 @Idog 库是生态系统的一部分。我主要担心的是“不用我编写任何低级代码就可以完成吗?”答案是肯定的(ish)。我知道是因为我使用了这个库(尽管底层 c 代码中有一个错误,我必须修复并提交拉取请求。)【参考方案3】:

这是解决您描述的问题的纯 Java 方法(不是 KeyListener 问题...使用机器人问题时的退出测试):

在整个测试过程中,将鼠标位置与您的测试最近设置的位置进行比较。如果不匹配,请退出测试。注意:此代码的重要部分是testPosition 方法。这是我最近使用的代码:

public void testSomething() throws Exception 
    try 
        // snip

        // you can even extract this into a method "clickAndTest" or something
        robot.mouseMove(x2, y2);
        click();
        testPosition(x2, y2);

        // snip
     catch (ExitEarlyException e) 
        // handle early exit
    


private static void click() throws InterruptedException 
    r.mousePress(InputEvent.BUTTON1_DOWN_MASK);
    Thread.sleep(30 + rand.nextInt(50));
    r.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    Thread.sleep(30 + rand.nextInt(50));


private static void testPosition(int x2, int y2) throws ExitEarlyException 
    Point p = MouseInfo.getPointerInfo().getLocation();
    if(p.x != x2 || p.y != y2) throw new ExitEarlyException();

【讨论】:

【参考方案4】:

这不是一个微不足道的问题,Java 并没有为您提供优雅的方法。您可以使用类似 banjollity 建议的解决方案,但如果您错误的鼠标点击打开另一个当前在任务栏中打开的全尺寸窗口,即使这样也不会一直有效。

事实上,默认情况下,Java 几乎没有给开发人员对操作系统的控制权。这是由于两个主要原因:安全性(正如 java 文档所引用的那样)以及不同的操作系统处理事件完全不同的事实,并且制作一个统一的模型来表示所有这些可能没有多大意义。

所以为了回答你的问题,我想你想要的是你的程序的某种行为,它在全球范围内监听按键,而不仅仅是在你的应用程序中。像这样的事情将要求您访问您选择的操作系统提供的功能,并且要在 Java 中访问它,您将需要通过 Java 本地接口 (JNI) 层来完成它。

所以你想做的是:

    使用 C 语言实现一个程序,该程序将侦听您的操作系统上的全局按键(如果此操作系统是 Windows),而不是查找有关 Windows 挂钩的文档,该文档由 Microsoft 和 MSDN 在 Web 和其他地方提供了很好的文档。如果您的操作系统是 Linux 或 Mac OS X,那么您将需要使用 X11 开发库来监听全局按键。这可以根据我在http://ubuntuforums.org/showthread.php?t=864566 写的 Howto 在 ubunutu linux 发行版上完成@

    通过 JNI 将 C 代码连接到 Java 代码。这一步实际上是更容易的一步。按照我在教程http://ubuntuforums.org/showthread.php?t=864566 在 windows 和 linux 下使用的过程,因为将 C 代码连接到 Java 代码的过程在两个操作系统上都是相同的。

要记住的重要一点是,如果您首先编写和调试您的 C/C++ 代码并确保它可以正常工作,那么让您的 JNI 代码工作要容易得多。然后将其与 Java 集成很容易。

【讨论】:

【参考方案5】:

有同样的问题。就我而言,机器人只控制了一个已最大化的 Windows 应用程序。我将这些行放在驱动机器人的主循环的顶部:

颜色 iconCenterColor = new Color(255,0,0); // 如果程序图标是红色的

如果 (iconCenterColor.equals(robot.getPixelColor(10,15))) throw new IllegalStateException("机器人没有与正确的应用交互。");

要取消机器人,只需按 alt-tab 键切换到另一个应用程序。非常适合简单的单应用驾驶机器人。

【讨论】:

【参考方案6】:

让您的程序打开第二个窗口,该窗口显示在主窗口下方但已最大化,然后您的错误鼠标点击将全部被最大化的窗口接收,并且它可以接收您的键盘输入。

【讨论】:

【参考方案7】:

从终端的命令行启动程序并使用 Ctrl-C 终止它。

【讨论】:

在我看来,这是正确的答案。该问题指出它仅用于调试,并且根本不需要更改代码。 我使用 ALT+ F4 关闭 Java 窗口,同时使用“卡住”的机器人编写测试。测试完成并正常工作(生产就绪)后,就不需要它了。

以上是关于在 Java 中没有焦点的情况下监听输入的主要内容,如果未能解决你的问题,请参考以下文章

Java中的事件监听器没有应用程序有焦点? (全局按键检测)

H5如何对android和ios手机软键盘的监听

关于js中事件监听的问题(文本框 回车键 失去焦点)

java swing里文本框控制不能输入汉字,或者输入字符串包含汉字在失去焦点事件时提示用户!

实时监听输入框

Python GUI 窗口在没有焦点的情况下保持在顶部