TKinter - How to stop a loop with a stop button?

There are several things wrong with your code. First of all you shouldn't use time.sleep() in a Tkinter program because it interferes with the mainloop(). Instead one typically uses the universal widget method .after() to schedule a function to run after a specified delay.

Secondly you're not using global variables correctly. When you assign a value to a named variable in a function, it will create a local variable unless that name has been previous declared global. So for instance, your stop() function is creating a local variable named running and setting its value to 0, not changing the value of the global variable with the same name.

The previous rule doesn't apply to just referencing (reading) the current value of a variable. That is why it was OK to not have declared Freq and Dur globals in start().

Another problem is with the sec % 1 == 0 in your start() function. Any value % 1 is 0. To check odd/evenness use sec % 2.

Here's a working version which has also been reformatted to follow PEP 8 - Style Guide for Python Code more closely.

import Tkinter
import tkMessageBox
import time
import winsound

FREQ = 2500
DUR = 150

after_id = None
secs = 0

def beeper():
    global after_id
    global secs
    secs += 1
    if secs % 2 == 0:  # every other second
        winsound.Beep(FREQ, DUR)
    after_id = top.after(1000, beeper)  # check again in 1 second

def start():
    global secs
    secs = 0
    beeper()  # start repeated checking

def stop():
    global after_id
    if after_id:
        top.after_cancel(after_id)
        after_id = None

top = Tkinter.Tk()
top.title('MapAwareness')
top.geometry('200x100')

startButton = Tkinter.Button(top, height=2, width=20, text="Start",
                             command=start)
stopButton = Tkinter.Button(top, height=2, width=20, text="Stop",
                            command=stop)
startButton.pack()
stopButton.pack()
top.mainloop()

You code have top.mainloop() which has a while loop running inside it and on top of that you also have a while loop inside def start():. So it is like loop inside loop.

You can create a function that does what you want for the body of the loop. It should do exactly one iteration of the loop. Once it is done, it needs to arrange for itself to be called again some time in the future using after. How far in the future defines how fast your loop runs.

And you can then use after_cancel to cancel the event. Below code worked for me

import Tkinter, tkMessageBox, time, winsound, msvcrt

Freq = 2500
Dur = 150

top = tkinter.Tk()
top.title('MapAwareness')
top.geometry('200x100') # Size 200, 200

def start():
    global job1
    if running == True:
        winsound.Beep(Freq, Dur)
        job1 = top.after(1000, start)  # reschedule event in 1 seconds

def stop():
    global job1
    top.after_cancel(job1)

startButton = tkinter.Button(top, height=2, width=20, text ="Start", command = start)
stopButton = tkinter.Button(top, height=2, width=20, text ="Stop", command = stop)

startButton.pack()
stopButton.pack()
#top.after(1000, start)
top.mainloop()

Tags:

Python

Tkinter