Как подключить индикатор выполнения к функции?
Я пытаюсь подключить индикатор выполнения к функции для моего проекта.
Это то, что я имею до сих пор, но я уверен, что это ничего не делает:
def main():
pgBar.start()
function1()
function2()
function3()
function4()
pgBar.stop()
Вот код, где я делаю свой индикатор выполнения, если это вообще помогает:
pgBar = ttk.Progressbar(window, orient = HORIZONTAL, length=300, mode = "determinate")
pgBar.place(x=45, y=130)
Я провел некоторое исследование и понимаю, что окно tkinter зависает при запуске функции или чего-то в этом роде. Есть ли способ, которым я мог бы "разморозить" окно в конце каждой функции, которая вызывается внутри основной?
3 ответа
Поскольку tkinter является однопоточным, вам нужен другой поток для выполнения main
функция без зависания графического интерфейса. Один общий подход заключается в том, что рабочий поток помещает сообщения в синхронизированный объект (например, Queue
), а часть с графическим интерфейсом потребляет эти сообщения, обновляя индикатор выполнения.
Следующий код основан на полном подробном примере ActiveState:
import tkinter as tk
from tkinter import ttk
import threading
import queue
import time
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.queue = queue.Queue()
self.listbox = tk.Listbox(self, width=20, height=5)
self.progressbar = ttk.Progressbar(self, orient='horizontal',
length=300, mode='determinate')
self.button = tk.Button(self, text="Start", command=self.spawnthread)
self.listbox.pack(padx=10, pady=10)
self.progressbar.pack(padx=10, pady=10)
self.button.pack(padx=10, pady=10)
def spawnthread(self):
self.button.config(state="disabled")
self.thread = ThreadedClient(self.queue)
self.thread.start()
self.periodiccall()
def periodiccall(self):
self.checkqueue()
if self.thread.is_alive():
self.after(100, self.periodiccall)
else:
self.button.config(state="active")
def checkqueue(self):
while self.queue.qsize():
try:
msg = self.queue.get(0)
self.listbox.insert('end', msg)
self.progressbar.step(25)
except Queue.Empty:
pass
class ThreadedClient(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
for x in range(1, 5):
time.sleep(2)
msg = "Function %s finished..." % x
self.queue.put(msg)
if __name__ == "__main__":
app = App()
app.mainloop()
Так как оригинальный пример на ActiveState является немного грязным IMO (ThreadedClient
вполне в сочетании с GuiPart
и такие вещи, как управление моментом создания потока из GUI, не так просты, как могли бы быть), я реорганизовал его и добавил кнопку для запуска нового потока.
Чтобы понять "замораживание", вам нужно понять mainloop()
, Вызов этого метода запускает цикл событий tkinter. Основной поток отвечает за этот цикл. Поэтому, когда ваша интенсивная работа выполняется в главном потоке, она также мешает основной петле. Чтобы предотвратить это, вы можете использовать вторичный Thread
запустить вашу функцию. Рекомендуется, чтобы вторичные потоки не имели доступа к объектам tkinter. Аллен Б. Тейлор, автор mtTkinter, заявляет:
Проблемы связаны с тем, что модуль _tkinter пытается получить контроль над основным потоком с помощью метода опроса при обработке вызовов из других потоков. Если это удастся, все хорошо. Если происходит сбой (т. Е. После тайм-аута), приложение получает исключение с сообщением: "RuntimeError: основной поток не находится в основном цикле".
Вы можете сделать так, чтобы вторичный поток помещал информацию в Queue
, Затем есть функция, которая проверяет очередь каждые x миллисекунд, в основном цикле, через after()
метод.
Во-первых, определите, какой должна быть максимальная опция Progressbar.
Это максимальное значение индикатора Progressbar (сколько единиц требуется для заполнения Progressbar). Например, вы можете установить maximum=4
а затем поместите соответствующее значение индикатора в очередь после каждой из четырех функций. Основной поток может затем извлечь эти значения (из очереди), чтобы установить прогресс через tkinter.IntVar()
, (Обратите внимание, что если вы используете progbar.step()
, индикатор выполнения сбрасывается до 0 (пусто) в конце, вместо достижения 4 (полностью заполнено).)
Вот краткий обзор того, как вы можете использовать tkinter.IntVar()
с прогрессбаром:
int_var = tkinter.IntVar()
pb_instance = ttk.Progressbar(root, maximum=4)
pb_instance['variable'] = int_var
pb_instance.pack()
# completely fill the Progressbar
int_var.set(4)
# get the progress value
x = int_var.get()
Вот пример, основанный на вашем собственном (переименован в "main" функцию "Произвольный"):
import time
import threading
try: import tkinter
except ImportError:
import Tkinter as tkinter
import ttk
import Queue as queue
else:
from tkinter import ttk
import queue
class GUI_Core(object):
def __init__(self):
self.root = tkinter.Tk()
self.int_var = tkinter.IntVar()
progbar = ttk.Progressbar(self.root, maximum=4)
# associate self.int_var with the progress value
progbar['variable'] = self.int_var
progbar.pack()
self.label = ttk.Label(self.root, text='0/4')
self.label.pack()
self.b_start = ttk.Button(self.root, text='Start')
self.b_start['command'] = self.start_thread
self.b_start.pack()
def start_thread(self):
self.b_start['state'] = 'disable'
self.int_var.set(0) # empty the Progressbar
self.label['text'] = '0/4'
# create then start a secondary thread to run arbitrary()
self.secondary_thread = threading.Thread(target=arbitrary)
self.secondary_thread.start()
# check the Queue in 50ms
self.root.after(50, self.check_que)
def check_que(self):
while True:
try: x = que.get_nowait()
except queue.Empty:
self.root.after(25, self.check_que)
break
else: # continue from the try suite
self.label['text'] = '{}/4'.format(x)
self.int_var.set(x)
if x == 4:
self.b_start['state'] = 'normal'
break
def func_a():
time.sleep(1) # simulate some work
def func_b():
time.sleep(0.3)
def func_c():
time.sleep(0.9)
def func_d():
time.sleep(0.6)
def arbitrary():
func_a()
que.put(1)
func_b()
que.put(2)
func_c()
que.put(3)
func_d()
que.put(4)
que = queue.Queue()
gui = GUI_Core() # see GUI_Core's __init__ method
gui.root.mainloop()
Если все, что вы хотите, это что-то, что указывает пользователю, что есть активность
Вы можете установить параметр режима Progressbar в 'indeterminate'
,
В этом режиме индикатор отскакивает назад и вперед (скорость относится к максимальной опции).
Тогда вы можете позвонить в Progressbar start()
метод непосредственно перед запуском вторичного потока;
а затем позвоните stop()
после secondary_thread.is_alive()
возвращает Ложь.
Вот пример:
import time
import threading
try: import tkinter
except ImportError:
import Tkinter as tkinter
import ttk
else: from tkinter import ttk
class GUI_Core(object):
def __init__(self):
self.root = tkinter.Tk()
self.progbar = ttk.Progressbar(self.root)
self.progbar.config(maximum=4, mode='indeterminate')
self.progbar.pack()
self.b_start = ttk.Button(self.root, text='Start')
self.b_start['command'] = self.start_thread
self.b_start.pack()
def start_thread(self):
self.b_start['state'] = 'disable'
self.progbar.start()
self.secondary_thread = threading.Thread(target=arbitrary)
self.secondary_thread.start()
self.root.after(50, self.check_thread)
def check_thread(self):
if self.secondary_thread.is_alive():
self.root.after(50, self.check_thread)
else:
self.progbar.stop()
self.b_start['state'] = 'normal'
def func_a():
time.sleep(1) # simulate some work
def func_b():
time.sleep(0.3)
def func_c():
time.sleep(0.9)
def func_d():
time.sleep(0.6)
def arbitrary():
func_a()
func_b()
func_c()
func_d()
gui = GUI_Core()
gui.root.mainloop()
Вы должны использовать:self.pgBar.step(x)
где "х" - это сумма, которую нужно увеличить в прогресс-баре. чтобы это обновлялось в вашем интерфейсе, вы должны поставитьself.window.update_idletasks()
после каждого self.pgBar.step(x)
заявление