JNA 锁定的 Swing 应用程序线程
Swing application Thread locked by JNA
我在 Windows 10 上安装了 Swing 应用程序。我还添加了低级键盘挂钩,它应该拦截键盘事件并重新映射,例如 'z' 到 's' 按钮.
这是挂接我的 java 应用程序外部的键盘事件所必需的。我已经使用 JNA 5.6.0 版和 jna-platform 5.6.0 版实现了它。它工作正常但不在我的 Swing 应用程序中。
我的问题 是挂钩打开时,swing 应用程序被锁定。我无法按下任何 Jbutton,甚至根本无法关闭 Jframe。
我的猜测是和线程有关,但是我在线程和多线程方面很弱
可重现的例子。
测试框架class:
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TestFrame extends JFrame {
public static void main(String [] args) {
TestFrame frame = new TestFrame();
JTextField textField=new JTextField();
textField.setBounds(50,50, 150,20);
JButton button=new JButton("Click Here");
button.setBounds(50,100,95,30);
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
ReMapper reMapper = new ReMapper();
reMapper.reMapOn();
textField.setText("Is my frame locked?");
}
});
frame.add(button);
frame.add(textField);
frame.setSize(400,400);
frame.setLayout(null);
frame.setVisible(true);
}
}
ReMapper class:
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.*;
public class ReMapper {
private static WinUser.HHOOK hHook;
final User32 user32Library = User32.INSTANCE;
WinDef.HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
static WinUser.INPUT input = new WinUser.INPUT();
public void reMapOn() {
WinUser.LowLevelKeyboardProc keyboardHook = new WinUser.LowLevelKeyboardProc() {
@Override
public WinDef.LRESULT callback(int nCode, WinDef.WPARAM wParam, WinUser.KBDLLHOOKSTRUCT kbDllHookStruct) {
if (nCode >= 0) {
if (wParam.intValue() == WinUser.WM_KEYDOWN) {
if (kbDllHookStruct.vkCode == 90) { // 90 is key code = z
sendKey(83); // 83 is key code = s
return new WinDef.LRESULT(1);
}
}
}
Pointer ptr = kbDllHookStruct.getPointer();
long peer = Pointer.nativeValue(ptr);
return user32Library.CallNextHookEx(hHook, nCode, wParam, new WinDef.LPARAM(peer));
}
};
hHook = user32Library.SetWindowsHookEx(WinUser.WH_KEYBOARD_LL, keyboardHook, hMod, 0);
int result;
WinUser.MSG msg = new WinUser.MSG();
while ((result = user32Library.GetMessage(msg, null, 0, 0)) != 0) {
if (result == -1) {
break;
} else {
user32Library.TranslateMessage(msg);
user32Library.DispatchMessage(msg);
}
}
}
static void sendKey(int keyCode) {
input.type = new WinDef.DWORD(WinUser.INPUT.INPUT_KEYBOARD);
input.input.setType("ki"); // Because setting INPUT_INPUT_KEYBOARD is not enough: https://groups.google.com/d/msg/jna-users/NDBGwC1VZbU/cjYCQ1CjBwAJ
input.input.ki.wScan = new WinDef.WORD(0);
input.input.ki.time = new WinDef.DWORD(0);
input.input.ki.dwExtraInfo = new BaseTSD.ULONG_PTR(0);
// Press
input.input.ki.wVk = new WinDef.WORD(keyCode); // 0x41
input.input.ki.dwFlags = new WinDef.DWORD(0); // keydown
User32.INSTANCE.SendInput(new WinDef.DWORD(1), (WinUser.INPUT[]) input.toArray(1), input.size());
// Release
input.input.ki.wVk = new WinDef.WORD(keyCode); // 0x41
input.input.ki.dwFlags = new WinDef.DWORD(2); // keyup
User32.INSTANCE.SendInput(new WinDef.DWORD(1), (WinUser.INPUT[]) input.toArray(1), input.size());
}
}
这是单击 'Click Here' 按钮后锁定 Jframe 的屏幕:
ReMapper class 独立于 Swing 应用程序工作正常。
reMapOn()
允许重新映射 'z' 到 's'。但我需要它在我的 Swing 应用程序中工作而不是阻止它..
有人知道问题出在哪里以及如何解决吗?
查看您的 reMapOn
代码,它有一个 while 循环,这表明它可以 运行 无限期地阻止应用程序 UI。
您需要做的只是在您的 addActionListener
方法中调用其自己线程上的 reMapOn
方法。这可以使用简单的 Thread or Swing Worker:
Swing Worker 示例(首选解决方案,因为您可以覆盖 done
并在重新映射器结束时在该方法中根据需要操作 swing 组件):
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
ReMapper reMapper = new ReMapper();
reMapper.reMapOn();
return null;
}
}.execute();
textField.setText("Is my frame locked?");
线程 示例:
new Thread(() -> {
ReMapper reMapper = new ReMapper();
reMapper.reMapOn();
}).start();
textField.setText("Is my frame locked?");
其他几点是:
- 不要使用
null
/AbsoluteLayout
而是使用适当的 LayoutManager
- 不要在组件上调用
setBounds()
或 setSize()
,如果您使用正确的布局管理器,系统会为您处理此问题
- 使用
LayoutManager
时,在将框架设置为可见之前调用 JFrame#pack()
- 不要不必要地扩展
JFrame
class
- 应通过
SwingUtilities.invokeLater
在 EDT 上调用所有 Swing 组件
我在 Windows 10 上安装了 Swing 应用程序。我还添加了低级键盘挂钩,它应该拦截键盘事件并重新映射,例如 'z' 到 's' 按钮.
这是挂接我的 java 应用程序外部的键盘事件所必需的。我已经使用 JNA 5.6.0 版和 jna-platform 5.6.0 版实现了它。它工作正常但不在我的 Swing 应用程序中。
我的问题 是挂钩打开时,swing 应用程序被锁定。我无法按下任何 Jbutton,甚至根本无法关闭 Jframe。
我的猜测是和线程有关,但是我在线程和多线程方面很弱
可重现的例子。
测试框架class:
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TestFrame extends JFrame {
public static void main(String [] args) {
TestFrame frame = new TestFrame();
JTextField textField=new JTextField();
textField.setBounds(50,50, 150,20);
JButton button=new JButton("Click Here");
button.setBounds(50,100,95,30);
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
ReMapper reMapper = new ReMapper();
reMapper.reMapOn();
textField.setText("Is my frame locked?");
}
});
frame.add(button);
frame.add(textField);
frame.setSize(400,400);
frame.setLayout(null);
frame.setVisible(true);
}
}
ReMapper class:
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.*;
public class ReMapper {
private static WinUser.HHOOK hHook;
final User32 user32Library = User32.INSTANCE;
WinDef.HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
static WinUser.INPUT input = new WinUser.INPUT();
public void reMapOn() {
WinUser.LowLevelKeyboardProc keyboardHook = new WinUser.LowLevelKeyboardProc() {
@Override
public WinDef.LRESULT callback(int nCode, WinDef.WPARAM wParam, WinUser.KBDLLHOOKSTRUCT kbDllHookStruct) {
if (nCode >= 0) {
if (wParam.intValue() == WinUser.WM_KEYDOWN) {
if (kbDllHookStruct.vkCode == 90) { // 90 is key code = z
sendKey(83); // 83 is key code = s
return new WinDef.LRESULT(1);
}
}
}
Pointer ptr = kbDllHookStruct.getPointer();
long peer = Pointer.nativeValue(ptr);
return user32Library.CallNextHookEx(hHook, nCode, wParam, new WinDef.LPARAM(peer));
}
};
hHook = user32Library.SetWindowsHookEx(WinUser.WH_KEYBOARD_LL, keyboardHook, hMod, 0);
int result;
WinUser.MSG msg = new WinUser.MSG();
while ((result = user32Library.GetMessage(msg, null, 0, 0)) != 0) {
if (result == -1) {
break;
} else {
user32Library.TranslateMessage(msg);
user32Library.DispatchMessage(msg);
}
}
}
static void sendKey(int keyCode) {
input.type = new WinDef.DWORD(WinUser.INPUT.INPUT_KEYBOARD);
input.input.setType("ki"); // Because setting INPUT_INPUT_KEYBOARD is not enough: https://groups.google.com/d/msg/jna-users/NDBGwC1VZbU/cjYCQ1CjBwAJ
input.input.ki.wScan = new WinDef.WORD(0);
input.input.ki.time = new WinDef.DWORD(0);
input.input.ki.dwExtraInfo = new BaseTSD.ULONG_PTR(0);
// Press
input.input.ki.wVk = new WinDef.WORD(keyCode); // 0x41
input.input.ki.dwFlags = new WinDef.DWORD(0); // keydown
User32.INSTANCE.SendInput(new WinDef.DWORD(1), (WinUser.INPUT[]) input.toArray(1), input.size());
// Release
input.input.ki.wVk = new WinDef.WORD(keyCode); // 0x41
input.input.ki.dwFlags = new WinDef.DWORD(2); // keyup
User32.INSTANCE.SendInput(new WinDef.DWORD(1), (WinUser.INPUT[]) input.toArray(1), input.size());
}
}
这是单击 'Click Here' 按钮后锁定 Jframe 的屏幕:
ReMapper class 独立于 Swing 应用程序工作正常。
reMapOn()
允许重新映射 'z' 到 's'。但我需要它在我的 Swing 应用程序中工作而不是阻止它..
有人知道问题出在哪里以及如何解决吗?
查看您的 reMapOn
代码,它有一个 while 循环,这表明它可以 运行 无限期地阻止应用程序 UI。
您需要做的只是在您的 addActionListener
方法中调用其自己线程上的 reMapOn
方法。这可以使用简单的 Thread or Swing Worker:
Swing Worker 示例(首选解决方案,因为您可以覆盖 done
并在重新映射器结束时在该方法中根据需要操作 swing 组件):
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
ReMapper reMapper = new ReMapper();
reMapper.reMapOn();
return null;
}
}.execute();
textField.setText("Is my frame locked?");
线程 示例:
new Thread(() -> {
ReMapper reMapper = new ReMapper();
reMapper.reMapOn();
}).start();
textField.setText("Is my frame locked?");
其他几点是:
- 不要使用
null
/AbsoluteLayout
而是使用适当的 LayoutManager - 不要在组件上调用
setBounds()
或setSize()
,如果您使用正确的布局管理器,系统会为您处理此问题 - 使用
LayoutManager
时,在将框架设置为可见之前调用 - 不要不必要地扩展
JFrame
class - 应通过
SwingUtilities.invokeLater
在 EDT 上调用所有 Swing 组件
JFrame#pack()