Python 你如何创建一个 Tkinter GUI 停止按钮来打破无限循环?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27050492/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do you create a Tkinter GUI stop button to break an infinite loop?
提问by Jonathan Davies
So I have a Tkinter GUI with two simple options, a start and stop button. I have defined the GUI layout:
所以我有一个带有两个简单选项的 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()
Here the Start button runs the infinite loop scanning, and the Stop button should break on press:
这里开始按钮运行无限循环扫描,停止按钮应该在按下时中断:
start = Button(app, text="Start Scan",command=scanning)
stop = Button(app, text="Stop",command="break")
start.grid()
stop.grid()
However, when I hit the Start button, it is always pushed down (assuming because of the infinite loop). But, I cannot click on the Stop buttonto break out of the while loop.
但是,当我点击开始按钮时,它总是被按下(假设是因为无限循环)。但是,我无法单击“停止”按钮来跳出 while 循环。
采纳答案by Jonathan Davies
You cannot start a while True:loop in the same thread that the Tkinter event loop is operating in. Doing so will block Tkinter's loop and cause the program to freeze.
您不能while True:在运行 Tkinter 事件循环的同一线程中启动循环。这样做会阻塞 Tkinter 的循环并导致程序冻结。
For a simple solution, you could use Tk.afterto run a process in the background every second or so. Below is a script to demonstrate:
对于一个简单的解决方案,您可以使用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()
Of course, you may want to refactor this code into a class and have runningbe an attribute of it. Also, if your program becomes anything complex, it would be beneficial to look into Python's threadingmoduleso that your scanningfunction can be executed in a separate thread.
当然,您可能希望将此代码重构为一个类并running成为它的一个属性。此外,如果您的程序变得复杂,那么查看 Python 的threading模块将是有益的,以便您的scanning函数可以在单独的线程中执行。
回答by Dennis Soemers
Here is a different solution, with the following advantages:
这是一个不同的解决方案,具有以下优点:
Does not require manually creating separate threads
Does not use
Tk.aftercalls. Instead, the original style of code with a continuous loop is preserved. The main advantage of this is that you do not have to manually specify a number of milliseconds that determines how often your code inside the loop runs, it simply gets to run as often as your hardware allows.
不需要手动创建单独的线程
不使用
Tk.after调用。相反,保留了具有连续循环的原始代码样式。这样做的主要优点是您不必手动指定决定循环内代码运行频率的毫秒数,它只需在您的硬件允许的情况下运行即可。
Note:I've only tried this with python 3, not with python 2. I suppose the same should work in python 2 too, I just don't know 100% for sure.
注意:我只在python 3 上试过这个,在python 2 上没有试过。我想这也应该在 python 2 中工作,我只是不确定 100%。
For the UI code and start/stopping logic, I'll use mostly the same code as in iCodez' answer. An important difference is that I assume we'll always have a loop running, but decide within that loop what to do based on which buttons have been pressed recently:
对于 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
In this code, we do not call root.mainloop()to have the tkinter GUI continually updating. Instead, we manually update it every so often (in this case, every 500 loop iterations).
在这段代码中,我们没有调用root.mainloop()让 tkinter GUI 不断更新。相反,我们经常手动更新它(在这种情况下,每 500 次循环迭代)。
Theoretically, this means we may not instantly stop the loop as soon as we hit the Stop button. For example, if at the exact moment where we hit the Stop button, we're at iteration 501, this code will continue looping until iteration 1000 has been hit. So, the disadvantage of this code is that we have a slighlty less responsive GUI in theory (but it will be unnoticeable if the code within your loop is fast). In return, we get the code inside the loop to run almost as fast as possible (only with sometimes overhead from a GUI update()call), and have it running inside the main thread.
从理论上讲,这意味着我们可能不会在按下停止按钮后立即停止循环。例如,如果在我们点击停止按钮的确切时刻,我们处于迭代 501,则此代码将继续循环,直到命中迭代 1000。因此,此代码的缺点是理论上我们的 GUI 响应速度较慢(但如果循环中的代码速度很快,则不会引起注意)。作为回报,我们让循环内的代码尽可能快地运行(只是有时会产生 GUIupdate()调用的开销),并让它在主线程内运行。
回答by José Crespo Barrios
Another solution is to create an executable that performs the function, and the while is NOT a while-true, but a condition that reads from outside (e.g. a binary file using pickle)
另一种解决方案是创建一个执行该功能的可执行文件,while 不是 while-true,而是从外部读取的条件(例如使用 pickle 的二进制文件)
condition = True
while condition:
condition = pickle.load(open(condition.p,'rb'))
print('hello from executable')
# endwhile condition
So, from the GUI you have a button that calls method 'pause'. It modifies the content of the file 'condition.p', and therefore the desired loop
因此,从 GUI 中,您有一个调用方法“暂停”的按钮。它修改文件“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
回答by Khairil
The best way is to use Thread and global variable. Your code was modified to include those. Hope it helps.
最好的方法是使用 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()

