Как создать кнопку остановки Tkinter GUI, чтобы разорвать бесконечный цикл?
Таким образом, у меня есть графический интерфейс 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.
2 ответа
Вы не можете начать 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()
Конечно, вы можете реорганизовать этот код в класс и иметь running
быть атрибутом этого. Кроме того, если ваша программа становится чем-то сложным, было бы полезно изучить Python threading
модуль, чтобы ваш scanning
Функция может быть выполнена в отдельном потоке.
Вот другое решение со следующими преимуществами:
Не требует создания отдельных потоков вручную
Не использует
Tk.after
звонки. Вместо этого оригинальный стиль кода с непрерывным циклом сохраняется. Основным преимуществом этого является то, что вам не нужно вручную указывать количество миллисекунд, которое определяет, как часто ваш код внутри цикла выполняется, он просто запускается так часто, как позволяет ваше оборудование.
Примечание: я пробовал это только с python 3, а не с python 2. Полагаю, то же самое должно работать и в python 2, я просто не знаю 100% точно.
Для кода пользовательского интерфейса и логики запуска / остановки я буду использовать в основном тот же код, что и в ответе 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 постоянно обновлялся. Вместо этого мы вручную обновляем его время от времени (в данном случае каждые 500 итераций цикла).
Теоретически это означает, что мы не можем немедленно остановить цикл, как только нажмем кнопку "Стоп". Например, если в тот момент, когда мы нажимаем кнопку "Стоп", мы находимся на итерации 501, этот код будет продолжать цикл до тех пор, пока не будет достигнута итерация 1000. Итак, недостаток этого кода в том, что у нас есть немного менее отзывчивый GUI в теории (но это будет незаметно, если код в вашем цикле будет быстрым). В свою очередь, мы получаем код внутри цикла, который выполняется почти так же быстро, как это возможно (только иногда с перегрузкой из графического интерфейса). update()
вызов), и он работает внутри основного потока.
Лучше всего использовать поток и глобальную переменную. Ваш код был изменен, чтобы включить их. Надеюсь, это поможет.
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()
Другое решение - создать исполняемый файл, который выполняет функцию, и while НЕ является while-true, а является условием, которое читает извне (например, двоичный файл с использованием pickle)
condition = True
while condition:
condition = pickle.load(open(condition.p,'rb'))
print('hello from executable')
# endwhile condition
Итак, в графическом интерфейсе у вас есть кнопка, которая вызывает метод "пауза". Он изменяет содержимое файла '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