我正在使用 Win32 API 和 PySide6 在 Python 中为 Windows 设置热键系统。我想在 HotkeyManager 类中注册热键并在单独的线程中侦听它们,以便 GUI 保持响应。但是,当我将侦听逻辑移至线程时,无法正确检测到热键事件。
这是不使用线程的代码,其中热键在主线程上注册和检测:
from threading import Thread
from typing import Callable, Dict
from win32gui import RegisterHotKey, UnregisterHotKey, GetMessage
from win32con import VK_NUMPAD0, MOD_NOREPEAT
class HotkeyManager:
def __init__(self):
self.hotkey_id = 1
self._callbacks: Dict[int, Callable] = {}
def register_hotkey(self, key_code: int, callback: Callable):
self._callbacks[self.hotkey_id] = callback
RegisterHotKey(0, self.hotkey_id, MOD_NOREPEAT, key_code)
self.hotkey_id += 1
def listen(self):
while True:
print("Listener started.")
msg = GetMessage(None, 0, 0)
hotkey_id = msg[1]
if hotkey_id in self._callbacks:
self._callbacks[hotkey_id]()
在主代码中,此设置按预期工作:
from PySide6 import QtWidgets
from win32con import VK_NUMPAD0
def on_press():
print("Numpad 0 pressed!")
app = QtWidgets.QApplication([])
manager = HotkeyManager()
manager.register_hotkey(VK_NUMPAD0, on_press)
manager.listen()
# Initialize window
widget = QtWidgets.QMainWindow()
widget.show()
app.exec()
但是,当我尝试将listen()方法移至单独的线程时,热键无法正确响应:
class HotkeyManager:
def listen(self):
def run():
while True:
print("Listener started.")
msg = GetMessage(None, 0, 0)
hotkey_id = msg[1]
if hotkey_id in self._callbacks:
self._callbacks[hotkey_id]()
thread = Thread(target=run, daemon=True)
thread.start()
如何在单独的线程中正确监听热键而不丢失功能?看来该问题可能是由于热键在主线程上注册而侦听逻辑在辅助线程中运行所致。我怎样才能解决这个问题,让一切按预期进行?
关于声明:“这是不使用线程即可工作的代码”,问题中的代码没有任何实际工作的内容。详细说一下:
from win32con import VK_NUMPAD0, MOD_NOREPEAT
会产生 ImportError。检查 [GitHub]:mhammond/pywin32 - 壮举:添加 MOD_NOREPEAT (RegisterHotKey) 常量(我几分钟前提交的)
[GitHub.MHammond]:win32gui.GetMessage(文档)似乎是错误的,返回的是2个元素的元组:
底层GetMessage返回码
已提交 [GitHub]:mhammond/pywin32 - 为此
这种对 WM_HOTKEY 进行线程监控的方式似乎有点笨拙,因为窗口有自己的消息循环,如果让我选择,我会尝试在窗口的消息处理函数中处理这些消息,但浅薄的谷歌搜索没有发现任何有用的信息。
发布基于您的代码的解决方案。这个想法是将热键注册和侦听包装在要在线程中执行的函数中。
code00.py:
#!/usr/bin/env python
import sys
import threading
import typing
import win32con as wcon
import win32gui as wgui
from PySide6 import QtWidgets
MOD_NOREPEAT = 0x4000
class HotkeyManager:
def __init__(self):
self.hotkey_id = 1
self._callbacks: typing.Dict[int, typing.Callable] = {}
self.threads = []
def __del__(self):
self.clear()
def register_hotkey(self, key_code: int, callback: typing.Callable):
t = threading.Thread(target=self._register_hotkey, args=(key_code, callback), daemon=True)
self.threads.append(t)
t.start()
def _register_hotkey(self, key_code: int, callback: typing.Callable):
self._callbacks[self.hotkey_id] = callback
wgui.RegisterHotKey(None, self.hotkey_id, MOD_NOREPEAT, key_code)
self.hotkey_id += 1
self._listen()
def _listen(self):
print(f"Listener started ({threading.get_ident()}, {threading.get_native_id()})")
while True:
res = wgui.GetMessage(None, 0, 0)
print(f" GetMessage returned: {res}") # @TODO - cfati: Check what it returns!
rc, msg = res
hotkey_id = msg[2]
if hotkey_id in self._callbacks:
self._callbacks[hotkey_id]()
def clear(self):
for hkid in self._callbacks:
wgui.UnregisterHotKey(None, hkid)
self._callbacks = {}
def on_press_np0():
print(f"NumPad0 pressed!")
def main(*argv):
print(f"Main thread ({threading.get_ident()}, {threading.get_native_id()})")
app = QtWidgets.QApplication([])
widget = QtWidgets.QMainWindow()
widget.setWindowTitle("SO q079161068")
widget.show()
print("HotKeys")
hkm = HotkeyManager()
hkm.register_hotkey(wcon.VK_NUMPAD0, on_press_np0)
print("App mesage loop")
app.exec()
if __name__ == "__main__":
print(
"Python {:s} {:03d}bit on {:s}\n".format(
" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32,
sys.platform,
)
)
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q079161068]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32 Main thread (31388, 31388) HotKeys App mesage loop Listener started (13152, 13152) GetMessage returned: [1, (0, 786, 1, 6291456, 513345140, (0, 0))] NumPad0 pressed! GetMessage returned: [1, (0, 786, 1, 6291456, 513346593, (1919, 0))] NumPad0 pressed! GetMessage returned: [1, (0, 786, 1, 6291456, 513347734, (1919, 1079))] NumPad0 pressed! GetMessage returned: [1, (0, 786, 1, 6291456, 513349359, (0, 1079))] NumPad0 pressed! GetMessage returned: [1, (0, 786, 1, 6291456, 513353093, (938, 561))] NumPad0 pressed! GetMessage returned: [1, (0, 786, 1, 6291456, 513355781, (1621, 305))] NumPad0 pressed! Done.
同时发布截图:
请注意,当线程运行时 NumPad 0 击键不会到达其他窗口。