有什么方法让tkinter窗口忽略Win + D命令吗?

0 投票
1 回答
31 浏览
提问于 2025-04-14 16:23

我正在用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

撰写回答