如何创建Tkinter图形用户界面停止按钮来中断无限循环?

2024-05-14 05:56:33 发布

您现在位置:Python中文网/ 问答频道 /正文

所以我有一个Tkinter图形用户界面,有两个简单的选项,一个开始和停止按钮。我已经定义了GUI布局:

from Tkinter import *

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

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

app = Frame(root)
app.grid()

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

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

start.grid()
stop.grid()

但是,当我按下开始按钮时,它总是被按下(假设是因为无限循环)。但是,我不能单击“停止”按钮来跳出while循环。


Tags: textapp定义tkinter选项buttonroot图形用户界面
3条回答

不能在Tkinter事件循环正在运行的同一线程中启动while True:循环。这样做会阻塞Tkinter的循环并导致程序冻结。

对于一个简单的解决方案,您可以使用^{}每隔一秒左右在后台运行一个进程。以下是演示脚本:

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

当然,您可能希望将此代码重构为一个类,并将running作为其属性。另外,如果您的程序变得很复杂,那么查看Python的^{} module将是有益的,这样您的scanning函数就可以在单独的线程中执行。

另一种解决方案是创建执行该功能的可执行文件,while不是while true,而是从外部读取的条件(例如,使用pickle的二进制文件)

condition = True
while condition:
    condition = pickle.load(open(condition.p,'rb'))
    print('hello from executable')
# endwhile condition

因此,在GUI中有一个按钮调用方法pause。它修改文件'condition.p'的内容,从而修改所需的循环

def pause(self):
    self.condition = not self.condition
    pickle.dump(self.condition, open('condition.p','wb'))
    if self.condition == True: # reset infinite loop again! :)
        os.system('executable.exe')
# enddef pause

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

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

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

注意:我只在python 3中尝试过,而不是在python 2中。我想在python 2中也应该如此,我只是不能百分之百确定。

对于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

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

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

相关问题 更多 >