【问题标题】:tkinter window not respondingtkinter 窗口没有响应
【发布时间】:2021-03-19 21:35:42
【问题描述】:

我有一个 GUI 可以做一些事情,然后重新启动面板,然后等待面板完成重新启动。这个想法是让用户按下“PROGRAM HMI”,它将执行动作()。我最大的问题是在重启过程中,GUI 需要等待 10 秒才能让 HMI 重启。在这 10 秒内,GUI 冻结,顶部出现“窗口无响应”消息。正如我在终端中看到的那样,代码正在执行,它的 GUI 最终解冻。我读过我不应该在这里使用 time.sleep() 。但是我应该怎么写它才不会冻结?我正在使用 python 3.8。

import requests
import json
import time
import warnings
import subprocess
import xml.etree.ElementTree as ET
from ftplib import FTP
import hashlib
from datetime import datetime
import sys
import tkinter as tk
import time
import threading
from tkinter import *

class main:
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("1750+ HMI Setup App")
        self.window.geometry("550x675")
        self.window.resizable(width=False, height=False)
        self.window.configure(bg='#34aeeb')

        self.action_updateIP = False

        self.ipString = "192.168.0.20"    # Should be found by DHCP server.
        self.newIPString = "192.168.0.20" # Default.
        self.url = "https://" + self.ipString + "/"   # TODO: Get IP address based on DHCP of panel, or fixed 192.168.0.1 if it exists?
        self.urlConfig = self.url + "machine_config/"
        self.urlApi = self.url + "rest/api/v1/"
        self.authData = ('admin', 'admin')
        
        #OUTPUT TEXT BOX
        self.outText = Text(self.window, width=50, height=20, wrap=WORD)
        self.outText.grid(row=10, columnspan=3, padx=10)

        self.make_widgets()
        self.window.mainloop()

    def make_widgets(self):
        Button(self.window, text='Select All', font=("Arial", 12), width=10, command=self.select_all).grid(row=7, sticky=W, padx=10, pady=20)

        ButtonShow = tk.Button(text="Program HMI", width=20, font=("Arial", 14), command=self.actions)
        ButtonShow.grid(column=0, row=8, sticky=W, padx=10, pady=10)

    def verboseSleep(self,seconds):
        chars = len(str(seconds))
        s = "{:" + str(chars) + "d}s"
        cntdn = seconds + 1
        for i in range(seconds, 0, -1):
            sys.stdout.write(str("\b" * (chars+1)) + s.format(i))
            cntdn = cntdn - 1    
            msg_seconds = str(cntdn)
            self.outText.insert(tk.END, str(cntdn))
            pos = self.outText.index('end')
            float_pos = float(pos) - 1.0
            self.window.update_idletasks()
            sys.stdout.flush()
            self.window.after(1000)
            self.outText.delete(str(float_pos), "end")
            self.outText.insert(tk.END, "\n")     

        sys.stdout.write(str("\b" * (chars + 1)))   # Remove all evidence of our countdown timer.
        sys.stdout.flush()

    def select_all(self):
        self.action_updateIP = True

    def actions(self):
        ##########################################################    
        ipString = "192.168.0.20"    # Should be found by DHCP server.
        newIPString = "192.168.0.20" # Default.
        self.outText.delete(1.0, END)
        #print (self.action_updateIP)
        if self.action_updateIP == True:
            print("Updating IP address...")
            self.outText.insert(tk.END, "Updating IP address...")  
            self.window.update_idletasks()
            postData = {"bridge":{"enabled":False,"interfaces":["eth0"],"list":[]},"wifi":{"interfaces":[]},"version":0,"dns":{"servers":[],"search":[]},"hostname":"HMI-2133","interfaces":[{"name":"eth0","label":"WAN","mac_address":"00:30:d8:06:21:33","dhcp":False,"configured":True,"readonly":False,"virtual":False,"hidden":False,"actual_netmask":"255.255.255.0","actual_ip_address":ipString,"ip_address":newIPString,"netmask":"255.255.255.0","gateway_ip":"192.168.0.100"},{"name":"lo","mac_address":"00:00:00:00:00:00","dhcp":False,"configured":True,"readonly":True,"virtual":True,"hidden":True,"actual_netmask":"255.0.0.0","actual_ip_address":"127.0.0.1"}]}
            try:
            # We expect this to fail due to the connection being abruptly ended by the panel...
                r = requests.post(url = urlApi + 'network', data = json.dumps(postData), timeout = 10, headers={"content-type": "application/json"}, auth=authData, verify=False)
            except:
                print("Waiting 10s for panel to update.")
                self.outText.insert(tk.END, "Waiting 10s for panel to update" + "\n")  
                self.window.update_idletasks()
                self.verboseSleep(10)

            print("IP Updated.  New IP: ", newIPString)
            msg_newIP = "IP Updated. New IP: " + newIPString + "\n"
            self.outText.insert(tk.END, msg_newIP)  
            self.window.update_idletasks()       
            self.ipString = self.newIPString
        else:
            print (self.action_updateIP)
            print ('Done!' + "\n")
            self.outText.insert(tk.END, "Done! DONE! I'm all DONE!!" +"\n")

main()
exit(0)

【问题讨论】:

  • 能否请您发送其余代码?例如,window 是什么?
  • 我更新了窗口定义。整个代码很长。我希望我已经捕获了足够的信息。我可以根据需要更新。基本上,它需要做一些事情,等待10秒,做更多的事情。但在这 10 秒内,GUI 冻结。

标签: python python-3.x tkinter tk


【解决方案1】:

查看 tkinter 中的 after 方法。您基本上可以设置延迟执行任务的时间,而不会冻结 UI

类似

window = tk.Tk()

...

def do_actions():
   some_function_calls

...

# delay is given in milliseconds, so you need to multiply with 1000 to get in seconds
window.after(10000, do_actions)  

如果您希望将其创建为循环,则可以执行以下操作:

def do_actions():
    some_function_calls
    return window.after(10000, do_actions)

after_loop = do_actions()

那么如果你想取消after循环,可以调用window.after_cancel(after_loop)

【讨论】:

  • 这里的菜鸟问题:如果我没看错,你的例子是,每 10 秒执行一次 do_actions。但是那我如何摆脱那个循环呢? 10秒后,需要进行下一步“……做更多的事情”
  • 我的第一个示例只执行一次代码。但是我编辑了我的答案,向您展示了进行一次性延迟调用和每 10 秒不断调用自身的“后循环”(以及如何最终取消它)之间的区别
  • 我正在考虑这个,但是在我的 time.sleep 之后,我也在做一些事情,删除并添加一个新行。这将如何适合您的示例?什么是after_loop?这是您最初启动 do_actions() 函数的方式/位置吗?
  • @nearbyatom 如果您使用我们可以运行和检查的示例更新您的代码,我们可以解决您的大部分问题
  • @nearbyatom 你根本不应该使用 time.sleep。您应该将代码的各个组件拆分为单独的函数,并使用 .after() 方法适当地延迟它们。变量 after_loop 是对 after 函数调用的引用,如果您希望稍后再次取消循环,则需要传递该函数调用
【解决方案2】:

使用线程模块在python中使用多线程可以避免“无响应”问题。

如果你定义了任何函数,比如 combine() 被添加为按钮中的命令:

btn = Button(root, text="Click Me",command=combine)

由于窗口冻结,则编辑上述类型代码如下图:

import threading
btn = Button(root,text="Click Me", command=threading.Thread(target=combine).start()) 

看看参考:stackoverflow - avoid freezing of tkinter window using one line multithreading code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多