所以我有一个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()
此处“开始”按钮运行无限循环扫描,按下“停止”按钮应按下:
start = Button(app, text="Start Scan",command=scanning)
stop = Button(app, text="Stop",command="break")
start.grid()
stop.grid()
但是,当我点击“开始”按钮时,它总是被按下(假设是因为无限循环)。但是,我无法点击停止按钮来突破while循环。
答案 0 :(得分:8)
您无法在Tkinter事件循环所在的同一个线程中启动while True:
循环。这样做会阻止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()
当然,您可能希望将此代码重构为一个类,并使running
成为它的一个属性。此外,如果您的程序变得复杂,那么查看Python的threading
module以便您的scanning
函数可以在单独的线程中执行将是有益的。
答案 1 :(得分:1)
这是一个不同的解决方案,具有以下优点:
不需要手动创建单独的线程
不使用Tk.after
来电。相反,保留了具有连续循环的原始代码样式。这样做的主要优点是您不必手动指定确定循环内部代码运行频率的毫秒数,它只需在您的硬件允许的情况下运行。
注意:我只尝试使用 python 3 ,而不是使用python 2.我认为同样也应该在python 2中运行,我只是#39;肯定知道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
在此代码中,我们不会调用root.mainloop()
让tkinter GUI不断更新。相反,我们经常手动更新它(在这种情况下,每500次循环迭代)。
理论上,这意味着一旦我们点击“停止”按钮,我们就不会立即停止循环。例如,如果在我们点击停止按钮的确切时刻,我们在迭代501处,则该代码将继续循环直到迭代1000被击中。所以,这个代码的缺点是理论上我们有一个响应较少的响应式GUI(但如果你的循环中的代码很快,它将是不明显的)。作为回报,我们得到循环内的代码几乎尽可能快地运行(只有有时来自GUI update()
调用的开销),并让它在主线程内运行。
答案 2 :(得分:1)
最好的方法是使用Thread和全局变量。您的代码已修改为包含这些代码。希望对您有所帮助。
from tkinter import *
from threading import Thread
def scanning():
while True:
print ("hello")
if stop == 1:
break #Break while loop when stop = 1
def start_thread():
# Assign global variable and initialize value
global stop
stop = 0
# Create and launch a thread
t = Thread (target = scanning)
t.start()
def stop():
# Assign global variable and set value to stop
global stop
stop = 1
root = Tk()
root.title("Title")
root.geometry("500x500")
app = Frame(root)
app.grid()
start = Button(app, text="Start Scan",command=start_thread)
stop = Button(app, text="Stop",command=stop)
start.grid()
stop.grid()
答案 3 :(得分:0)
另一种解决方案是创建一个执行该功能的可执行文件,而while不是真正的while,而是从外部读取的条件(例如,使用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