Hi, in my GUI I have a function with a while loop inside which is ruining when START button is pressed. The function drive a microcontroller continuously. I am wondering is it possible to terminate the running function when the STOP button is pressed ?
A while loop will block a GUI's mainloop, the while loop won't block if it running in a separate thread.
A flag variable that can be set outside of the while loop, can be checked in the while loop to decide if the while loop should continue looping or stop.
Yes exactly, this I want to implement.
A flag variable can be set outside of the while loop when the STOP button is pressed while the while loop is running in a separate thread. In side the while loop the flag variable decide the while loop should continue looping or stop.
Question how to run a while loop in a separate thread when START button in GI is pressed and how to assign the main GUI in another thread ?
I am using tkinter. Here is my code. There are two buttons on the GUI. STRAT and STOP. If I press START then a counter up to 30 should be printed on the shell but if I press STOP anytime before the counter reach 30 then the loop should terminate and back to the man loop of GUI. Where should I add thread ?
import time
from tkinter import *
root = Tk()
root.title("Loop Terminate")
time.sleep(0.5)
# Function button_stop
def button_stop():
# If the STOP button is pressed then terminate the loop
i = 1
# Function button_start
def button_start():
j = 1
while j <= int(30):
print("Loop Index = " + str(j))
time.sleep(0.5)
j = j+1
# Button START
button_start = Button(root, text = "START", padx=53, pady=20, command=button_start)
button_start.grid(columnspan=1, row=1,column=0)
# Button STOP
button_stop = Button(root, text = "STOP", padx=44, pady=20, command=button_stop)
button_stop.grid(row=2,column=0)
Please see
Namespace flooding with * imports
Here is an example of what you want to do
import threading
import time
import tkinter as tk
from dataclasses import dataclass, field
btn_state = {True: "normal", False: "disabled"}
@dataclass
class Task:
sleep_duration: float = field(default=0.5)
_run_loop: bool = field(default=False, init=False)
def start(self) -> None:
self._run_loop = True
for number in range(1, 31):
if not self._run_loop:
return
print(f"Loop index = {number}")
time.sleep(self.sleep_duration)
self._run_loop = False
def stop(self) -> None:
self._run_loop = False
def threading_start(self) -> None:
thread = threading.Thread(target=self.start)
thread.start()
@property
def running(self) -> bool:
return self._run_loop
class TaskFrame(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, *kwargs)
self.task = Task()
self._create_contrls()
self._create_layout()
self._create_binds()
self._update_btn_states()
def _create_contrls(self) -> None:
self.btn_start = tk.Button(master=self, text="START", padx=53, pady=20)
self.btn_stop = tk.Button(master=self, text="STOP", padx=44, pady=20)
def _create_layout(self) -> None:
self.btn_start.grid(columnspan=1, row=1, column=0, padx=2, pady=2)
self.btn_stop.grid(row=2, column=0, padx=2, pady=2)
def _create_binds(self) -> None:
self.btn_start.bind("<Button-1>", self._on_btn_start)
self.btn_stop.bind("<Button-1>", self._on_btn_stop)
def _on_btn_start(self, event: tk.Event) -> None:
if self.task.running:
return
self.task.threading_start()
def _on_btn_stop(self, event: tk.Event) -> None:
if not self.task.running:
return
self.task.stop()
def _update_btn_states(self) -> None:
self.btn_start.configure(state=btn_state[not self.task.running])
self.btn_stop.configure(state=btn_state[self.task.running])
self.after(500, self._update_btn_states)
def main():
app = tk.Tk()
app.title("Loop Terminate")
main_frame = TaskFrame(app)
main_frame.pack()
app.mainloop()
if __name__ == "__main__":
main()
Your program doesn't work because tkinter processes things like button presses while your program is not running. If you enter a loop, tkinter stops running and waits for the loop to complete. To know that a button was pressed you cannot be waiting for it to happen.
You can get around this by having a thread for the GUI and another for the background task, but is it required? Why are you waiting? Could you implement the logic in some other way. I would implement your example using tkinter.after().
import tkinter as tk
class Counter():
'''Periodically print counter value to stdout'''
def __init__(self, parent, end, start=0, increment=1, interval=1000):
self.parent = parent
self.start_value = start
self.end_value = end
self.increment = -increment if (end - start) * increment < 0 else increment
self.interval = interval
self.value = start
self.running = False
def start(self):
'''Start counter'''
self.value = self.start_value
self.running = True
self.doit()
def stop(self):
'''Stop counter'''
self.running = False
def doit(self):
'''Called periodically to incrementer counter and print value'''
if self.running:
print('Counter value =', self.value)
self.value += self.increment
self.running = (self.end_value - self.value) * self.increment > 0
if self.running:
self.parent.after(self.interval, self.doit)
root = tk.Tk()
root.title("Loop Terminate")
counter = Counter(root, 0, 10)
# Function button_stop
def button_stop():
counter.stop()
# Function button_start
def button_start():
counter.start()
# Button START
button_start = tk.Button(root, text = "START", padx=53, pady=20, command=button_start)
button_start.grid(columnspan=1, row=1,column=0)
# Button STOP
button_stop = tk.Button(root, text = "STOP", padx=44, pady=20, command=button_stop)
button_stop.grid(row=2,column=0)
root.mainloop()