【问题标题】:Implementing a global key press for javafx methods为 javafx 方法实现全局按键
【发布时间】:2018-02-23 15:29:37
【问题描述】:

是否可以使用 javafx 方法 .setOnKeyPressed() 来实现 jnativehook ?

例如,这是在应用程序获得焦点时监听按键的方式:

ListView<String> list = new ListView<String>();

list.setOnKeyPressed(new EventHandler<KeyEvent>() {

    @override
    public void handle(KeyEvent event){

        if (event.getCode() == KeyCode.U) {

            //Go Up list..

         }

         event.consume();

     }

});

但我希望它在焦点位于另一个应用程序时听全局按键,所以理想情况下我希望它看起来像这样:

list.setOnKeyPressed(new EventHandler<NativeKeyEvent>() {

    @override
    public void handle(NativeKeyEvent event){

        if (event.getKeyCode() == NativeKeyEvent.VC_U) {

            //Go Up list..

         }

         event.consume();

     }

});

但是第一行标记了错误:

1. The method setOnKeyPressed(EventHandler <? super KeyEvent>) in the type Node is not applicable for the arguments (new EventHandler<NativeKeyEvent>(){})

2. Bound mismatch: The type NativeKeyEvent is not a valid substitute for the bounded parameter <T extends Event> of the type EventHandler<T>

我确定我需要重载该方法以考虑这些参数,但是我不知道如何去做以及如何处理第二个错误。

对于 jnativehook,方法 .consume() 也是 unsupported

或者有没有其他方法可以在javafx中上下一个listview而不使用这些方法并且兼容jnativehook?

【问题讨论】:

  • 您为什么要这样做?想象一下,您的用户正在使用您的应用程序,并收到一封需要立即回复的电子邮件。您是否真的希望您的应用能够响应他们在输入电子邮件时(例如)进行的任意按键操作?
  • 它不是真正为用户准备的,我需要它用于一个研究项目,我运行一个模拟器和这个应用程序并排运行,所以我需要在用户启动时由这个应用程序注册按键列出列表并进行选择。所以我只需要注册 3 个键,一个让它在列表中上升的键,另一个使它在列表中下降的键和另一个进行选择。
  • 有Robot,还有java.awt关键事件。从其他应用程序窃取按键将是一个严重的安全漏洞。像 Selenium 这样的东西可以进行远程控制。所以你要么必须封装模拟器,这就是当它是一个java应用程序时比较容易。
  • 实验将在受控环境中进行,其中只有两个应用程序将运行,模拟器和此自定义应用程序。无论我决定使用什么键来遍历列表,我都会确保它们在模拟器中被绑定以不做任何事情。我不太明白你所说的“窃取”密钥是什么意思,把它想象成同时使用两个应用程序的输入。

标签: java javafx java-native-interface overloading keyboard-events


【解决方案1】:

首先,让我们谈谈事件传递和 UI 线程,因为了解在幕后进行的上下文切换以及线程之间共享的内存非常重要。

Java 就像几乎所有其他曾经设计过的应用程序一样,对所有 UI 事件都使用一个线程。原因是整个互联网上的well documented。因此,如果您使用 AWT/Swing 或 Java FX,您的事件将分别在 AWT/Swing 或 Java FX 平台线程上分派,并且不应有其他线程与这些事件交互或期待这些事件。

JNativeHook 使用自己的调度线程,完全独立于 Java 的 UI。这主要是出于兼容性原因,因为我不想在实现用户上强制使用特定的 UI API。因此,如果您想使用 AWT/Swing、Java FX 或其他东西,您必须在负责您的 UI 的线程上下文上调度本机事件。库中内置了一个 spring 调度程序,以及我发布的一个 Java FX 调度程序here,它允许通过向 GlobalScreen 注册调度程序通过默认调度方法进行非常简单的上下文切换。

您可能会问自己,“如果我想在事件发送之前对其进行修改怎么办?”这也应该是可能的,但需要更多的努力来注册一个调度类。您应该能够扩展 GlobalScreen 类及其匿名内部类来完成您需要的任何其他事情。例如,如果您想调度 Swing InputEvents 而不是 NativeInputEvents,您可以执行以下操作:

