Running a looping function and killing it when required Tkinter - python

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

Related

Python tKinter hanging

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...")

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.

Call root.destroy() from a child thread

The program below simply tests whether I can call root.destroy() of tkinter in a child thread. Though no error raised, I see a lot of posts in stackoverflow reminding people not to use tkinter in a child thread, so I wonder if calling root.destroy() in a child thread is not a good idea too, though I used tkinter in the main thread?
import tkinter
from tkinter import ttk
import time
import _thread
def callBackFunc():
global LOOP_ACTIVE
LOOP_ACTIVE = False
root = tkinter.Tk()
root.geometry('200x100')
chkValue = tkinter.BooleanVar()
chkValue.set(False)
chk = ttk.Checkbutton(root, variable=chkValue, text='Click me', command=callBackFunc)
chk.grid(column=0, row=0)
def loop_function():
global LOOP_ACTIVE
LOOP_ACTIVE = True
while LOOP_ACTIVE:
print(chk.state())
time.sleep(5)
print("you clicked me")
root.destroy()
_thread.start_new_thread(loop_function, ())
root.mainloop()
print("printed after mainloop ends")
You can avoid calling destroy() in the child thread by periodically checking the flag in the main GUI thread by using the universal after() widget method to schedule them to occur.
In the code below I've added a check_loop_status() function that does that, and it will call root.destroy() if it discovers that the LOOP_ACTIVE flag is no longer set. To keep on process going, it schedules another call to itself before returning (which of course will never happen if destroy() got called).
Since this all that happens in the main thread, any call to destroy() will be fine.
import tkinter
from tkinter import ttk
import time
import _thread
DELAY = 1000 # milliseconds
root = tkinter.Tk()
root.geometry('200x100')
chkValue = tkinter.BooleanVar(value=False)
def callBackFunc():
global LOOP_ACTIVE
print("button was clicked")
LOOP_ACTIVE = False
def check_loop_status():
global LOOP_ACTIVE
if not LOOP_ACTIVE:
root.destroy() # OK in this function.
root.after(DELAY, check_loop_status) # Check again after delay.
chk = ttk.Checkbutton(root, variable=chkValue, text='Click me', command=callBackFunc)
chk.grid(column=0, row=0)
def loop_function():
global LOOP_ACTIVE
while LOOP_ACTIVE:
print(f'Checkbutton state: {chk.state()}')
time.sleep(5)
LOOP_ACTIVE = True
_thread.start_new_thread(loop_function, ())
check_loop_status() # Start periodic checking.
root.mainloop()
print("printed after mainloop ends")

Python Multithreading - not running simultaneously

im trying to learn python. But i have problems with the threading. First i failed in the "Proces" class because i putted the loop on the wrong place and my program newer returned from the other class.
But now i think all is correct and it still does not work. I need to have a GUI where i want to be able to write my conditions via text entries and i need another class "Proces" that will do stuff, checking status ower internet and so on constantly or in a specified interval...
The Problem is that my tkinter GUI is freezing after pressing something
here is my GUI.py file:
import tkinter as tk
from Proces import Proces
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
button = tk.Button(frame, text="QUIT", fg="red",command=quit).pack(side=tk.LEFT)
pr = Proces()
print("\nGUI: proces init...")
pr.start()
print("\nGUI: Start ended")
root.mainloop()
here is the Proces.py file:
import time, threading
class Proces(threading.Thread):
def loop(self):
while True:
time.sleep(2)
print("\nProces: looping")
def __init__(self):
threading.Thread.__init__(self)
print("\nProces: Starting proces")
time.sleep(2)
def run(self):
self.deamon = True
print("\nProces: Starting loop")
self.loop()
*This is the output: *
Proces: Starting proces
GUI: proces init...
Proces: Starting loop
GUI: Start ended
Proces: looping
Proces: looping
Proces: looping
Proces: looping
*But the GUI of the tkinter does not react.*
How should i do this kind of task?
Thank you for your help, advice and answer
I think you don't have problem about starting thread etc. However, you should be able to control your thread, meaning your thread should return based on some condition. It seems you have a button to quit. I assume you want to finish the process by clicking the button.
To do that, when you click to button, your main thread should pass a variable to Process in order to break your while loop, which is your thread basically.
Here is a code that you can work on.
import tkinter as tk
import time
import threading
class MainWindow:
def __init__(self, master):
self.master = master
self.quit_button = tk.Button(self.master, text="QUIT", command=lambda:self.quit(), width=20)
self.quit_button.pack(side=tk.LEFT)
self.process = None
print("\nGUI: process init...")
self.start_task()
def quit(self):
self.process.stop_process = True
print("\nGUI: Start ended")
def start_task(self):
self.process = Process()
self.process.start()
class Process(threading.Thread):
def loop(self):
while True:
if not self.stop_process:
time.sleep(2)
print("\nProcess: looping")
else:
print("\nProcess: looping ended")
return
def __init__(self):
threading.Thread.__init__(self)
self.stop_process = False
print("\nProcess: Starting proces")
time.sleep(2)
def run(self):
self.deamon = True
print("\nProcess: Starting loop")
self.loop()
if __name__ == '__main__':
root = tk.Tk()
app = MainWindow(master=root)
root.mainloop()
So you start yout tkinter, which is your main thread. Then iniating another class within your main thread, which is inherits the thread. So you have two seperate thread that working. When you clicked to "quit" button, you pass variable stop_process, which breaks the loop and return, meaning ending your thread. Your main thread is still alive for running your tkinter window.
I hope it will help

