Using tkinter button to stop multiple threads - python

I'm trying to use a button to stop all threads I've created in the for loop. Is there a method to stop threads?
Here's my code:
import threading
import time
from tkinter import *
tk= Tk()
class go(threading.Thread):
def __init__(self,c):
threading.Thread.__init__(self)
self.c = c
def run(self):
while 1 == 1 :
time.sleep(5)
print('This is thread: '+str(self.c))
for count in range(10):
thread = go(count)
thread.start()
btn1 = Button(tk, text="Stop All", width=16, height=5)
btn1.grid(row=2,column=0)
tk.mainloop()

You need to provide a condition to quit the while loop in the run() method. Normally threading.Event object is used:
def __init__(self, c):
...
self.stopped = threading.Event()
Then you need to check whether the Event object is set using Event.is_set():
def run(self):
while not self.stopped.is_set():
...
So to terminate the thread, just set the Event object. You can create a class method to do it:
def terminate(self):
self.stopped.set()
Below is a modified example based on yours:
import threading
import time
from tkinter import *
tk = Tk()
class go(threading.Thread):
def __init__(self,c):
threading.Thread.__init__(self)
self.c = c
self.stopped = threading.Event()
def run(self):
while not self.stopped.is_set():
time.sleep(5)
print(f'This is thread: {self.c}')
print(f'thread {self.c} done')
def terminate(self):
self.stopped.set()
threads = [] # save the created threads
for count in range(10):
thread = go(count)
thread.start()
threads.append(thread)
def stop_all():
for thread in threads:
thread.terminate()
btn1 = Button(tk, text="Stop All", width=16, height=5, command=stop_all)
btn1.grid(row=2, column=0)
tk.mainloop()

Related

How to create a tkinter quit button that works for all the functions in the class?

Suppose I have a class and it has a lot of functions in it.
I wanted to create a quit button using tkinter that will work for all the functions in the class.
Is there any easy way to do it? Here is what I have tried.
import threading
import tkinter as tk
import time
def threaded(fn):
def wrapper(*args, **kwargs):
thread = threading.Thread(target=fn, args=args, kwargs=kwargs)
thread.start()
e = threading.Event()
return thread,e
return wrapper
class MyClass:
#threaded
def func_to_be_threaded(self):
i = 10
while i > 1:
print("xyz")
time.sleep(2)
i -= 1
def stopbutton(thread,e):
def _quit():
print("Exiting...")
e.set()#set the flag that will kill the thread when it has finished
thread.join()
root.quit()
root.destroy()
root = tk.Tk()
QuitButton = tk.Button(master=root, text="Quit", command=_quit) # the quit button
QuitButton.pack(side=tk.BOTTOM)
root.mainloop()
my_obj = MyClass()
handle,e = my_obj.func_to_be_threaded()
stopbutton(handle,e)
It doesnt exit the function though
Your threaded function does not check e (the instance of Event()), so why do you expect that the threaded function will be terminated when e.set() is called?
You need to pass e to the threaded function and check whether it is set. If it is set, break out the while loop. I would suggest to pass e as one of the "kwargs" as below:
import threading
import tkinter as tk
import time
def threaded(fn):
def wrapper(*args, **kwargs):
# create the Event() object and save it in the kwargs
e = threading.Event()
kwargs["EventObject"] = e # choose any keyword you want
thread = threading.Thread(target=fn, args=args, kwargs=kwargs)
thread.start()
return thread, e
return wrapper
class MyClass:
#threaded
def func_to_be_threaded(self, *args, **kwargs): # added *args and **kwargs
i = 10
while i > 1 and not kwargs["EventObject"].is_set():
print("xyz")
time.sleep(2)
i -= 1
def stopbutton(thread, e):
def _quit():
print("Exiting...")
e.set()#set the flag that will kill the thread when it has finished
thread.join()
#root.quit() # calling root.destroy() is enough
root.destroy()
root = tk.Tk()
QuitButton = tk.Button(master=root, text="Quit", command=_quit) # the quit button
QuitButton.pack(side=tk.BOTTOM)
root.mainloop()
my_obj = MyClass()
handle, e = my_obj.func_to_be_threaded()
stopbutton(handle, e)

