GUI

Python Tkinter Event Loop(3) ステップ

イベントループのブロッキングされている場合、可能であれば、操作を小さなステップに分割し、それぞれのステップを高速に実行することが最も良い方法。次のステップがいつ発生するかは、イベントループが責任を持つ。このようにすることでイベントループは、通常のイベント処理、画面の更新、そしてその間に次のステップを実行するためのコードを呼び出しながら実行できる。

大きな作業を小さなステップに分割し、タイマーイベントと連動させる

そのためにタイマーを利用する。このイベントは、イベントループに後から発生させることができる。イベントループがその時刻になると、通常の操作の一部として、イベントを処理するためにコードにコールバックされる。コードは、次のステップの処理を実行することになり、次の操作のために別のタイマ・イベントをスケジュールし、すぐにイベント・ループに制御を戻す。

Tkのafterコマンドは、タイマーイベントを生成するために使用できる。イベントが発生するまでの待ち時間をミリ秒単位で指定する。Tk が他のイベントの処理に追われている場合、それよりも遅くなることがあるが、それ以前には発生しない。また、アイドルイベントの発生を要求できる。(Tkの画面の更新・再描画は idle イベントのコンテキストで発生する。) afterの詳細についてはリファレンスマニュアルを参照すること。

以下の例では、30の小さなステップに分割された長い操作を実行する。操作の実行中に、プログレスバーを更新し、ユーザーが操作を中断を実現する。

import tkinter as tk
from tkinter import ttk

STEP_MAX = 30

def start():
    button.configure(text='停止', command=stop)
    label['text'] = '計算中'
    global interrupt
    interrupt = False
    app.after(1, step)
    
def stop():
    global interrupt
    interrupt = True
    
def step(count=0):
    progressbar['value'] = count

    if interrupt:
        result(None)
        return
    app.after(100)

    if count == STEP_MAX:
        result(1000)
        return
    app.after(1, lambda: step(count+1))
    
def result(answer):
    progressbar['value'] = 0
    button.configure(text='開始', command=start)
    label['text'] = "結果: " + str(answer) if answer else "無回答"
    
app = tk.Tk()

frame = ttk.Frame(app)
frame.grid()

label = ttk.Label(frame, text="-")
label.grid(column=0, row=0, padx=5, pady=5)

progressbar = ttk.Progressbar(frame, orient="horizontal", mode="determinate", maximum=STEP_MAX)
progressbar.grid(column=0, row=1, padx=5, pady=5)

button = ttk.Button(frame, text="開始", command=start)
button.grid(column=0, row=2, padx=5, pady=5)

app.mainloop()