8

プログレスバーをプロジェクトの関数に接続しようとしています。

これは私がこれまでに持っているものですが、それが何もしないことはかなり確信しています:

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ウィンドウがフリーズすることを理解しています。メイン関数内で呼び出される各関数の最後にあるウィンドウを「フリーズ解除」する方法はありますか?

4

3 に答える 3

18

tkinter はシングル スレッドmainであるため、GUI をフリーズせずに関数を実行するには別のスレッドが必要です。一般的なアプローチの 1 つは、作業スレッドがメッセージを同期オブジェクト ( などQueue) に入れ、GUI 部分がこのメッセージを消費して進行状況バーを更新することです。

次のコードは、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 からスレッドを生成する瞬間を制御するなどのことは、可能な限り簡単ではありません)、それをリファクタリングし、新しいスレッドを開始するためのボタン。

于 2013-03-10T15:30:20.900 に答える
15

「凍結」を理解するには、理解する必要がありますmainloop()。このメソッドを呼び出すと、tkinterイベント ループが開始されます。メイン スレッドがこのループを担当します。したがって、作業集約型の関数がメイン スレッドで実行されると、メインループにも干渉します。これを防ぐために、セカンダリを使用しThreadて関数を実行できます。セカンダリ スレッドに tkinter オブジェクトへのアクセスを許可しないことをお勧めします。mtTkinterの著者である Allen B.Taylor は次のように述べています。

この問題は、_tkinter モジュールが、他のスレッドからの呼び出しを処理するときに、ポーリング手法を介してメイン スレッドの制御を取得しようとするという事実に起因しています。それが成功すれば、すべてがうまくいきます。失敗した場合 (つまり、タイムアウト後)、アプリケーションは次のメッセージを含む例外を受け取ります。「RuntimeError: メイン スレッドがメイン ループにありません」

セカンダリ スレッドに情報をQueue. 次に、メソッドを介して、メインループ内でxミリ秒ごとにキューをチェックする関数を用意します。after()

最初に、Progressbarmaximumオプションの値をどのようにするかを決定します。
これは、プログレスバーの最大インジケーター値 (プログレスバーを満たすために必要な単位数) です。たとえばmaximum=4、適切な指標値を設定してから、4 つの関数のそれぞれの後にキューに入れることができます。次に、メイン スレッドはこれらの値を (キューから) 取得して、 を介して進行状況を設定できますtkinter.IntVar()。( を使用すると、プログレスバーは 4 (完全に塗りつぶされた) に達するのではなく、最後に 0 (空) にリセットされることに注意してくださいprogbar.step()。)

tkinter.IntVar()プログレスバーで a を使用する方法を簡単に説明します。

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

アクティビティがあることをユーザーに示すものだけが必要な場合は
、プログレスバーのモードオプションを に設定できます'indeterminate'
このモードでは、インジケータは前後に跳ねます (速度は最大オプションに関連します)。

start()次に、セカンダリ スレッドを開始する前に、Progressbar のメソッドを直接呼び出すことができます。Falseを返した後
に呼び出します。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()

プログレスバー参照

于 2013-03-12T23:12:55.940 に答える