Using Tkinter button to stop an iterating function

I have created a GUI with a "stop" button. When the GUI is ran, another module is called that contains a while loop for a background function. The stop button would be used to pass a variable to the loop to stop it. However, when the module containing the loop is called the GUI freezes. I have considered using the library "threading" but cannot find any tkinter specific content. Any advice or small example of how you would create the code would help a lot.
Here is a basic GUI with 2 buttons that can start and stop a thread which increments a variable count.
I let you try it :
import tkinter as tk
import threading
import time
class GUI(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("test")
self.button_start = tk.Button(self, text="Start", command=self.start_thread)
self.button_start.pack()
self.button_stop = tk.Button(self, text="Stop", command=self.stop_thread)
self.button_stop.pack()
self.count = 0
self.continue_thread = True
def start_thread(self):
self.count = 0
self.continue_thread = True
self.t1 = threading.Thread(target = self.counter)
self.t1.daemon = True # With this parameter, the thread functions stops when you stop the main program
self.t1.start()
def stop_thread(self):
self.continue_thread = False
self.t1.join()
def counter (self):
while self.continue_thread:
print("i =", self.count)
self.count += 1
time.sleep(1)
if __name__ == "__main__":
app = GUI()
app.mainloop()
This is just a sample program to illustrate how to kill a running thread.
import threading
import time
def run():
while True:
print('thread running')
global stop_threads
if stop_threads:
break
if __name__=="__main__":
stop_threads = False
t1 = threading.Thread(target = run) ##background loop
t1.start()
time.sleep(1)
#while clicking on the button in GUI kill the thread like this
stop_threads = True
t1.join()
print('thread killed')

How to call a method into a ticker Button as command

In the tkinter GUI I created a run Button. I liked to click the button then it should start counting. But When I call the method into the ttk.Button as command. it does not work. In this code two class was created. run method was created in the first class and it will be call into the second class. Could you please kindly check the code. Thanks in advance.
from tkinter import *
import threading
import queue
from time import sleep
import random
from tkinter import ttk
class Thread_0(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
count = 0
while True:
count +=1
hmi.thread_0_update(count)
sleep(random.random()/100)
class HMI:
def __init__(self):
self.master=Tk()
self.master.geometry('200x200+1+1')
self.f=ttk.Frame(self.master,height = 100, width = 100, relief= 'ridge')
self.f.grid(row=1,column=1, padx=20, pady=20)
self.l0=ttk.Label(self.f)
self.l0.grid(row=1,column=1)
self.button=ttk.Button(self.master, text = 'run')
self.button.grid(row=2,column=2)
self.q0=queue.Queue()
self.master.bind("<<Thread_0_Label_Update>>",self.thread_0_update_e)
def start(self):
self.master.mainloop()
self.master.destroy()
#################################
def thread_0_update(self,val):
self.q0.put(val)
self.master.event_generate('<<Thread_0_Label_Update>>',when='tail')
def thread_0_update_e(self,e):
while self.q0.qsize():
try:
val=self.q0.get()
self.l0.config(text=str(val), font = ('Times New Roman', 15))
except queue.Empty:
pass
##########################
if __name__=='__main__':
hmi=HMI()
t0=Thread_0()
t0.start()
hmi.start()
You can use
Button( ..., command=t0.start )
See: start is without (). But you have to create t0 before hmi
if __name__ == '__main__':
t0 = Thread_0()
hmi = HMI()
hmi.start()
Full code which works for me
from tkinter import *
import threading
import queue
from time import sleep
import random
from tkinter import ttk
class Thread_0(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
count = 0
while True:
count +=1
hmi.thread_0_update(count)
sleep(random.random()/100)
class HMI:
def __init__(self):
self.master=Tk()
self.master.geometry('200x200+1+1')
self.f = ttk.Frame(self.master, height=100, width=100, relief='ridge')
self.f.grid(row=1, column=1, padx=20, pady=20)
self.l0 = ttk.Label(self.f)
self.l0.grid(row=1, column=1)
self.button = ttk.Button(self.master, text='run', command=t0.start)
self.button.grid(row=2, column=2)
self.q0 = queue.Queue()
self.master.bind("<<Thread_0_Label_Update>>", self.thread_0_update_e)
def start(self):
self.master.mainloop()
self.master.destroy()
#################################
def thread_0_update(self,val):
self.q0.put(val)
self.master.event_generate('<<Thread_0_Label_Update>>', when='tail')
def thread_0_update_e(self,e):
while self.q0.qsize():
try:
val = self.q0.get()
self.l0.config(text=str(val), font=('Times New Roman', 15))
except queue.Empty:
pass
##########################
if __name__ == '__main__':
t0 = Thread_0()
hmi = HMI()
hmi.start()

I'm using threading, so why get my gui all time frezzed O.o

Oke i got the problem days ago and someone helped me with treading but my code was really ugly (im totaly new to coding) now i try to make it better and on an smarter way but now my gui get a frezze all time.
i tryed to do it on the way like my last code but it dosent work this time.
What have i to do this time i cant understand it but want understand it.
some Helpful Tips and tricks ?
Or better ways to do it smart, faster, and more powerfull, or mybae the gui more Beautyfule ?
import time
import sys
from tkinter import *
import threading
root = Tk()
root.geometry("600x400")
global start
start = 1
def startUp():
user_input()
thr = threading.Thread(target=user_input)
thr.start()
def user_input():
global nameInput
global start
nameInput = textBox.get("1.0","end-1c")
start = 0
if start < 1:
while True:
apex = ApexLegends("APIKey")
player = apex.player(nameInput)
print("Gesamt Kills: " + player.kills + "\n" + 'Gesamt Damage: ' + player.damage)
time.sleep(3)
else:
print("stop")
anleitung=Label(text="Gib einen Spielernamen ein und drücke Start")
anleitung.pack()
textBox=Text(root, height=1, width=30)
textBox.pack()
startButton=Button(root, height=1, width=10, text="Start", command=lambda:startUp())
startButton.pack()
Tkinter isn't designed to be accessed by more than one thread. Here is an excellent answer by one of the guys who has a very deep understainding of how tcl & tk works (the libraries that tkinter depends on), explaining why this is so.
Callback to python function from Tkinter Tcl crashes in windows
This is the first of the two paragraphs in that answer:
Each Tcl interpreter object (i.e., the context that knows how to run a Tcl procedure) can only be safely used from the OS thread that creates it. This is because Tcl doesn't use a global interpreter lock like Python, and instead makes extensive use of thread-specific data to reduce the number of locks required internally. (Well-written Tcl code can take advantage of this to scale up very large on suitable hardware.)
def startUp():
user_input()
thr = threading.Thread(target=user_input)
thr.start()
This doesn't look right. You're calling user_input() in both the main thread and the child thread. If you only want it to run in the child thread, don't call it that first time.
def startUp():
thr = threading.Thread(target=user_input)
thr.start()
Hi #Trason I've played with your code and I suggest an oo approach.
In the code below I've try to adapt a functional script to your code.
First of all I've use a variable as
self.nameInput = tk.IntVar()
to store the user input on
tk.Entry(w, bg='white', textvariable=self.nameInput).pack()
I've use an Entry widget instead of Text but it should be the same.
Furthermore I use a class to manage thread start and stop operation.
Look, I changed your 'start' variable with 'check' because start is a reserved word
in python thread module.
I tried to recreate the functionality of your code.
Try to import and use your ApexLegends.
import tkinter as tk
import threading
import queue
import datetime
import time
class MyThread(threading.Thread):
def __init__(self, queue,nameInput):
threading.Thread.__init__(self)
self.queue = queue
self.nameInput = nameInput
self.check = True
def stop(self):
self.check = False
def run(self):
while self.check:
# apex = ApexLegends("APIKey")
#player = apex.player(self.nameInput.get())
x = "Gesamt Kills: " + "player.kills" + "\n" + 'Gesamt Damage: ' + "player.damage"+ "\n"
s = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
msg = "{} datetime: {} nameInput {}".format(x,s,self.nameInput.get())
time.sleep(3)
self.queue.put(msg)
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.master.protocol("WM_DELETE_WINDOW",self.on_close)
self.queue = queue.Queue()
self.my_thread = None
self.nameInput = tk.IntVar()
self.init_ui()
def init_ui(self):
self.f = tk.Frame()
w = tk.Frame()
tk.Label(w, text = "Gib einen Spielernamen ein und drücke Start").pack()
tk.Entry(w, bg='white', textvariable=self.nameInput).pack()
w.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
w = tk.Frame()
tk.Button(w, text="Start", command=self.startUp).pack()
tk.Button(w, text="Stop", command=self.stop_thread).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
self.f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
def startUp(self):
if (threading.active_count()!=0):
self.my_thread = MyThread(self.queue,self.nameInput)
self.my_thread.start()
self.periodiccall()
def stop_thread(self):
if(threading.active_count()!=1):
self.my_thread.stop()
def periodiccall(self):
self.checkqueue()
if self.my_thread.is_alive():
self.after(1, self.periodiccall)
else:
pass
def checkqueue(self):
while self.queue.qsize():
try:
ret = self.queue.get(0)
msg = "%s"%(ret)
print(msg)
except queue.Empty:
pass
def on_close(self):
if(threading.active_count()!=0):
if self.my_thread is not None:
self.my_thread.stop()
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()

python script runs after x seconds but when tkinter code inserted it runs only once

What is the best way to repeatedly execute a function every x seconds in Python?
i have tried the solutions posted on above links but none helped me to achieve the desired result.
the code above prints "Doing stuff..." on console many times as per seconds mentioned i.e. 5 but when i add the line of window() which is a tkinter code for displaying a message the code runs just once and not anytime again .
please help . i want to run the tkinter code again and again on specific time as per system clock but now i am just trying to execute it after x amounts of seconds .
any help would really mean a lot to me.Thanks
search for tkinter .after method.
This will alow you to run a command every x seconds.
The problem your tkinter code runs only once, is since its set up first and then goes into a loop, (root.mainloop()) , hence never returning to your code to display anything again.
Example : tkinter: how to use after method
I think you need thread and queue...let me show a little demo.
I've set time.sleep(1) per seconds in the thead class.
In this matter you get two advantages, first repet your funcion any times
you desire and second your program never freeze it self.
import tkinter as tk
import threading
import queue
import datetime
import time
class MyThread(threading.Thread):
def __init__(self, queue,):
threading.Thread.__init__(self)
self.queue = queue
self.check = True
def stop(self):
self.check = False
def run(self):
while self.check:
x = "Doing stuff.. "
y = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
msg = x+y
time.sleep(1)
self.queue.put(msg)
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.master.protocol("WM_DELETE_WINDOW",self.on_close)
self.queue = queue.Queue()
self.my_thread = None
self.init_ui()
def init_ui(self):
self.f = tk.Frame()
w = tk.Frame()
tk.Button(w, text="Start", command=self.launch_thread).pack()
tk.Button(w, text="Stop", command=self.stop_thread).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
self.f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
def launch_thread(self):
if (threading.active_count()!=0):
self.my_thread = MyThread(self.queue)
self.my_thread.start()
self.periodiccall()
def stop_thread(self):
if(threading.active_count()!=1):
self.my_thread.stop()
def periodiccall(self):
self.checkqueue()
if self.my_thread.is_alive():
self.after(1, self.periodiccall)
else:
pass
def checkqueue(self):
while self.queue.qsize():
try:
ret = self.queue.get(0)
msg = "%s"%(ret)
print(msg)
except queue.Empty:
pass
def on_close(self):
if(threading.active_count()!=1):
self.my_thread.stop()
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()

Categories

Resources