きっとfor文を使うんだろうなというのはわかるのですが、使い方を調べてみてもわからなかったです。
GUI等のイベント駆動型プログラミングでは、イベントループが稼働して
イベントや描画等の処理を常に行っています。
ユーザ定義のイベントは、その合間に呼び出される形式になっていて
処理は直ぐにイベントループ側に戻さないと、ウィンドウが応答なしとなってしまいます。
原則、他の時間の掛かるループを使えません。
(使う方法はありますが工夫が必要です)
GUIでのアニメーション処理には、
GUIライブラリの提供するタイマーを用いるのが一般的な解決策です。
追記: 以下サンプルコードはタイマーの利用を説明したものなので、
「カーソルを動かしたとき」ではなく、「ボタンをクリック」した時になってます。
適宜読み替えて下さい。
タイマーの始動となる部分を、Motionイベント内のカーソルが任意の場所に来た時とします。
補足: 注意点は、タイマーが稼働してるときに他の色に変更するタイマーを重ねる場合の対応。
after() の戻り値を保存し、after_cancel() に渡す事で、タイマーの中断が可能です。
期待通りに動かないコード for文利用
python
1
2import tkinter as tk
3import time
4
5root = tk.Tk()
6label = tk.Label(root, text="TEST")
7label.pack(fill=tk.BOTH, expand=tk.YES)
8def onClick():
9 for num in range(16):
10 # 色変更 黒000000 → 白FFFFFF
11 color = "#" + (f"{num:x}" * 6)
12 label.config(bg=color)
13
14 time.sleep(0.2) # XXX: この間 GUI は応答なしになる
15
16 # XXX: 色変更が反映されるのは、関数を抜けた後
17
18button = tk.Button(root, text="Click", command=onClick)
19button.pack()
20root.mainloop()
21
22
タイマーを使った解決策(推奨
python
1
2def onClick():
3 def _update(num):
4 color = "#" + (f"{num:x}" * 6)
5 label.config(bg=color)
6
7 if num < 16:
8 root.after(200, _update, num+1)
9
10 root.after_idle(_update, 0)
11
タイマー機能を提供するafter関数は、
○○ミリ秒後に指定の関数を呼び出しという形式なので、
ループ文の記述を再帰で書き換える必要があります。
実際の実行時の呼出は再帰呼出ではありません。
追記: タイマーを使った解決策2 (案のみ
python
1
2def change_bg(color):
3 canvas['bg'] = color
4
5# タイマーを時差で発動させることでアニメーションを表現
6
7root.after(200*1, change_bg, "#000000")
8root.after(200*2, change_bg, "#111111")
9root.after(200*3, change_bg, "#222222")
10root.after(200*4, change_bg, "#333333")
11
12# for文を使って一度にタイマーを登録する
13
14for idx, num in enumerate(range(16), start=1):
15 color = "#" + (f"{num:x}" * 6)
16 root.after(200*idx, change_bg, color)
デメリットはあって、ループ数が多い場合は効率的ではありません。
推奨しない解決策: ループ内 .update() で強制的に描画を反映できますが…
イベントループがネストするので、稀ですが
for 文実行中に他のイベントが発生する場合、
イベント実行順序が意図通りに制御できなくなる、
潜在的な問題があります。
python
1
2
3import tkinter as tk
4import time
5
6root = tk.Tk()
7label = tk.Label(root, text="TEST")
8label.pack(fill=tk.BOTH, expand=tk.YES)
9def onClick():
10 # XXX: ループ文が実行中にウィンドウが閉じられた場合、エラー
11 for num in range(16):
12 color = "#" + (f"{num:x}" * 6)
13 label.config(bg=color)
14
15 root.update() # XXX: ここで色反映を出来るが、この中で他のイベントが呼び出される可能性
16
17 time.sleep(0.2) # XXX: ウィンドウが応答なしになる
18button = tk.Button(root, text="Click", command=onClick)
19button.pack()
20root.mainloop()
21
22
ジェネレーターを使った解決策(外部ライブラリ利用
for 文で記述でき、タイマーを使う実装。
アニメーションのロジックが複雑になる場合検討して見て下さい。
途中の中断にも対応可能です。
python
1# 要外部ライブラリ: pip install gentimer
2
3import tkinter as tk
4import gentimer
5
6root = tk.Tk()
7label = tk.Label(root, text="TEST")
8label.pack(fill=tk.BOTH, expand=tk.YES)
9
10def onClick():
11 def _update():
12 for num in range(16):
13 color = "#" + (f"{num:x}" * 6)
14 label.config(bg=color)
15
16
17 # time.sleep/root.updateの代わり。タイマーで指定秒後に復帰する。
18 # 前述したような time.sleep/root.update の問題は起こらない。
19 yield 0.2
20
21 gentimer.tk(root, _update())
22
23button = tk.Button(root, text="Click", command=onClick)
24button.pack()
25root.mainloop()
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/12/16 09:03
2021/12/16 10:43