質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

1223閲覧

カーソルを動かしたときに徐々に背景の色が変化する方法

shibububu1117

総合スコア3

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/12/15 19:36

カーソルを動かしたときに徐々に背景の色を変化させるプログラムを作成したいです。
X座標が100未満の時に背景を黒色に、100以上200未満の場合にグレー、200以上の時に黒に戻すものはできました。
これを一瞬で色を変化させるのではなくgray0からだんだんとgray40に変化し、gray0に徐々に戻っていくプログラムができません。
きっとfor文を使うんだろうなというのはわかるのですが、使い方を調べてみてもわからなかったです。
どなたか教えてもらえたら幸いです。

Pythonコード:

import tkinter
import tkinter.ttk

class Application(tkinter.Frame):
def init(self, master=None):
super().init(master)
self.master = master
self.master.title('tkinter canvas trial')
self.pack()
self.create_widgets()

def create_widgets(self): self.current_x = tkinter.StringVar() self.current_y = tkinter.StringVar() self.test_canvas = tkinter.Canvas(self, bg='gray0', width=300, height=300, highlightthickness=0) self.test_canvas.grid(row=0, column=0, rowspan=7) self.test_canvas.bind('<B1-Motion>', self.pickup_position) def pickup_position(self, event): if event.x<100: for i in range(0,100): event.x+=i self.test_canvas['bg']='gray40' elif 100<event.x<200: self.test_canvas['bg']='gray0' elif event.x>200: self.test_canvas['bg']='gray40'

root = tkinter.Tk()
app = Application(master=root)
app.mainloop()

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

きっと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 02:20

編集2021/12/16 03:57
teamikl

総合スコア8760

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shibububu1117

2021/12/16 09:03

timeを使わないといけなかったんですね。とても参考になりました!
teamikl

2021/12/16 10:43

time.sleep を使う方法は期待通りに動作しない例で、 time.sleep の間はウィンドウは操作を受け付けません。 GUIの提供するタイマー機能 (tkinter の場合は after 関数) を使う方法が推奨です。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問