Как подключить индикатор выполнения к функции?

Я пытаюсь подключить индикатор выполнения к функции для моего проекта.

Это то, что я имею до сих пор, но я уверен, что это ничего не делает:

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) заявление

Другие вопросы по тегам