Swing 应用程序线程被 JNA 锁定
Posted
技术标签:
【中文标题】Swing 应用程序线程被 JNA 锁定【英文标题】:Swing application Thread locked by JNA 【发布时间】:2021-03-23 08:48:06 【问题描述】:我在 Windows 10 上有 Swing 应用程序。我还添加了低级键盘钩子,它应该拦截键盘事件并将例如“z”重新映射到“s”按钮。
这是在我的 java 应用程序之外挂钩键盘事件所必需的。我使用 JNA 5.6.0 版和 jna-platform 5.6.0 版实现了它。它工作正常,但不在我的 Swing 应用程序中。
我的问题是当钩子打开时,摆动应用程序被锁定了。我根本无法按下任何 Jbutton 甚至关闭 Jframe。
我的猜测是它与线程有关,但我在线程和多线程方面非常薄弱。
可重现的例子。
TestFrame 类:
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);
重映射类:
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());
这是单击“单击此处”按钮后锁定 Jframe 的屏幕:
ReMapper 类独立于 Swing 应用程序可以正常工作。
reMapOn()
允许将 'z' 重新映射到 's'。但我需要它在我的 Swing 应用程序中工作而不是阻止它..
有谁知道问题可能是什么以及如何解决?
【问题讨论】:
【参考方案1】:查看您的 reMapOn
代码,它有一个 while 循环,这表明它可以无限期运行并阻塞应用程序 UI。
您需要做的只是在您的addActionListener
方法中调用它自己的线程上的reMapOn
方法。这可以使用简单的Thread 或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 组件
【讨论】:
感谢您的帮助。它现在就像一个魅力:)以上是关于Swing 应用程序线程被 JNA 锁定的主要内容,如果未能解决你的问题,请参考以下文章
Java Swing 1.6 更新 23 个使用 Applet 的 JVM 锁