如何创建Tkinter GUI停止按钮以打破无限循环?

Jon*_*ies 5 python user-interface tkinter

所以我有一个Tkinter GUI,有两个简单的选项,一个启动和停止按钮.我已经定义了GUI布局:

from Tkinter import *

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

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

app = Frame(root)
app.grid()
Run Code Online (Sandbox Code Playgroud)

这里的"开始"按钮运行无限循环扫描,按下"停止"按钮应按下:

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

start.grid()
stop.grid()
Run Code Online (Sandbox Code Playgroud)

但是,当我点击"开始"按钮时,它总是被按下(假设是因为无限循环).但是,我无法单击"停止"按钮以突破while循环.

iCo*_*dez 9

您无法while True:在Tkinter事件循环所在的同一个线程中启动循环.这样做会阻止Tkinter的循环并导致程序冻结.

对于一个简单的解决方案,您可以使用Tk.after每秒左右在后台运行一个进程.下面是一个演示脚本:

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()
Run Code Online (Sandbox Code Playgroud)

当然,您可能希望将此代码重构为类并具有该类running的属性.此外,如果您的程序变得复杂,那么查看Python的threading模块以便您的scanning函数可以在单独的线程中执行将是有益的.


Den*_*ers 6

这是一个不同的解决方案,具有以下优点:

  1. 不需要手动创建单独的线程

  2. 不使用Tk.after调用。相反,保留了具有连续循环的原始代码样式。这样做的主要优点是您不必手动指定决定循环内代码运行频率的毫秒数,它只需在您的硬件允许的情况下运行即可。

注意:我只用python 3试过这个,没有用 python 2试过。我想同样的方法也应该在 python 2 中工作,我只是不确定 100%。

对于 UI 代码和启动/停止逻辑,我将使用与 iCodez 的回答中大致相同的代码。一个重要的区别是,我假设我们将始终运行一个循环,但是根据最近按下的按钮来决定在该循环中做什么:

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
Run Code Online (Sandbox Code Playgroud)

在这段代码中,我们没有调用root.mainloop()让 tkinter GUI 不断更新。相反,我们经常手动更新它(在这种情况下,每 500 次循环迭代)。

从理论上讲,这意味着我们可能不会在按下停止按钮后立即停止循环。例如,如果在我们点击停止按钮的确切时刻,我们处于迭代 501,则此代码将继续循环,直到命中迭代 1000。因此,此代码的缺点是理论上我们的 GUI 响应速度较慢(但如果循环中的代码速度很快,则不会引起注意)。作为回报,我们让循环内的代码尽可能快地运行(只是有时会产生 GUIupdate()调用的开销),并让它在主线程内运行。