Python tKinter hanging - python

Why does in the code below button1 hang until the time.sleep(10) has completed.
I can only assume tKinter is waiting for the click event to finish before updating it's paint function.
I want on button1 click the state to change to DISABLED as in the code straight away, not when mainformbutton1press() has finished.
I have put time.sleep(10) to mimic rest of code functions - but the actual programme will be many minutes instead.
EDIT! - sleep is just there to show how tkinter hangs. My real programme has lots more code and no sleep function - and it takes a long time to process data with the hung GUI as mentioned. No more sleep suggestions please :)
import tkinter as tk
from tkinter import ttk
from tkinter.constants import DISABLED, NORMAL
import time
# ==================================================
class App:
def __init__(self, tk, my_w):
self.button1 = tk.Button(my_w, text="START", width=34, command = self.mainformbutton1press)
self.button1.grid(columnspan=3, row=6, column=1,padx=10,pady=20, ipadx=20, ipady=20)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def mainformbutton1press(self):
self.button1.config(text="PLEASE WAIT...")
self.button1['state'] = DISABLED
# DO REST OF PROCESSING
# TO MIMIC THIS:
time.sleep(10)
print("doing...")
# ==================================================
if __name__ == "__main__":
my_w = tk.Tk()
my_w.geometry("430x380")
my_w.resizable(False, False)
app = App(tk, my_w)
my_w.mainloop() # Keep the window open

Tk.mainloop is a sort of while loop. time.sleep() stops the loop for a particular period of time. That makes the window unresponsive. You might use .after function:
class App:
def __init__(self, tk, my_w):
self.my_w=my_w
....
def continue_next(self):
print("Doing")
....
def mainformbutton1press(self):
self.button1.config(text="PLEASE WAIT...")
self.button1['state'] = DISABLED
# DO REST OF PROCESSING
# TO MIMIC THIS:
self.my_w.after(10000,self.continue_next)

The only change you need to make to your code is to insert an update to your button.
The 10 second delay might need to be shortened (10 seconds is a long time to wait)
self.button1.config(text="PLEASE WAIT...")
self.button1['state'] = DISABLED
# INSERT UPDATE HERE
self.button1.update()
# DO REST OF PROCESSING
# TO MIMIC THIS:
time.sleep(1)
print("doing...")

Related

Running a looping function and killing it when required Tkinter

I am using tkinter to build my application for testing something. I have a start button which starts a function myFunc() which is defined something like this:
def myFunc():
for i in range(20):
# do something which takes 0.5sec to execute
And I have another button which should stop this function when pressed, even if its not fully executed.
There are 2 problems here. First, the loop in myFunc() takes 0.5*20=10seconds to execute, which will freeze my application for that long. I can use threading to overcome this but the second problem is if I use someThread.join() as the command for the stop button, it will still wait until the function is executed. Also, although the GUI is not frozen while myFunc() is running, it hangs when I press stop button (because now its waiting until the function completes). So I need to kill the thread and stop executing that function AS SOON AS the stop button is pressed. This is a necessity.
I read everywhere on the internet that its not recommended to kill a thread. How do I do this elegantly? I dont want any problems or freezing. I just want the function to stop executing myFunc() the instant I press the stop button.
Full source code:
import tkinter as tk
from tkinter import ttk
import time
from threading import Thread
def myFunc():
for i in range(20):
time.sleep(0.5)
print('DONE')
def start():
someThread.start()
def stop():
someThread.join()
root.quit()
root = tk.Tk()
root.title('My app')
someThread = Thread(target=myFunc)
ttk.Button(root, text='Start', command=start).pack()
ttk.Button(root, text='Stop', command=stop).pack()
root.mainloop()
We would need to define a variable such as stopped which will tell the code if to stop the for loop or not. I have also added print('hi') and print('hi') to check if def stop() is triggered or not and if the for loop stopped or not.
Code
import tkinter as tk
from tkinter import ttk
import time
from threading import Thread
def myFunc():
global stopped
for i in range(20):
print('hi')
if stopped == True: #checks if stopped is True or False
break
time.sleep(.5)
print('DONE')
def start():
someThread.start()
def stop():
global stopped
stopped = True #makes stopped True which will stop the for loop/thread
print('triggered')
root = tk.Tk()
root.title('My app')
someThread = Thread(target=myFunc)
ttk.Button(root, text='Start', command=start).pack()
ttk.Button(root, text='Stop', command=stop).pack()
stopped = False #stopped is false so the for loop can run
root.mainloop()
hi and triggered
hi
hi
hi
triggered #pressed stop
hi
DONE

Threading and Tkinter - How to use the threading module with my simple example?

