简体   繁体   English

Tkinter - 如何使用停止按钮停止循环?

[英]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.问题是,在我按下“开始”并开始发出哔哔声后,我无法单击“停止”按钮,因为 window 冻结了。 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()

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() .首先你不应该使用time.sleep()因为它与干扰的Tkinter的程序mainloop() Instead one typically uses the universal widget method .after() to schedule a function to run after a specified delay.相反,通常使用通用小部件方法.after()来安排函数在指定延迟后运行。

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 .当您为函数中的命名变量赋值时,它将创建一个局部变量,除非该名称之前已声明为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.例如,您的stop()函数正在创建一个名为running的局部变量并将其值设置为 0,而不是更改具有相同名称的全局变量的值。

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() .这就是为什么不在start()声明FreqDur全局变量是可以的。

Another problem is with the sec % 1 == 0 in your start() function.另一个问题是start()函数中的sec % 1 == 0 Any value % 1 is 0 .任何值% 1都是0 To check odd/evenness use sec % 2 .要检查奇数/偶数,请使用sec % 2

Here's a working version which has also been reformatted to follow PEP 8 - Style Guide for Python Code more closely.这是一个工作版本,它也经过重新格式化以更紧密地遵循PEP 8 - Python 代码风格指南

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()

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(): .你的代码有top.mainloop() ,它里面有一个while循环,最重要的是你在def start():有一个 while 循环。 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 .完成后,它需要安排自己在将来的某个时间再次使用after调用。 How far in the future defines how fast your loop runs.未来多远定义了循环运行的速度。

And you can then use after_cancel to cancel the event.然后您可以使用after_cancel取消该事件。 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()

The problem is that the while loop in start() blocks the GUI handler mainloop() .问题是start()中的 while 循环阻塞了 GUI 处理程序mainloop() Try using Tk.after() in start() :尝试在start()使用Tk.after() start()

def start(force=True):
    global running
    if force:
        running = True
    if running:
        winsound.Beep(Freq, Dur)
        top.after(1000, start, False)

And change stop() :并更改stop()

def stop():
    global running
    running = False

Beaten to the punch again but here goes nothing.再次受到打击,但这里什么也没有。 As above use the after function to prevent the mainloop blocking.如上所述,使用after函数来防止主mainloop阻塞。
See: tkinter: how to use after method请参阅: tkinter:如何使用 after 方法

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

Freq = 2500
Dur = 150

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

def start():
    print ("Beep")
    top.after(1000, start)

def stop():
    print ("Stop")
    top.quit()

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()

I used thread and global variable to fit your need.我使用线程和全局变量来满足您的需要。 Not so complicated if you understand how they work.如果您了解它们的工作原理,就不会那么复杂。 Just an addition of few lines and minor change to your existing line, and it works.只需添加几行并对您现有的行进行微小更改,它就可以工作。 Look through to see the changes made to your original code.仔细查看对原始代码所做的更改。

    #!/usr/bin/python
    import tkinter
    from tkinter import messagebox
    import time, winsound, msvcrt
    from threading import Thread

    running = True

    Freq = 2500
    Dur = 150

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

    def button_click():
        global running  #create global
        running = True

        # Create new thread
        t = Thread(target = start)
        # Start new thread
        t.start()

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

            time.sleep(1)
            sec += 1

    def stop():
        global running  #create global
        running = False

    startButton = tkinter.Button(top, height=2, width=20, text ="Start", command = button_click) #Change to call button_click instead start
    stopButton = tkinter.Button(top, height=2, width=20, text ="Stop", command = stop)

    startButton.pack()
    stopButton.pack()

    top.mainloop()
from threading import Thread

def stttart():
    t1=Thread(target=start)
    t1.start()
def start():
    ...

def stop():
    ...

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

startButton.pack()
stopButton.pack()

https://www.geeksforgeeks.org/how-to-use-thread-in-tkinter-python/ https://www.geeksforgeeks.org/how-to-use-thread-in-tkinter-python/

last year, this had been my big problem for some months去年,这几个月来一直是我的大问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM