有什么方法让tkinter窗口忽略Win + D命令吗?
我正在用Python的tkinter库制作一个应用程序,这个程序需要始终保持在屏幕的最上面。我查了很多资料,终于知道怎么让tkinter窗口一直在最上面,但我找不到方法让它在用户按下Win + D快捷键时仍然保持在最上面。
有没有什么办法可以让tkinter窗口忽略Win + D这个命令呢?
1 个回答
1
你可以设置一个低级别的键盘钩子,基本上就是“吸收”任何不允许的按键,不过这样做可能有点过于复杂。在这个例子中,我设置了一个过滤器来处理左边和右边的Windows键。
这个方法依赖于一个叫做 pywin32 的模块,不过也可以考虑只用 ctypes
来重写。
不过需要注意几点:
- 这种方法比较强硬,会阻止所有与Windows键相关的快捷键在运行期间工作
- 这个过滤是全系统范围的,不仅仅是在你的应用程序中
这段代码主要是改编自 这个回答,原作者还提供了一个 keyboard 模块,可以简化一些技术细节 - 值得一看。
说完这些,主要的思路是你需要创建一个 KeyFilter
类的实例,并让 KeyFilter.set_hook
方法在自己的线程中运行,你在 ForbiddenKeys
枚举中设置的按键会在这个线程(更可能是创建这个线程的你的应用)运行期间被过滤掉。
在 if __name__ == '__main__'
这个代码块中有一个基础示例,你可以直接运行它来看看效果。
import atexit
import ctypes
import win32api
import win32con
import win32gui
from enum import Enum
class ForbiddenKeys(int, Enum):
"""Enumerator of keycodes that will be filtered out"""
# NOTE: inheriting from int allows for testing of membership once the Enum
# is cast to a set, e.g.: 'if <val> in set(ForbiddenKeys)'
# (int class must come before Enum)
KEY_LWIN = win32con.VK_LWIN # 0x5B
KEY_RWIN = win32con.VK_RWIN # 0x5C
...
# you can add more keys here as needed
# e.g.: KEY_LCTRL = 0xA2
class KeyFilter:
"""
Sets up a Windows low-level keyboard hook to filter out "forbidden" keys:
(keycodes defined in ForbiddenKeys enum)
"""
user32 = ctypes.WinDLL('user32', use_last_error=True)
CMPFUNC = ctypes.CFUNCTYPE(
ctypes.c_int,
ctypes.c_int,
ctypes.c_int,
ctypes.POINTER(ctypes.c_void_p)
)
def _filter_handler(self, nCode, wParam, lParam) -> int:
keycode = lParam[0] & 0xFFFF_FFFF # upper bits of keycode are masked
if keycode in set(ForbiddenKeys):
return 1
else: # key allowed, move along to the next hook
return self.user32.CallNextHookEx(
self.hook_id,
nCode,
wParam,
lParam
)
def _unhook(self) -> None:
"""Unhook the keyboard hook and stop the listener loop"""
self.user32.UnhookWindowsHookEx(self.hook_id)
def set_hook(self) -> None:
"""Listen for keyboard events until unhooked at exit"""
handler_ptr = self.CMPFUNC(self._filter_handler)
# NOTE: argtypes are required for 64-bit Python compatibility
self.user32.SetWindowsHookExW.argtypes = (
ctypes.c_int,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_uint,
)
# set up a Windows low-level keyboard hook
self.hook_id = self.user32.SetWindowsHookExW(
win32con.WH_KEYBOARD_LL, # hook type: low-level keyboard input
handler_ptr, # point to the handler method
win32api.GetModuleHandle(None), # handler is in this module
0,
)
atexit.register(self._unhook) # release hook at interpreter exit
# start the message loop
while (msg := win32gui.GetMessage(0, 0, 0)) > 0:
win32gui.TranslateMessage(ctypes.byref(msg))
win32gui.DispatchMessage(ctypes.byref(msg))
if __name__ == '__main__':
# test keyfilter
from threading import Thread
keyfilter = KeyFilter()
keyfilter_thread = Thread(
target=keyfilter.set_hook,
name='KeyFilter',
daemon=True,
)
keyfilter_thread.start()
# press ctrl-c or close the terminal to stop the keyfilter
while True:
try:
pass
except KeyboardInterrupt:
break