68

「開始」ボタンと進行状況バーを備えた小さな GUI テストがあります。望ましい動作は次のとおりです。

  • [開始] をクリックします。
  • プログレスバーが 5 秒間振動する
  • プログレスバーが止まる

観測された動作は、[開始] ボタンが 5 秒間フリーズし、その後プログレスバーが表示される (振動なし) ことです。

これまでの私のコードは次のとおりです。

class GUI:
    def __init__(self, master):
        self.master = master
        self.test_button = Button(self.master, command=self.tb_click)
        self.test_button.configure(
            text="Start", background="Grey",
            padx=50
            )
        self.test_button.pack(side=TOP)

    def progress(self):
        self.prog_bar = ttk.Progressbar(
            self.master, orient="horizontal",
            length=200, mode="indeterminate"
            )
        self.prog_bar.pack(side=TOP)

    def tb_click(self):
        self.progress()
        self.prog_bar.start()
        # Simulate long running process
        t = threading.Thread(target=time.sleep, args=(5,))
        t.start()
        t.join()
        self.prog_bar.stop()

root = Tk()
root.title("Test Button")
main_ui = GUI(root)
root.mainloop()

ここのBryan Oakley からの情報に基づいて、スレッドを使用する必要があることを理解しています。スレッドを作成してみましたが、スレッドはメインスレッド内から開始されるため、役に立たないのではないかと推測しています。

ロジック部分を別のクラスに配置し、そのクラス内から GUI をインスタンス化するというアイデアがありました。これは、A. Rodas のサンプル コードのようです

私の質問:

このコマンドを次のようにコーディングする方法がわかりません。

self.test_button = Button(self.master, command=self.tb_click)

他のクラスにある関数を呼び出します。これは悪いことですか、それとも可能ですか? self.tb_click を処理できる 2 番目のクラスを作成するにはどうすればよいですか? 美しく機能する A. Rodas のサンプル コードに従ってみました。しかし、アクションをトリガーするボタン ウィジェットの場合、彼のソリューションを実装する方法がわかりません。

代わりに、単一の GUI クラス内からスレッドを処理する必要がある場合、メイン スレッドに干渉しないスレッドを作成するにはどうすればよいでしょうか?

4

4 に答える 4

70

メインスレッドで新しいスレッドに参加すると、スレッドが終了するまで待機するため、マルチスレッドを使用していても GUI がブロックされます。

ロジック部分を別のクラスに配置する場合は、Thread を直接サブクラス化し、ボタンを押したときにこのクラスの新しいオブジェクトを開始できます。Thread のこのサブクラスのコンストラクターは Queue オブジェクトを受け取ることができ、それを GUI 部分と通信できるようになります。だから私の提案は:

  1. メイン スレッドで Queue オブジェクトを作成する
  2. そのキューにアクセスできる新しいスレッドを作成します
  3. メインスレッドのキューを定期的にチェックする

次に、ユーザーが同じボタンを 2 回クリックするとどうなるかという問題を解決する必要があります (クリックするたびに新しいスレッドが生成されます) self.prog_bar.stop()

import queue

class GUI:
    # ...

    def tb_click(self):
        self.progress()
        self.prog_bar.start()
        self.queue = queue.Queue()
        ThreadedTask(self.queue).start()
        self.master.after(100, self.process_queue)

    def process_queue(self):
        try:
            msg = self.queue.get_nowait()
            # Show result of the task if needed
            self.prog_bar.stop()
        except queue.Empty:
            self.master.after(100, self.process_queue)

class ThreadedTask(threading.Thread):
    def __init__(self, queue):
        super().__init__()
        self.queue = queue
    def run(self):
        time.sleep(5)  # Simulate long running process
        self.queue.put("Task finished")
于 2013-05-25T08:17:41.277 に答える
5

問題は、t.join() がクリック イベントをブロックし、メイン スレッドがイベント ループに戻って再描画を処理しないことです。Tkinter のプロセス後に ttk プログレスバーが表示される理由、または電子メールの送信時に TTK プログレス バーがブロックされる理由を参照してください。

于 2013-05-25T01:32:41.803 に答える