有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java的一个令人困惑的按键跳跃案例

背景

开发一个基本的、开源的键盘和鼠标屏幕显示桌面应用程序,名为KmCaster

Screen Casting Preview

应用程序使用JNativeHook库来接收全局键盘和鼠标事件,因为Swing的KeyMouse侦听器仅限于接收指向应用程序本身的事件

问题

当应用程序失去焦点时,用户界面显示间歇性按键,而不是每次按键。然而,控制台显示应用程序已收到每一次按键

代码

一个简短、自包含、可编译的示例:

import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import org.jnativehook.keyboard.NativeKeyEvent;
import org.jnativehook.keyboard.NativeKeyListener;

import javax.swing.*;

import static java.util.logging.Level.OFF;
import static java.util.logging.Logger.getLogger;
import static javax.swing.SwingUtilities.invokeLater;
import static org.jnativehook.GlobalScreen.*;
import static org.jnativehook.keyboard.NativeKeyEvent.getKeyText;

public class Harness extends JFrame implements NativeKeyListener {

  private final JLabel mLabel = new JLabel( "Hello, world" );
  private int mCount;

  public void init() {
    getContentPane().add( mLabel );

    setDefaultCloseOperation( EXIT_ON_CLOSE );
    setLocationRelativeTo( null );
    setAlwaysOnTop( true );
    pack();
    setVisible( true );
  }

  @Override
  public void nativeKeyPressed( final NativeKeyEvent e ) {
    final var s = getKeyText( e.getKeyCode() );
    System.out.print( s + " " + (++mCount % 10 == 0 ? "\n" : "") );

    invokeLater( () -> mLabel.setText( s ) );
  }

  public static void main( final String[] args ) throws NativeHookException {
    disableNativeHookLogger();
    registerNativeHook();

    final var harness = new Harness();
    addNativeKeyListener( harness );

    invokeLater( harness::init );
  }

  private static void disableNativeHookLogger() {
    final var logger = getLogger( GlobalScreen.class.getPackage().getName() );
    logger.setLevel( OFF );
    logger.setUseParentHandlers( false );
  }

  @Override
  public void nativeKeyReleased( final NativeKeyEvent e ) {}

  @Override
  public void nativeKeyTyped( final NativeKeyEvent e ) {}
}

上面的代码生成一个小窗口,在运行时显示问题:

Harness Screenshot

请务必在任何其他窗口中键入,以查看演示应用程序中令人费解的按键丢失情况

环境

  • OpenJDK版本“14.0.1”2020-04-14,64位
  • XFCE
  • ArchLinux
  • JNativeHook 2.1.0

细节

JNativeHook在自己的线程中运行,但使用invokeLater(或invokeAndWait?)应该在Swing的事件线程上发布UI更新

disableNativeHookLogger()的调用并不相关,它只是在运行演示时保持控制台干净

控制台输出

以下是应用程序具有焦点时的控制台输出:

Shift I Space A M Space I N S I 
D E Space T H E Space A P P 
L I C A T I O N Period

以下是应用程序失去焦点时的控制台输出:

Shift I Space A M Space O U T S
I D E Space T H E Space A P
P L I C A T I O N Period 

因此,很明显,无论应用程序是否具有焦点,在调用nativeKeyPressed时都不会丢失键盘事件。也就是说,JNativeHook及其通过JNI冒泡的事件似乎都不是罪魁祸首

问题:

无论应用程序是否具有焦点,都需要进行哪些更改,以便每次按键时更新JLabel文本

想法

一些有帮助的项目包括:

  • 调用getDefaultToolkit().sync();显式刷新呈现管道
  • 调用标签上的paintImmediately( getBounds() )

第一项似乎有很大的不同,但有些键似乎仍然丢失(尽管可能是我打字太快)。防止渲染管道合并绘制请求可以避免关键点笔划的丢失

研究

与这一问题有关的资源:


共 (1) 个答案

  1. # 1 楼答案

    使用默认工具箱调用sync()

      @Override
      public void propertyChange( final PropertyChangeEvent e ) {
        invokeLater(
            () -> {
              update( e );
    
              // Prevent collapsing multiple paint events.
              getDefaultToolkit().sync();
            }
        );
      }
    

    full code