tkinter window not responding

Viewed 763

I have a GUI that will do stuff, then reboot the panel, then wait for the panel to finish rebooting. The idea is for the user to press "PROGRAM HMI" which will execute the actions(). My big problem is during the reboot, the GUI needs to wait for 10 seconds for the HMI to reboot. During these 10 seconds, the GUI freezes, and I get a "Window not responding" message up top. The code is executing as I can see it in the terminal and it GUI eventually unfreezes. I've read that I'm not supposed to use the time.sleep() here. But how should I write this so it doesn't freeze? I'm using 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)
2 Answers

Look into the after method in tkinter. You can basically set a delay for when a task will be executed with this, without freezing the UI

So something like

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)  

If you wish to create it as a loop instead, you could do the following:

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

after_loop = do_actions()

Then if you want to cancel the after loop, you can call window.after_cancel(after_loop)

'Not responding' problem can be avoided using Multithreading in python using the thread module.

If you've defined any function, say combine() to be added as command in a button:

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

Due to which the window is freezing, then edit the above type code as shown below:

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

Have a look for reference: stackoverflow - avoid freezing of tkinter window using one line multithreading code

Related