How do you create a Tkinter GUI stop button to break an infinite loop?

The best way is to use Thread and global variable. Your code was modified to include those. Hope it helps.

    from tkinter import *
    from threading import Thread

    def scanning():
        while True:
            print ("hello")
            if stop == 1:   
                break   #Break while loop when stop = 1

    def start_thread():
        # Assign global variable and initialize value
        global stop
        stop = 0

        # Create and launch a thread 
        t = Thread (target = scanning)
        t.start()

    def stop():
        # Assign global variable and set value to stop
        global stop
        stop = 1

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

    app = Frame(root)
    app.grid()

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

    start.grid()
    stop.grid()

Here is a different solution, with the following advantages:

  1. Does not require manually creating separate threads

  2. Does not use Tk.after calls. Instead, the original style of code with a continuous loop is preserved. The main advantage of this is that you do not have to manually specify a number of milliseconds that determines how often your code inside the loop runs, it simply gets to run as often as your hardware allows.

Note: I've only tried this with python 3, not with 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/stopping logic, I'll use mostly the same code as in iCodez' answer. An important difference is that I assume we'll always have a loop running, but decide within that loop what to do based on which buttons have been 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 do not call root.mainloop() to have the tkinter GUI continually updating. Instead, we manually update it every so often (in this case, every 500 loop iterations).

Theoretically, this means we may not instantly stop the loop as soon as we hit the Stop button. For example, if at the exact moment where we hit the Stop button, we're at iteration 501, this code will continue looping until iteration 1000 has been hit. So, the disadvantage of this code is that we have a slighlty less responsive GUI in theory (but it will be unnoticeable if the code within your loop is fast). In return, we get the code inside the loop to run almost as fast as possible (only with sometimes overhead from a GUI update() call), and have it running inside the main thread.


You cannot start a while True: loop in the same thread that the Tkinter event loop is operating in. Doing so will block Tkinter's loop and cause the program to freeze.

For a simple solution, you could use Tk.after to run a 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 may want to refactor this code into a class and have running be an attribute of it. Also, if your program becomes anything complex, it would be beneficial to look into Python's threading module so that your scanning function can be executed in a separate thread.