You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tkinter按钮点击后无法恢复RAISED状态及GUI卡顿问题求助

解决Tkinter按钮点击后阻塞界面的问题

这个问题我之前也碰到过!核心原因是Tkinter的主事件循环是单线程的——你的Power_On_Off函数里包含sleep(4)和telnet网络操作,这些耗时任务会直接卡死主线程,导致界面完全无法响应:按钮一直保持按下(SUNKEN)状态,其他控件也接收不到任何操作。

要实现「点击后立即恢复按钮状态,后台执行任务」的需求,我们需要用Python的threading模块把耗时操作移到后台线程,让主线程能立刻回到事件循环处理界面更新。


具体解决方案步骤

1. 导入线程模块

首先在代码顶部添加threading的导入:

from tkinter import Tk, Menu, Button
import telnetlib
from time import sleep
import threading  # 新增线程模块

2. 修改事件处理函数,将耗时任务放入线程

修改OnButtonClick函数,不再直接调用Power_On_Off,而是启动一个后台线程来执行它:

def OnButtonClick(button_id):
    if button_id == 1:
        # 用线程执行耗时操作,daemon=True确保主程序退出时线程自动终止
        threading.Thread(target=Power_On_Off, args=('1', 'Off'), daemon=True).start()
    elif button_id == 2:
        threading.Thread(target=Power_On_Off, args=('1', 'On'), daemon=True).start()

这样修改后,主线程会在启动线程后立刻回到事件循环,按钮会自动恢复RAISED状态,其他按钮也能正常响应点击。

3. 进阶优化:添加按钮状态反馈(可选)

如果想给用户更清晰的操作反馈(比如防止重复点击),可以在点击后暂时禁用按钮、修改文本/颜色,任务完成后再恢复。注意:子线程不能直接操作Tkinter控件,必须用root.after()把UI操作调度到主线程执行。

修改后的完整代码示例:

from tkinter import Tk, Menu, Button
import telnetlib
from time import sleep
import threading

On_Color = '#53d411'
Off_Color = '#e78191'
root = None  # 全局变量,用于在子线程中调度UI操作

def Power_On_Off (PowerSwitchPort, Action, callback=None):
    ''' Control the Power On Off Switch '''
    on_off = telnetlib.Telnet("10.0.5.9", 2016)
    if Action == 'On' or Action =='ON' or Action == 'on':
        StringDict = {'1': b"S00D1DDDDDDDE", '2': b"S00DD1DDDDDDE", '3': b"S00DDD1DDDDDE", '4': b"S00DDDD1DDDDE", '5': b"S00DDDDD1DDDE", '6': b"S00DDDDDD1DDE", '7': b"S00DDDDDDD1DE", '8': b"S00DDDDDDDD1E"}
    elif Action == 'Off' or Action =='OFF' or Action == 'off':
        StringDict = {'1': b"S00D0DDDDDDDE", '2': b"S00DD0DDDDDDE", '3': b"S00DDD0DDDDDE", '4': b"S00DDDD0DDDDE", '5': b"S00DDDDD0DDDE", '6': b"S00DDDDDD0DDE", '7': b"S00DDDDDDD0DE", '8': b"S00DDDDDDDD0E"}
    PowerSwitchPort = str(PowerSwitchPort)
    on_off.read_eager()
    on_off.write(b"S00QLE\n")
    sleep(4)
    on_off.write(StringDict[PowerSwitchPort])
    on_off.close()
    
    # 任务完成后,在主线程执行UI更新回调
    if callback:
        root.after(0, callback)

def OnButtonClick(button_id):
    # 定义按钮恢复的回调函数
    def reset_btn1():
        power_socket1.config(text='Socket 1 Off', bg=On_Color, state='normal')
    def reset_btn2():
        power_socket2.config(text='Socket 1 On', bg=On_Color, state='normal')
    
    if button_id == 1:
        # 点击后立即修改按钮状态,防止重复点击
        power_socket1.config(text='操作中...', state='disabled', bg='#cccccc')
        threading.Thread(target=Power_On_Off, args=('1', 'Off', reset_btn1), daemon=True).start()
    elif button_id == 2:
        power_socket2.config(text='操作中...', state='disabled', bg='#cccccc')
        threading.Thread(target=Power_On_Off, args=('1', 'On', reset_btn2), daemon=True).start()

def main ():
    global root
    root = Tk()
    root.title("Power Supply Control")
    root.iconbitmap(r'c:\Users\alpha_2.PL\Desktop\Power.ico')
    
    global power_socket1, power_socket2
    power_socket1 = Button(root, text = 'Socket 1 Off', command=lambda: OnButtonClick(1), bg = On_Color)
    power_socket1.pack()
    power_socket2 = Button(root, text = 'Socket 1 On', command=lambda: OnButtonClick(2), bg = On_Color)
    power_socket2.pack()
    
    ''' Menu Bar '''
    menubar = Menu(root)
    file = Menu(menubar, tearoff = 0)
    file.add_command(label='Settings')
    menubar.add_cascade(label='Options', menu = file)
    root.config(menu=menubar)
    root.mainloop()

if __name__ == '__main__':
    main()

关键注意事项

  • 永远不要在主线程执行耗时操作:Tkinter的事件循环必须保持流畅,任何阻塞都会导致界面冻结。
  • 子线程禁止直接操作UI控件:所有Tkinter的控件操作必须在主线程执行,root.after()是实现线程间UI更新的标准方式。
  • 守护线程(daemon=True):设置为守护线程可以确保当主窗口关闭时,后台任务会自动终止,避免残留线程占用资源。

内容的提问来源于stack exchange,提问作者Moshe S.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:23:30