2013-03-10 3 views
6

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

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

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) 

I проводили некоторые исследования и понимали, что окно tkinter зависает при запуске функции или что-то в этом роде. Есть ли способ «разморозить» окно в конце каждой функции, которая вызывается внутри основной?

ответ

15

Поскольку tkinter является с одной резьбой, вам понадобится другой поток для выполнения вашей функции main без зависания графического интерфейса. Один общий подход заключается в том, что рабочий поток помещает сообщения в синхронизированный объект (например, Queue), а часть GUI потребляет эти сообщения, обновляя индикатор выполнения.

Следующий код на основе полного детального example на 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() 

Поскольку original example на ActiveState несколько хаотично ИМО (ThreadedClient вполне в сочетании с GuiPart, и такие вещи, как контролировать момент для создания потока из графического интерфейса не так просто, как могли бы быть), я отредактировал его, а также добавил кнопку, чтобы начать новый поток.

+1

Aaaah я не понимаю. вы могли бы объяснить это, возможно, сделать более простой пример – user2148781

+0

@ user2148781 Я добавил пример. Единственное отличие состоит в том, что у этого есть виджет индикатора выполнения, но концепция такая же. –

+0

Но как я мог запустить workThread изнутри GuiPart, а не запускать его автоматически? – tatsuhirosatou

11

Чтобы понять «замораживание», вам необходимо понять mainloop(). Вызов этого метода запускает цикл событий tkinter. Основной поток отвечает за этот цикл. Поэтому, когда ваша интенсивная работа работает в основном потоке, она также мешает mainloop. Чтобы предотвратить это, вы можете использовать вторичный Thread для запуска вашей функции. Рекомендуется, чтобы вторичные потоки не получали доступ к объектам tkinter. Аллен B.Taylor, автор mtTkinter, утверждает:

The problems stem from the fact that the _tkinter module attempts to gain control of the main thread via a polling technique when processing calls from other threads. If it succeeds, all is well. If it fails (i.e., after a timeout), the application receives an exception with the message: "RuntimeError: main thread is not in main loop".

Вы можете иметь вторичный поток поместить информацию в Queue. Затем выполните функцию, которая проверяет Очередь каждые x миллисекунд, в пределах mainloop, с помощью метода after().

Прежде всего, решите, что вы хотите, чтобы значение Progressbar: maximum вариант быть.
Это максимальное значение индикатора Progressbar (сколько единиц требуется для заполнения Progressbar). Например, вы можете установить maximum=4 и затем поместить соответствующее значение индикатора в очередь после каждой из четырех функций. Затем основной поток может извлечь эти значения (из очереди), чтобы установить прогресс через tkinter.IntVar(). (Обратите внимание, что если вы используете progbar.step(), Progressbar сбрасывается до 0 (пустой) в конце, а не достигает 4 (полностью заполняется).)

Вот быстрый взгляд на то, как вы можете использовать tkinter.IntVar() с ProgressBar:

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

Вот пример, основанный на своем собственном (переименована в «основную» функции «произвольный»):

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

Если вы хотите что-то, что указывает пользователю, что есть активность
, вы можете установить опцию Пробега mode на 'indeterminate'.
Индикатор отскакивает назад и вперед в этом режиме (скорость относится к максимальной опции).

Затем вы можете вызвать метод Progressbar start() непосредственно перед началом вторичного потока;
, а затем позвонить по телефону stop() после secondary_thread.is_alive()False.

Вот пример:

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

Progressbar reference

0

Вы должны использовать: self.pgBar.step(x) где 'х' сумма должна быть увеличена в ProgressBar. для этого, чтобы получить обновление в вашем пользовательском интерфейсе вы должны поставить self.window.update_idletasks() после каждого self.pgBar.step(x) заявления