public class CustomScreen extends GlobalScreen {

    // Static blocks are run when the class is loaded.
    static {
        // Add adaptors to the listener to convert the events.
        addNativeKeyListener(new KeyAdapter());
        addNativeMouseListener(new MouseAdapter());

        setEventDispatcher(new SwingDispatchService());
    }

    private static class KeyAdapter extends SwingKeyAdapter {
        public void keyTyped(KeyEvent keyEvent) {
            KeyListener[] listeners = eventListeners.getListeners(KeyListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].keyTyped(keyEvent);
            }
        }

        public void keyPressed(KeyEvent keyEvent) {
            KeyListener[] listeners = eventListeners.getListeners(KeyListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].keyPressed(keyEvent);
            }
        }

        public void keyReleased(KeyEvent keyEvent) {
            KeyListener[] listeners = eventListeners.getListeners(KeyListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].keyReleased(keyEvent);
            }
        }
    }

    private static class MouseAdapter extends SwingMouseAdapter {
        public void mouseClicked(MouseEvent mouseEvent) {
            MouseListener[] listeners = eventListeners.getListeners(MouseListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].mouseClicked(mouseEvent);
            }
        }

        public void mousePressed(MouseEvent mouseEvent) {
            MouseListener[] listeners = eventListeners.getListeners(MouseListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].mousePressed(mouseEvent);
            }
        }

        public void mouseReleased(MouseEvent mouseEvent) {
            MouseListener[] listeners = eventListeners.getListeners(MouseListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].mouseReleased(mouseEvent);
            }
        }

        public void mouseEntered(MouseEvent mouseEvent) {
            MouseListener[] listeners = eventListeners.getListeners(MouseListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].mouseEntered(mouseEvent);
            }
        }

        public void mouseExited(MouseEvent mouseEvent) {
            MouseListener[] listeners = eventListeners.getListeners(MouseListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].mouseExited(mouseEvent);
            }
        }
    }

    public static void addSwingKeyListener(KeyListener listener) {
        if (listener != null) {
            eventListeners.add(KeyListener.class, listener);
        }
    }

    public static void removeKeyListener(KeyListener listener) {
        if (listener != null) {
            eventListeners.remove(KeyListener.class, listener);
        }
    }

    public static void addMouseListener(MouseListener listener) {
        if (listener != null) {
            eventListeners.add(MouseListener.class, listener);
        }
    }

    public static void removeMouseListener(MouseListener listener) {
        if (listener != null) {
            eventListeners.remove(MouseListener.class, listener);
        }
    }
}

所有这一切都是在父全局屏幕上注册库中包含的几个适配器类,并分派给适配器中的摇摆侦听器。还有其他更强大的方法可以做到这一点,例如扩展 GlobalScreen.NativeHookThread 匿名内部类,但这需要您编写更多代码。

处理Java FX 和swing 非常相似,你可以在我之前的帖子中找到一个示例调度程序:https://stackoverflow.com/a/45423466/773849

.consume() 而言,是的,这是不受支持的,但在某些平台上仍然可行。如果您使用的是 Windows 或 OS X,您可以使用wiki 中概述的一些巧妙代码来停止事件传播。这将要求您覆盖前面提到的 NativeHookThread 内部类,并且您必须快速处理您的事件,因为在这种情况下 NativeHookThread 并不是真正的非线程,并且会阻塞操作系统。这个方法在Linux上是不可用的,因为linux此时没有办法执行这个功能。

【讨论】:

    【解决方案2】:

    尝试使用addEventFilterScene

    @Override
    public void start(Stage stage) throws Exception {
        VBox root = new VBox();
        Scene scene = new Scene(root);
        scene.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {
            System.out.println("Key pressed");
            if (event.getCode() == KeyCode.F1) {
                System.out.println("F1 pressed");
            }
            event.consume();
        });
        stage.setScene(scene);
        stage.show();
    }
    

    【讨论】:

      猜你喜欢
      • 2013-11-15
      • 2016-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-12
      相关资源
      最近更新 更多