I don't understand how to use the threading module properly. In this example I have two tkinter widgets, a button and a progress bar. The progress bar (configured in indeterminate mode) has to be active when the user pushes the button, and when the task is completed, the progress bar has to be stopped.
import tkinter as tk
from tkinter import ttk
import threading, ipaddress
class MainWindow:
def __init__(self):
self.parent=tk.Tk()
self.parent.geometry("786x524+370+100")
self.parent.title("Test")
self.parent.configure(background="#f0f0f0")
self.parent.minsize(786, 524)
self.ProBar=ttk.Progressbar(self.parent, mode="indeterminate")
self.ProBar.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.StartButton=ttk.Button(self.parent, text="Start", command=self.MyHeavyTask)
self.StartButton.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.parent.mainloop()
# my start function:
def Start(self):
self.ProBar.start(4)
self.MyHeavyTask()
self.ProBar.stop()
# my real start function. it's just an example, it needs time to be completed:
def MyHeavyTask(self):
ls=[]
obj=ipaddress.ip_network("10.0.0.0/8")
for obj in list(obj.hosts()):
print(obj.exploded)
# start my test:
if __name__=="__main__":
app=MainWindow()
This code has an issue, it can't run the function "MyHeavyTask" and at the same time keep active the progress bar widget. to solve it, I tried to put "MyHeavyTask" in an indipendent thread changing the line 17 with this one:
self.StartButton=ttk.Button(self.parent, text="Start",
command=threading.Thread(target=self.MyHeavyTask).start())
unfortunately this solution doesn't work. when I press the button, nothig happens…why? What is the right way to use the threading module in my example?
You can add a method to the class
def Get_Input(self):
message = input(">")
if message:
send_message(message)
and add in init class
threading.Thread(target=self.Get_Input, args=(,)).start()
Please note :
If you passing one argument, you need to use
threading.Thread(target=self.Get_Input, args=(var1,)).start()
Unlike common sense :)
Here a runnable example similar to the code in your question, that shows a way to run a background task and keep a ttk.Progressbar active simultaneously. It does this by using the universal after() widget method to repeatedly schedule calls to a method that checks whether the background task is running and updates the progress bar if it is. It also disables and re-enables the Start button appropriately so the task can't be start again while it's running.
Note I strongly suggest you read and start following the PEP 8 - Style Guide for Python Code.
from random import randint
import tkinter as tk
from tkinter import ttk
import threading
from time import sleep
class MainWindow:
def __init__(self):
self.parent = tk.Tk()
self.parent.geometry("786x524+370+100")
self.parent.title("Test")
self.parent.configure(background="#f0f0f0")
self.parent.minsize(786, 524)
self.task = threading.Thread(target=self.my_heavy_task)
self.pro_bar = ttk.Progressbar(self.parent, mode="indeterminate")
self.pro_bar.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.start_btn = ttk.Button(self.parent, text="Start", command=self.start)
self.start_btn.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.parent.mainloop()
def check_thread(self):
if self.task.is_alive():
self.pro_bar.step() # Update progressbar.
self.parent.after(20, self.check_thread) # Call again after delay.
else:
self.pro_bar.stop()
self.start_btn.config(state=tk.ACTIVE)
def start(self):
"""Start heavy background task."""
self.start_btn.config(state=tk.DISABLED)
self.task.start()
self.pro_bar.start()
self.check_thread() # Start checking thread.
def my_heavy_task(self):
"""Slow background task."""
for obj in (randint(0, 99) for _ in range(6)):
print(obj)
sleep(.5)
if __name__=="__main__":
app = MainWindow()
Does this help?
start_thread = MainWindow()
run_test = threading.Thread(None, start_thread.start)
run_test.start()
# start my test:
if __name__=="__main__":
app=MainWindow()

How to have a progress bar display while a function is being processed (tkinter) [duplicate]

I would like to implement a progress bar in Tkinter which fulfills the following requirements:
The progress bar is the only element within the main window
It can be started by a start command without the need of pressing any button
It is able to wait until a task with unknown duration is finished
The indicator of the progress bar keeps moving as long as the task is not finished
It can be closed by a stop command without the need of pressing any stop bar
So far, I have the following code:
import Tkinter
import ttk
import time
def task(root):
root.mainloop()
root = Tkinter.Tk()
ft = ttk.Frame()
ft.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD = ttk.Progressbar(ft, orient='horizontal', mode='indeterminate')
pb_hD.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD.start(50)
root.after(0,task(root))
time.sleep(5) # to be replaced by process of unknown duration
root.destroy()
Here, the problem is that the progress bar does not stop after the 5s are over.
Could anybody help me finding the mistake?
Once the mainloop is active, the script wont move to the next line until the root is destroyed.
There could be other ways to do this, but I would prefer doing it using threads.
Something like this,
import Tkinter
import ttk
import time
import threading
#Define your Progress Bar function,
def task(root):
ft = ttk.Frame()
ft.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD = ttk.Progressbar(ft, orient='horizontal', mode='indeterminate')
pb_hD.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD.start(50)
root.mainloop()
# Define the process of unknown duration with root as one of the input And once done, add root.quit() at the end.
def process_of_unknown_duration(root):
time.sleep(5)
print 'Done'
root.destroy()
# Now define our Main Functions, which will first define root, then call for call for "task(root)" --- that's your progressbar, and then call for thread1 simultaneously which will execute your process_of_unknown_duration and at the end destroy/quit the root.
def Main():
root = Tkinter.Tk()
t1=threading.Thread(target=process_of_unknown_duration, args=(root,))
t1.start()
task(root) # This will block while the mainloop runs
t1.join()
#Now just run the functions by calling our Main() function,
if __name__ == '__main__':
Main()
Let me know if that helps.