GUI Button hold down - tkinter

I'm trying to do a GUI in python to control my robotic car. My question is how I do a function that determine a hold down button. I want to move the car when the button is pressed and held down and stop the car when the button is released.
from Tkinter import *
hold_down = False
root = Tk()
def button_hold(event):
hold_down=true
while hold_down== True:
print('test statement')
hold_down = root.bind('<ButtonRelease-1>',stop_motor)
def stop_motor(event):
hold_down= False
print('button released')
button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<Button-1>',button_forward)
root.mainloop()
I'm trying to simulate what I found in this answer
I try to do it in a while loop with a boolean. When the user presses the button the boolean changes to True and code enters the while loop. When user releases the button the boolean changes to False and code exits from loop but in this code the boolean stay always true no matter if I released the button or not.
Edit: I want a function to be called until a condition occurs.The function to be called is hold_down() and the condition to check is the button is released.
Update: I found a way to make it work.
Set a flag when the button is pressed, unset the flag when the button is released. There's no need for a loop since you're already running a loop (mainloop)
from Tkinter import *
running = False
root = Tk()
def start_motor(event):
global running
running = True
print("starting motor...")
def stop_motor(event):
global running
print("stopping motor...")
running = False
button = Button(root, text ="forward")
button.pack(side=LEFT)
button.bind('<ButtonPress-1>',start_motor)
button.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()
Assuming that you actually want to do something while the key is pressed, you can set up an animation loop using after. For example, to call a print statement once a second while the button is pressed you can add a function that does the print statement and then arranges for itself to be called one second later. The stop button merely needs to cancel any pending job.
Here's an example. The main difference to the original code is the addition of a move function. I also added a second button to show how the same function can be used to go forward or backward.
from Tkinter import *
running = False
root = Tk()
jobid = None
def start_motor(direction):
print("starting motor...(%s)" % direction)
move(direction)
def stop_motor():
global jobid
root.after_cancel(jobid)
print("stopping motor...")
def move(direction):
global jobid
print("Moving (%s)" % direction)
jobid = root.after(1000, move, direction)
for direction in ("forward", "backward"):
button = Button(root, text=direction)
button.pack(side=LEFT)
button.bind('<ButtonPress-1>', lambda event, direction=direction: start_motor(direction))
button.bind('<ButtonRelease-1>', lambda event: stop_motor())
root.mainloop()
You might want to try the repeatinterval option. The way it works is a button will continually fire as long as the user holds it down. The repeatinterval parameter essentially lets the program know how often it should fire the button if so. Here is a link to the explanation:
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/button.html
Search in-page for "repeatinterval".
Another name for this parameter is repeatdelay.
Building on Bryan Oakley's answer of using flags to simulate a press and hold button. The problem is that you can't have any while loops in your tkinter application to say while running move car forward.
Which is why I suggest using threads. This way you can have a while loop running in the background checking if the car should be moving foward.
from threading import Thread
from Tkinter import *
running = False
root = Tk()
def start_motor(event):
global running
print("starting motor...")
running = True
def stop_motor(event):
global running
running = False
print("stopping motor...")
def move_forward():
while True: # Thread will run infinitely in the background
if running:
print("Car is moving forward...\n")
button = Button(root, text ="forward")
button.pack(side=LEFT)
button.bind('<ButtonPress-1>',start_motor)
button.bind('<ButtonRelease-1>',stop_motor)
# Create and start the new thread
t = Thread(target = move_forward, args = ())
t.start()
root.mainloop()
Try this...
from Tkinter import *
root = Tk()
global hold_down
def button_hold(event):
hold_down = True
while hold_down:
print('test statement')
def stop_motor(event):
hold_down = False
print('button released')
button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<Button-1>',button_hold)
root.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()
# Danny Try the following code:
def stop_motor(event):
print('button released')
return False
This answer run print 'test statement' one time. The while loop run one time when the button is pressed.
# Bryan Oakley Set a flag when the button is pressed, unset the flag when the button is released. There's no need for a loop since you're already running a loop (mainloop)
from Tkinter import *
running = False
root = Tk()
def start_motor(event):
global running
running = True
print("starting motor...")
def stop_motor(event):
global running
print("stopping motor...")
running = False
button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<ButtonPress-1>',start_motor)
root.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()
This answer above stays in a infinite loop when the button is pressed.
# Joseph FarahYou might want to try the repeatinterval option. The way it works is a button will continually fire as long as the user holds it down. The repeatinterval parameter essentially lets the program know how often it should fire the button if so. Here is a link to the explanation:
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/button.html
Search in-page for "repeatinterval".
Another name for this parameter is repeatdelay.
I set the repeat interval in the parameter option for the button widget but it doesn't repeat the command.
Thanks for all the answer . Still looking to solve this problem.

Categories

Resources