a Tkinter GUI stop button to break an infinite loop?

有人想用tkinter寫按鈕A,按鈕A事件中有while迴圈,造成按下按鈕A,就無法再去按下按鈕B。求助如何可以讓按鈕A持續跑while,但是滑鼠也可以去按下按鈕B‧

這題的解法,開新的 thread 就解決了。


如果題目換成,按下按鈕A,中斷一個在其他地方執行的無窮迴圈。
https://stackoverflow.com/questions/27050492/how-do-you-create-a-tkinter-gui-stop-button-to-break-an-infinite-loop

解法1:使用 .after()

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.

For a simple solution, you could use Tk.after to run a process in the background every second or so. Below is a script to demonstrate:

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 running be an attribute of it. Also, if your program becomes anything complex, it would be beneficial to look into Python’s threading module so that your scanning function can be executed in a separate thread.


解法2:global

Here is a different solution, with the following advantages:

  1. Does not require manually creating separate threads
  2. Does not use Tk.after calls. 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.

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.

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:

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

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.

相關文章

寫留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *