Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

python - Tkinter - How to stop a loop with a stop button?

I have this program which beeps every second until it's stopped. The problem is that after I press "Start" and the beeps starts, I cannot click the "Stop" button because the window freezes. Any help is welcome.

#!/usr/bin/python
import Tkinter, tkMessageBox, time, winsound, msvcrt

running = True

Freq = 2500
Dur = 150

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

def start():
    sec = 0
    while running:
        if sec % 1 == 0:
            winsound.Beep(Freq, Dur)

        time.sleep(1)
        sec += 1

def stop():
    running = False

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()
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.

try:
    import tkinter as tk
except ModuleNotFoundError:
    import Tkinter as tk  # Python 2.
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


if __name__ == '__main__':

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

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

    top.mainloop()

Update

Since this answer has become fairly popular, I'd like touch on another slightly more advanced topic — namely how making the code more object-oriented would simplify things by eliminating the need almost all of the global variables.

try:
    import tkinter as tk
except ModuleNotFoundError:
    import Tkinter as tk  # Python 2.
import time
import winsound

FREQ = 2500
DUR = 150

class Application(tk.Frame, object):
    def __init__(self, master=None):
        super(Application, self).__init__(master)  # Call baseclass constructor.
        self.after_id = None
        self.secs = 0

        # Create widgets,
        startButton = tk.Button(top, height=2, width=20, text="Start", command=self.start)
        stopButton = tk.Button(top, height=2, width=20, text="Stop", command=self.stop)
        startButton.pack()
        stopButton.pack()

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

    def start(self):
        self.secs = 0
        self.beeper()  # Start repeated checking.

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


if __name__ == '__main__':

    top = tk.Tk()
    app = Application()
    app.master.title('MapAwareness')
    app.master.geometry('200x100')
    app.mainloop()


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...