在没有焦点的情况下监视 tkinter 中的按键

Monitor keypress in tkinter without focus

我正在使用 tkinter 在 Python 中编写一个小型计时模块。在这方面,我想全局监控何时按下转义键以停止计时。

不幸的是,tkinters“.bind”和“.bind_all”函数仅在 window 处于焦点时拾取击键。

我查看了其他几个用于记录击键的解决方案,包括包 "keyboard" 和 "pynput",但是这些包需要 运行 一个 while 循环,这会使 tkinter GUI 冻结并停止工作。

我找到了这个线程,但它对于具体说明如何完成它不是很有帮助:

我尝试了一些不同的选择

方案一:使用tkinter循环功能,但按键不注册

import keyboard
def _check_esc_pressed(self):
    if self.run_active and keyboard.press('esc'):
        self.Lap()
        self.Stop()
    self.after(50, self._check_esc_pressed())

选项 2:冻结 tkinter 客户端

import keyboard  
def _check_esc_pressed(self):
    while True:  
        if keyboard.is_pressed('esc'): 
            self.Lap()
            self.Stop()
            break  
        else:
            pass

选项 3:冻结 tkinter 客户端

from pynput.keyboard import Key, Listener
def on_release(self, key):
    if key == Key.esc:
        self.Lap()
        self.Stop()
        # Stop listener
        return False

def _check_esc_pressed(self):
    def on_press(key):
        pass
    with Listener(
            on_press=on_press,
            on_release=on_release) as listener:
        listener.join()

我希望按退出键会终止“_check_esc_pressed”功能,记录一圈并停止计时器。转义检查只应在​​ 运行 处于活动状态时处理

我通过使用 python 的 system_hotkey 包找到了问题的解决方案。这个包允许你分配系统范围的热键,这些热键可以在不关注 tkinter 程序的情况下工作。

from system_hotkey import SystemHotkey
hk = SystemHotkey()
hk.register(['alt', 'q'], callback=lambda event: self.Start())
hk.register(['alt', 'w'], callback=lambda event: self.Stop())

请记住,像这样添加的任何热键都会使其他程序无法访问已注册的热键组合。

如果您仍然需要答案,或者其他人觉得这很有用...

答案可能在于我们看不到的代码 - 通过将应用程序的逻辑放在另一个线程中,它允许 tkinter 在不冻结的情况下做它的事情。

下面的代码遵循您的(稍作修改的)选项 2:

import queue
import keyboard  
import threading
import time
import tkinter as tk

def app_main_loop(my_label):
    # Create another thread that monitors the keyboard
    input_queue = queue.Queue()
    kb_input_thread = threading.Thread(target=_check_esc_pressed, args=(input_queue,))
    kb_input_thread.daemon = True
    kb_input_thread.start()
    
    # Main logic loop
    run_active = True
    while True:
        if not input_queue.empty():
            if (run_active) and (input_queue.get() == "esc"):
                run_active = False
                Lap(my_label)
                Stop()
        time.sleep(0.1)  # seconds

def _check_esc_pressed(input_queue):
    while True:
        if keyboard.is_pressed('esc'):
            input_queue.put("esc")
        time.sleep(0.1) # seconds

def Lap(my_label):
    my_label.configure(text = "Lap")

def Stop():
    print("Stopped")

if __name__ == "__main__":
    # Create the ui
    root = tk.Tk()
    root.attributes("-fullscreen", True)
    my_label = tk.Label(root, text="Hello World!")
    my_label.pack()
    
    # Run the app's main logic loop in a different thread
    main_loop_thread = threading.Thread(target=app_main_loop, args=(my_label, ))
    main_loop_thread.daemon = True
    main_loop_thread.start()
    
    # Run the UI's main loop
    root.mainloop()