Posts: 30
Threads: 12
Joined: Aug 2021
Sep-09-2021, 07:58 AM
(This post was last modified: Sep-09-2021, 11:39 AM by Yoriz.
Edit Reason: Added prefix
)
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 ?
Posts: 2,168
Threads: 35
Joined: Sep 2016
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.
Posts: 30
Threads: 12
Joined: Aug 2021
Sep-09-2021, 08:27 AM
(This post was last modified: Sep-09-2021, 08:32 AM by Yoriz.
Edit Reason: removed unnecessary quote of previous post
)
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 ?
Posts: 2,168
Threads: 35
Joined: Sep 2016
Posts: 30
Threads: 12
Joined: Aug 2021
Sep-09-2021, 11:16 AM
(This post was last modified: Sep-09-2021, 11:38 AM by Yoriz.
Edit Reason: removed unnecessary quote of previous post
)
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)
Posts: 2,168
Threads: 35
Joined: Sep 2016
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()
Posts: 6,779
Threads: 20
Joined: Feb 2020
Sep-09-2021, 06:33 PM
(This post was last modified: Sep-09-2021, 06:33 PM by deanhystad.)
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()
|