Python Forum
[Tkinter] How to terminate a loop in python with button in GUI
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] How to terminate a loop in python with button in GUI
#1
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 ?
Reply
#2
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.
Reply
#3
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 ?
Reply
#4
You don't mention which GUI framework you are using, the following links give an idea of a way to use threads with tkinter and wxpython:
[Tkinter] How to deal with code that blocks the mainloop, freezing the gui
[WxPython] How to deal with code that blocks the mainloop, freezing the gui
Reply
#5
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 ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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)
Reply
#6
Please see Namespace flooding with * imports
Here is an example of what you want to do
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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()
Reply
#7
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().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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()
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [PyQt] Loop triggered by button help Purple0 1 2,890 May-17-2020, 02:57 AM
Last Post: deanhystad
  [Tkinter] Binding Entry box to <Button-3> created in for loop iconit 5 7,110 Apr-22-2020, 05:47 AM
Last Post: iconit
  [PySimpleGui] How to alter mouse click button of a standard submit button? skyerosebud 3 5,956 Jul-21-2019, 06:02 PM
Last Post: FullOfHelp
  [Tkinter] loop function when called from tkinter button click WantedStarling 5 10,218 Jul-13-2018, 06:12 PM
Last Post: nilamo

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020