如何在Tkinter GUI中检测按钮长按/抬起及键盘按键状态?
树莓派机器人GUI控制:实现按键/按钮长按检测方案
需求背景
我正在基于Tkinter(使用customtkinter库)构建树莓派机器人的控制GUI,核心功能是通过W/S/A/D按键或对应GUI按钮实现前进/后退/左转/右转。目前已完成基础界面布局,但需要完善逻辑,实现长按按键或按钮时持续输出对应方向,释放后停止(后续将替换为电机控制逻辑)。
当前代码如下:
import tkinter import tkinter.messagebox import customtkinter # Setting up theme customtkinter.set_appearance_mode("Dark") # Modes: "System" (standard), "Dark", "Light" customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" class App(customtkinter.CTk): def __init__(self): super().__init__() # configure window self.title("Cool Blue") self.geometry(f"{1200}x{700}") # configure grid layout (4x4) self.grid_columnconfigure(1, weight=1) self.grid_columnconfigure((2, 3), weight=0) self.grid_rowconfigure((0, 1, 2), weight=1) # create sidebar frame for controls self.sidebar_frame = customtkinter.CTkFrame(self, width=200) self.sidebar_frame.grid(row=0, column=0, rowspan=4, padx=(5, 5), pady=(10, 10), sticky="nsew") self.sidebar_frame.grid_rowconfigure(4, weight=1) self.logo_label = customtkinter.CTkLabel(self.sidebar_frame, text="Controls", font=customtkinter.CTkFont(size=20, weight="bold")) self.logo_label.grid(row=0, column=1, padx=20, pady=(10, 10)) self.button_up = customtkinter.CTkButton(self.sidebar_frame, text="W", height=10, width=10, command=self.motion_event) self.button_up.grid(row=1, column=1, padx=20, pady=10, ipadx=10, ipady=10) self.button_down = customtkinter.CTkButton(self.sidebar_frame, text="S", height=10, width=10, command=self.motion_event) self.button_down.grid(row=3, column=1, padx=20, pady=10, ipadx=10, ipady=10) self.button_left = customtkinter.CTkButton(self.sidebar_frame, text="A", height=10, width=10, command=self.motion_event) self.button_left.grid(row=2, column=0, padx=10, pady=10, ipadx=10, ipady=10) self.button_right = customtkinter.CTkButton(self.sidebar_frame, text="D", height=10, width=10, command=self.motion_event) self.button_right.grid(row=2, column=2, padx=10, pady=10, ipadx=10, ipady=10) self.button_stop = customtkinter.CTkButton(self.sidebar_frame, text="Stop", height=10, width=10, command=self.motion_event) self.button_stop.grid(row=2, column=1, padx=10, pady=10, ipadx=10, ipady=10) # create Video Canvas self.picam = customtkinter.CTkCanvas(self, width=800, background="gray") self.picam.grid(row=0, column=1, rowspan=2, padx=(5, 5), pady=(20, 20), sticky="nsew") self.picam_label = customtkinter.CTkLabel(master=self.picam, text="Live Video", font=customtkinter.CTkFont(size=20, weight="bold")) self.picam_label.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="") # create sidebar frame for Environmental Variable self.measurements = customtkinter.CTkFrame(self, width=200) self.measurements.grid(row=0, column=3, rowspan=4, padx=(5, 5), pady=(10, 10), sticky="nsew") self.measurements.grid_rowconfigure(4, weight=1) self.label_measurements = customtkinter.CTkLabel(master=self.measurements, text="Environment:", font=customtkinter.CTkFont(size=20, weight="bold")) self.label_measurements.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="") def motion_event(self): # if button_up pressed down or physical key "w" on keyboard pressed and held down, print "Forward" until key released or button not being clicked anymore. # else if button_down pressed down or physical key "s" on keyboard pressed and held down, print "Backward" until key released or button not being clicked anymore. # else if button_left pressed down or physical key "a" on keyboard pressed and held down, print "Left" until key released or button not being clicked anymore. # else if button_right pressed down or physical key "d" on keyboard pressed and held down, print "Right" until key released or button not being clicked anymore. if __name__ == "__main__": app = App() app.mainloop()
GUI截图:
实现方案
核心思路
要实现长按持续触发的效果,需要解决两个关键问题:
- 追踪按键/按钮的按下/释放状态
- 定时检查状态并执行对应动作(避免阻塞GUI主循环)
修改后的完整代码
import customtkinter # Setting up theme customtkinter.set_appearance_mode("Dark") customtkinter.set_default_color_theme("blue") class App(customtkinter.CTk): def __init__(self): super().__init__() # configure window self.title("Cool Blue") self.geometry(f"{1200}x{700}") # 状态追踪:记录每个方向是否处于激活状态 self.motion_states = { "forward": False, "backward": False, "left": False, "right": False } # configure grid layout (4x4) self.grid_columnconfigure(1, weight=1) self.grid_columnconfigure((2, 3), weight=0) self.grid_rowconfigure((0, 1, 2), weight=1) # create sidebar frame for controls self.sidebar_frame = customtkinter.CTkFrame(self, width=200) self.sidebar_frame.grid(row=0, column=0, rowspan=4, padx=(5, 5), pady=(10, 10), sticky="nsew") self.sidebar_frame.grid_rowconfigure(4, weight=1) self.logo_label = customtkinter.CTkLabel(self.sidebar_frame, text="Controls", font=customtkinter.CTkFont(size=20, weight="bold")) self.logo_label.grid(row=0, column=1, padx=20, pady=(10, 10)) # 绑定按钮的按下/释放事件(替代command参数) self.button_up = customtkinter.CTkButton(self.sidebar_frame, text="W", height=10, width=10) self.button_up.grid(row=1, column=1, padx=20, pady=10, ipadx=10, ipady=10) self.button_up.bind("<ButtonPress-1>", lambda e: self.set_motion_state("forward", True)) self.button_up.bind("<ButtonRelease-1>", lambda e: self.set_motion_state("forward", False)) self.button_down = customtkinter.CTkButton(self.sidebar_frame, text="S", height=10, width=10) self.button_down.grid(row=3, column=1, padx=20, pady=10, ipadx=10, ipady=10) self.button_down.bind("<ButtonPress-1>", lambda e: self.set_motion_state("backward", True)) self.button_down.bind("<ButtonRelease-1>", lambda e: self.set_motion_state("backward", False)) self.button_left = customtkinter.CTkButton(self.sidebar_frame, text="A", height=10, width=10) self.button_left.grid(row=2, column=0, padx=10, pady=10, ipadx=10, ipady=10) self.button_left.bind("<ButtonPress-1>", lambda e: self.set_motion_state("left", True)) self.button_left.bind("<ButtonRelease-1>", lambda e: self.set_motion_state("left", False)) self.button_right = customtkinter.CTkButton(self.sidebar_frame, text="D", height=10, width=10) self.button_right.grid(row=2, column=2, padx=10, pady=10, ipadx=10, ipady=10) self.button_right.bind("<ButtonPress-1>", lambda e: self.set_motion_state("right", True)) self.button_right.bind("<ButtonRelease-1>", lambda e: self.set_motion_state("right", False)) self.button_stop = customtkinter.CTkButton(self.sidebar_frame, text="Stop", height=10, width=10, command=self.stop_all_motion) self.button_stop.grid(row=2, column=1, padx=10, pady=10, ipadx=10, ipady=10) # create Video Canvas self.picam = customtkinter.CTkCanvas(self, width=800, background="gray") self.picam.grid(row=0, column=1, rowspan=2, padx=(5, 5), pady=(20, 20), sticky="nsew") self.picam_label = customtkinter.CTkLabel(master=self.picam, text="Live Video", font=customtkinter.CTkFont(size=20, weight="bold")) self.picam_label.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="") # create sidebar frame for Environmental Variable self.measurements = customtkinter.CTkFrame(self, width=200) self.measurements.grid(row=0, column=3, rowspan=4, padx=(5, 5), pady=(10, 10), sticky="nsew") self.measurements.grid_rowconfigure(4, weight=1) self.label_measurements = customtkinter.CTkLabel(master=self.measurements, text="Environment:", font=customtkinter.CTkFont(size=20, weight="bold")) self.label_measurements.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="") # 绑定键盘事件 self.bind("<KeyPress-w>", lambda e: self.set_motion_state("forward", True)) self.bind("<KeyRelease-w>", lambda e: self.set_motion_state("forward", False)) self.bind("<KeyPress-s>", lambda e: self.set_motion_state("backward", True)) self.bind("<KeyRelease-s>", lambda e: self.set_motion_state("backward", False)) self.bind("<KeyPress-a>", lambda e: self.set_motion_state("left", True)) self.bind("<KeyRelease-a>", lambda e: self.set_motion_state("left", False)) self.bind("<KeyPress-d>", lambda e: self.set_motion_state("right", True)) self.bind("<KeyRelease-d>", lambda e: self.set_motion_state("right", False)) # 启动动作检查循环 self.check_motion_loop() def set_motion_state(self, direction, state): """更新指定方向的激活状态""" self.motion_states[direction] = state def stop_all_motion(self): """停止所有方向的动作""" for direction in self.motion_states: self.motion_states[direction] = False def check_motion_loop(self): """定时检查状态并执行对应动作""" # 按优先级处理(可根据需求调整) if self.motion_states["forward"]: print("Forward") elif self.motion_states["backward"]: print("Backward") elif self.motion_states["left"]: print("Left") elif self.motion_states["right"]: print("Right") # 每100ms检查一次,可根据需求调整间隔 self.after(100, self.check_motion_loop) if __name__ == "__main__": app = App() app.mainloop()
关键修改说明
- 状态追踪:新增
self.motion_states字典,记录每个方向的激活状态(True表示按下/长按,False表示释放) - 按钮事件绑定:将按钮的
command参数替换为<ButtonPress-1>和<ButtonRelease-1>事件,分别设置状态为激活/关闭 - 键盘事件绑定:绑定
<KeyPress>和<KeyRelease>事件,对应更新方向状态 - 循环检查机制:用
after()方法实现非阻塞的定时检查,每隔100ms输出当前激活的方向(间隔可根据需求调整) - 停止功能:点击Stop按钮重置所有状态,立即停止输出
后续只需将print()语句替换为电机控制的代码即可。
内容的提问来源于stack exchange,提问作者Slavisha84
相关产品推荐
相关产品推荐

