How do you create a TKinter GUI stop button to break an endless loop?

So I have a Tkinter GUI with two simple options, a start and stop button. I have defined the GUI layout:

from Tkinter import *

def scanning():
    while True:
        print "hello"

root = Tk()
root.title("Title")
root.geometry("500x500")

app = Frame(root)
app.grid()

      

Here the "Start" button starts scanning an infinite loop, and the "Stop" button should be interrupted when pressed:

start = Button(app, text="Start Scan",command=scanning)
stop = Button(app, text="Stop",command="break")

start.grid()
stop.grid()

      

However, when I press the start button, it is always pushed downward (assuming due to an infinite loop). But I cannot press the Stop button to exit the while loop.

+4


source to share


3 answers


You cannot start the loop while True:

on the same thread that the Tkinter event loop is running on. This blocks the Tkinter loop and causes the program to freeze.

For a simple solution, you can use Tk.after

to start the process in the background every second or so. Below is a script to demonstrate:



from Tkinter import *

running = True  # Global flag

def scanning():
    if running:  # Only do this if the Stop button has not been clicked
        print "hello"

    # After 1 second, call scanning again (create a recursive loop)
    root.after(1000, scanning)

def start():
    """Enable scanning by setting the global flag to True."""
    global running
    running = True

def stop():
    """Stop scanning by setting the global flag to False."""
    global running
    running = False

root = Tk()
root.title("Title")
root.geometry("500x500")

app = Frame(root)
app.grid()

start = Button(app, text="Start Scan", command=start)
stop = Button(app, text="Stop", command=stop)

start.grid()
stop.grid()

root.after(1000, scanning)  # After 1 second, call scanning
root.mainloop()

      

Of course, you might want to refactor this code into a class and running

be an attribute of it. Also, if your program gets complicated, it might be helpful to look at a threading

module
in Python so that your function scanning

can run on a separate thread.

+8


source


Here's another solution with the following benefits:

  • No need to manually create separate streams

  • Doesn't use calls Tk.after

    . Instead, the original continuous looping code style is retained. The main benefit of this is that you don't have to manually specify the few milliseconds that determine how often your code is executed inside the loop, it just runs as often as your hardware allows.

Note: I've only tried this with python 3 , not python 2. I suppose the same should work in python 2 too, I just don't know 100% for sure.

For the UI code and start / stop logic, I will use basically the same code as in iCodez's answer. The important difference is that I am assuming that we will always work with a loop, but in this loop we will decide what to do based on which buttons were pressed recently:



from tkinter import *

running = True  # Global flag
idx = 0  # loop index

def start():
    """Enable scanning by setting the global flag to True."""
    global running
    running = True

def stop():
    """Stop scanning by setting the global flag to False."""
    global running
    running = False

root = Tk()
root.title("Title")
root.geometry("500x500")

app = Frame(root)
app.grid()

start = Button(app, text="Start Scan", command=start)
stop = Button(app, text="Stop", command=stop)

start.grid()
stop.grid()

while True:
    if idx % 500 == 0:
        root.update()

    if running:
        print("hello")
        idx += 1

      

In this code, we are not calling root.mainloop()

to keep the tkinter GUI updated all the time. Instead, we manually update it as often (in this case, every 500 iteration cycles).

In theory, this means that we cannot instantly stop the cycle as soon as we press the Stop button. For example, if at the moment when we press the Stop button we are at iteration 501, this code will continue to loop until 1000 is hit. Thus, the disadvantage of this code is that in theory we have less flexible GUI (but it won't be noticeable if the code inside your loop is fast). In turn, we get the code inside the loop to run as fast as possible (only sometimes with the overhead of calling the GUI update()

) and execute it inside the main thread.

+1


source


Another solution is to create an executable that executes the function, and while is not while-true, but a condition that reads from the outside (like a binary using pickle).

condition = True
while condition:
    condition = pickle.load(open(condition.p,'rb'))
    print('hello from executable')
# endwhile condition

      

So, from the GUI you have a button that calls the pause method. It changes the contents of the file 'condition.p' and hence the required loop

def pause(self):
    self.condition = not self.condition
    pickle.dump(self.condition, open('condition.p','wb'))
    if self.condition == True: # reset infinite loop again! :)
        os.system('executable.exe')
# enddef pause

      

0


source







All Articles