tkinter Window unresposive while getting data from API

I have a tkinter program, and it gets some data from API. When the internet connection is slow, it can take a really long time. The tkinter window becomes unresponsive. To stop it I have to force close the program. How do I avoid this?
I have a function which is called that retrieves data from the API. I want to be able to stop that function while still keeping the program and tkinter window running.
After uploading the image, I upload that to the API(which obviously takes a bit of time), and after getting the response, pastes the relevant info retrieved into a txt file and opens it.
Use a thread for the call and a recursive event check until the call is done.
from threading import Thread
from time import sleep
import tkinter as tk
class App(tk.Tk):
def api_call(self, time):
sleep(time)
print('API call done')
self.api_result = 10
def on_button_click(self):
self.button.config(state=tk.DISABLED)
self.api_thread = Thread(target=self.api_call, args=(3,))
self.api_thread.start()
self.button_update()
def __init__(self):
tk.Tk.__init__(self)
self.api_thread = None
self.api_result = None
self.button = tk.Button(self, text='Send', command=self.on_button_click)
self.button.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
def button_update(self):
if self.api_result is not None:
if self.api_thread.is_alive():
self.api_thread.join(timeout=2)
self.button.config(state=tk.NORMAL)
self.api_thread = None
self.api_result = None
else:
self.button.after(500, self.button_update)
if __name__ == '__main__':
aplication = App()
aplication.mainloop()

tkinter user non closeable window

I am trying to write a program where i have removed the main window close options and providing a exit button to the user to close the program.
After pressing i need to do some processing in the background which would be time consuming, i don't want user to close the program while that is going on accidentally. Is there a way to remove all buttons from the messagebox which is presented ?
import tkinter as tk
from win32api import GetSystemMetrics
from tkinter import messagebox
def on_closing():
pass
def exit():
messagebox.showinfo("Wait", "Please wait for background process to complete")
root.destroy()
root = tk.Tk()
root.resizable(width=False, height=False)
root.protocol("WM_DELETE_WINDOW", on_closing)
width = GetSystemMetrics(0)
height = GetSystemMetrics(1)
root.geometry('{}x{}'.format(width,height))
exitButton = tk.Button(root,text="Exit",width=15,command=exit)
exitButton.grid(row=0,column=1,padx=6,pady=6)
root.overrideredirect(True)
root.mainloop()
In the Background : There are some files generated on user's machine and i would like to archive them using python library. The files can go maybe sometime at 1GB so i think it would take more amount of time, if the laptop on which it is run is having very less computing power. And this would be the case for my base hence i want them just to wait until that popup is closed. This i can define in user manual.
I am not sure what work you want to do, but for this example I'm doing a work of printing something and then sleeping and then printing it. So this takes about 20 seconds. And in those 20 seconds you wont be able to exit the GUI.
import tkinter as tk
from win32api import GetSystemMetrics
from tkinter import messagebox
import time
import threading
def on_closing():
if started == False: #if work is not going on, then quit
root.destroy()
else: # else show the message.
messagebox.showinfo("Wait", "Please wait for background process to complete")
def work():
global started
started = True #mentioning that the work started
print('Hey')
time.sleep(5)
print('There')
time.sleep(5)
print('Whats Up')
time.sleep(5)
print('Cool?')
time.sleep(5)
started = False #mentioning that the work stopped
started = False #initially work is not started
root = tk.Tk()
root.resizable(width=False, height=False)
root.protocol("WM_DELETE_WINDOW", on_closing)
width = GetSystemMetrics(0)
height = GetSystemMetrics(1)
root.geometry('{}x{}'.format(width,height))
exitButton = tk.Button(root,text="Exit",width=15,command=on_closing)
exitButton.grid(row=0,column=1,padx=6,pady=6)
Button = tk.Button(root,text="Work",width=15,command=threading.Thread(target=work).start)
Button.grid(row=1,column=1,padx=6,pady=6)
# root.overrideredirect(True)
root.mainloop()
Here, started acts like a flag. You have to set it to True before starting your work and set it to False after it ends.
You can ignore the fact that I created a new button and used threading, it was just to simulate to you an example of work done. Threading helps the GUI to not freeze. Though I'm not sure if this will work with root.overrideredirect(True), but I think you can get rid of it.

Categories

Resources