我正在尝试使用 python tkinter 制作一些应用程序,它必须始终位于屏幕顶部。查了很多文档,终于知道如何设置 tkinter 窗口置顶,但是没找到即使用户按下 Win + D 快捷键也能置顶的方法。
有什么方法可以让 tkinter 窗口忽略 Win + D 命令吗?
您可以设置一个低级键盘挂钩来本质上“吸收”任何禁止的按键,尽管这可能有点矫枉过正。在此示例中,我为左右 Windows 键设置了过滤器。
这依赖于 pywin32 模块,但可能会被重写以专门使用
ctypes
。
对于它的价值,有一些警告:
这段代码很大程度上改编自这个答案,原作者还提供了一个键盘模块,它抽象了一些技术功夫 - 它可能值得研究一下。
完成所有这些后,这里的想法是,您设置
KeyFilter
类的实例以在其自己的线程中运行,并且您在 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