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

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

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

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

Python

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

Q&A

解決済

1回答

845閲覧

Tkinterでafter_cancel()をし、再度after()を使うと動作が遅れる

miyatakataka

総合スコア1

Tkinter

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

Python

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

0グッド

0クリップ

投稿2023/07/14 07:04

実現したいこと

tkinterのafter_cancel()を使た後再びafter()を使って疑似的に二重ループを回したい。

1号室の1番目の窓を選択

1号室の2番目の窓を選択

1号室の3番目の窓を選択

1号室の4番目の窓を選択

2号室の1番目の窓を選択

2号室の2番目の窓を選択

2号室の3番目の窓を選択

2号室の4番目の窓を選択

というふうに、何秒かおきに自動で処理を進めたいのですが、
tkinterでは構造上、二重のforループを使えないみたいで、
疑似的にafter関数を使って実現することにしました。
・・・

該当のソースコード

Python

1import customtkinter 2import datetime 3import time 4 5class MyButtonFrame(customtkinter.CTkFrame): 6 def __init__(self, master, values): 7 super().__init__(master) 8 self.values = values 9 self.buttons = [] 10 11 for i, value in enumerate(self.values): 12 button = customtkinter.CTkButton(self, text=value) 13 if i<5: 14 button.grid(row=0, column=i, padx=10, pady=(10, 0), sticky="nw") 15 else:#改行して表示 16 button.grid(row=1, column=i-5, padx=10, pady=(10, 10), sticky="nw") 17 self.buttons.append(button) 18 19class Disproom(customtkinter.CTk): 20 def __init__(self): 21 super().__init__() 22 23 self.title("各部屋制御画面") 24 self.geometry("600x300") 25 26 #フレームの作成(ダミーデータで埋める) 27 #本格的な描画は末尾の関数で実行 28 #------------------------------------------------------------ 29 #時計フレーム 30 self.clock_frame = customtkinter.CTkFrame(self) 31 self.clock_frame.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="nw") 32 #時計フレーム内のラベル 33 self.lblClock_ymd = customtkinter.CTkLabel(self.clock_frame, text="99/99/99") 34 self.lblClock_ymd.grid(row=0, column=0, padx=10, pady=(0, 0), sticky="nw") 35 self.lblClock_hms = customtkinter.CTkLabel(self.clock_frame, text="99:99:99") 36 self.lblClock_hms.grid(row=1, column=0, padx=10, pady=(0, 0), sticky="nw") 37 self.lblClock_free = customtkinter.CTkLabel(self.clock_frame, text="") 38 self.lblClock_free.grid(row=2, column=0, padx=10, pady=(0, 0), sticky="nw") 39 #---------------------------------------------------------- 40 41 #各窓制御フレーム 42 self.window_control_frame = customtkinter.CTkFrame(self) 43 self.window_control_frame.grid(row=0, column=3, padx=10, pady=(10, 0), sticky="new") 44 #各窓制御フレームの題名 45 self.lblwindow_control_caption = customtkinter.CTkLabel(self.window_control_frame, text="天窓制御 ON No.0 制御中 (目標角 99.9°) ") 46 self.lblwindow_control_caption.grid(row=0, column=0, padx=10, pady=(0, 0), sticky="nw",columnspan=2) 47 #東天窓フレーム 48 self.window_frame_east = customtkinter.CTkFrame(self.window_control_frame) 49 self.window_frame_east.grid(row=2, column=0, padx=10, pady=(10, 0), sticky="new") 50 self.lblwindow_east_1 = customtkinter.CTkLabel(self.window_frame_east, text="東天窓") 51 self.lblwindow_east_1.grid(row=0, column=0, padx=10, pady=(0, 0), sticky="nw") 52 self.lblwindow_east_2 = customtkinter.CTkLabel(self.window_frame_east, text="No.1") 53 self.lblwindow_east_2.grid(row=0, column=1, padx=10, pady=(0, 0), sticky="nw") 54 self.lblwindow_east_3 = customtkinter.CTkLabel(self.window_frame_east, text="温度") 55 self.lblwindow_east_3.grid(row=0, column=2, padx=10, pady=(0, 0), sticky="nw") 56 self.lblwindow_east_4 = customtkinter.CTkLabel(self.window_frame_east, text="角度") 57 self.lblwindow_east_4.grid(row=1, column=2, padx=10, pady=(0, 0), sticky="nw") 58 self.lblwindow_east_temp = customtkinter.CTkLabel(self.window_frame_east, text="99.9") 59 self.lblwindow_east_temp.grid(row=0, column=3, padx=10, pady=(0, 0), sticky="nw") 60 self.lblwindow_east_angle = customtkinter.CTkLabel(self.window_frame_east, text="99.9") 61 self.lblwindow_east_angle.grid(row=1, column=3, padx=10, pady=(0, 0), sticky="nw") 62 #省略 63 #--------------------------------------------------------- 64 65 #-------------------------------------------------------- 66 #時計を起動 67 self.updateClocklbl() 68 #各部屋制御ルーチン 69 self.Selectroom() 70 71 def updateClocklbl(self): 72 #再帰呼び出し 73 self.lblClock_hms.after(1000, self.updateClocklbl)#1000[ms] 74 75 now = datetime.datetime.now() 76 ymd = now.strftime("%y/%m/%d") 77 hms = now.strftime("%H:%M:%S") 78 #値を更新して再配置 79 self.lblClock_ymd = customtkinter.CTkLabel(self.clock_frame, text=ymd) 80 self.lblClock_ymd.grid(row=0, column=0, padx=10, pady=(0, 0), sticky="nw") 81 self.lblClock_hms = customtkinter.CTkLabel(self.clock_frame, text=hms) 82 self.lblClock_hms.grid(row=1, column=0, padx=10, pady=(0, 0), sticky="nw") 83 84 85 def Selectroom(self): 86 87 #数値設定 88 global roomNo 89 global room_reset_flag 90 91 #最大部屋番号ならカウントリセット 92 if roomNo == len(WINNUM_ARRAY): 93 room_reset_flag = True 94 95 #部屋番号の算出 96 if room_reset_flag: 97 roomNo = 1#True(リセットした)なら 98 room_reset_flag = False#旗を降ろす 99 else: 100 roomNo += 1#Falseなら 101 102 #再帰呼び出しの設定 103 #各部屋の制御窓数分の時間を確保 104 self.lblClock_hms.after(MAX_SEC*WINNUM_ARRAY[roomNo-1], self.Selectroom) 105 106 #各窓制御ルーチン 107 self.Selectwindow() 108 109 110 def Selectwindow(self):#各部屋表示値計算 111 112 #数値設定 113 global windowNo 114 global window_reset_flag 115 global after_id 116 117 #最大窓番号ならカウントリセット 118 if windowNo == WINNUM_ARRAY[int(roomNo)-1]: 119 window_reset_flag = True 120 self.lblClock_hms.after_cancel(after_id) 121 else: 122 #再帰呼び出し設定 123 after_id = self.lblClock_hms.after(MAX_SEC, self.Selectwindow) 124 125 if window_reset_flag: 126 windowNo = 1#True(リセットした)なら 127 window_reset_flag = False#旗を降ろす 128 else: 129 windowNo += 1 #Falseなら 130 131 #再描画 132 self.lblwindow_control_caption.destroy() 133 self.lblwindow_control_caption = customtkinter.CTkLabel(self.window_control_frame, text=str(roomNo)+"号室 No."+str(windowNo)+" 制御中") 134 self.lblwindow_control_caption.grid(row=0, column=0, padx=10, pady=(0, 0), sticky="nw",columnspan=2) 135 136 #デバッグ用 137 print("部屋番号:"+str(roomNo),",窓番号:"+str(windowNo)) 138 139if __name__ == "__main__": 140 141 #グローバル定数 142 WINNUM_ARRAY=[4,4,4,4,4,4,4]#各部屋の制御窓数を配列で保持 143 MAX_SEC=5000#最大窓制御時間[ms] 144 145 #グローバル変数 146 roomcount=0 147 windowcount=0 148 room_reset_flag=True 149 window_reset_flag=True 150 roomNo=0 151 windowNo=0 152 after_id=0 153 154 Disproom = Disproom() 155 Disproom.mainloop() 156

試したこと

プリントデバッグしたところ、
イメージ説明
プログラム164行目にてafter_cancelをしたときに、次の窓に移行する時間が他より多くなりました。

聞きたいこと

after関数を使わずに、tkinterでforの二重ループを回して順番に表示させる方法があればご教示いただけませんか。

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

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

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

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

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

guest

回答1

0

ベストアンサー

ソースコードは読んでいないので的外れかもしれませんが、tkinterに限らず、GUIのアプリケーションでイベントループ(tkinterのmainloop())の外でfor文を回してその中で処理をしようとするのは筋が悪いです。

n個とか、m × n個 のものを定期的にチェックしたいのであれば、チェックする関数を1つ用意して、それを定期的に呼ぶ=その関数内でafterで自分を呼ぶようにするのがいいのではないかと思います。
関数が呼ばれたときに何をするかについては、global変数などで管理します。

after_cancel()したい理由はよくわかりませんでした。


追記

コメントいただいたので、コードちょっと読んでみました。
前の回答はちょっとまとを外していましたね。

2重ループになるので、部屋番号と窓番号を別のafterループ(と言うのか)で制御しようとしていたのですね。
窓を1つずつ進んで、1部屋終れば次の部屋でよければ、1つだけで足りると思います。
roomNoと windowNoを両方制御すればいいはずです。

python

1 def select_room_window(self): 2 3 #数値設定 4 global roomNo 5 global windowNo 6 global after_id 7 8 9 if windowNo >= WINNUM_ARRAY[int(roomNo)-1]: 10 #最大窓番号 になったら次の部屋に移る 11 windowNo = 1 12 roomNo += 1 13 #最大部屋番号 になったら最初の部屋に移る 14 if roomNo >= len(WINNUM_ARRAY): 15 roomNo = 1 16 else: 17 # 次の窓に移る 18 windowNo += 1 19 20 after_id = self.lblClock_hms.after(MAX_SEC, self.select_room_window) 21 22 23 #再描画 24 self.lblwindow_control_caption.destroy() 25 self.lblwindow_control_caption = customtkinter.CTkLabel(self.window_control_frame, text=str(roomNo)+"号室 No."+str(windowNo)+" 制御中") 26 self.lblwindow_control_caption.grid(row=0, column=0, padx=10, pady=(0, 0), sticky="nw",columnspan=2) 27 28 #デバッグ用 29 print(f"部屋番号:{roomNo},窓番号:{windowNo}")

この関数1つだけで窓と部屋を回しています。
after_idは残していますが使っていません。

投稿2023/07/14 07:34

編集2023/07/14 10:36
TakaiY

総合スコア14413

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

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

miyatakataka

2023/07/14 08:03 編集

さっそくのご回答ありがとうございます。 >>>tkinterに限らず、GUIのアプリケーションでイベントループ(tkinterのmainloop())の外で >>>for文を回してその中で処理をしようとするのは筋が悪いです。 ごもっともです。 >>>チェックする関数を1つ用意して、それを定期的に呼ぶ=その関数内で >>>afterで自分を呼ぶようにするのがいいのではないかと思います。 漠然とですが、実装のイメージがつかめたような気がします。 >>>after_cancel()したい理由はよくわかりませんでした。 すみません。肝心なことを書いていませんでした。 そもそもafter_cancel()しようとしたのは、 部屋番号のafterと窓番号のafterが別々に動作していると、 長い時間がたつと徐々にタイミングがずれてきてしまう現象があったからです。 そこで各部屋の末端の窓の処理が終了したら、いちど 窓番号のafter呼び出しをリセットし、 次部屋に移った後再度呼び出すことで、 時間的なずれの影響を少なくできると考えたためです。 ただ、この考えだと今回のように却って面倒なことになってしまったので とくにafter_canncelにこだわらず、なにか実装の定石があるようでしたら お力を貸していただきたいと思い、質問してみました。
TakaiY

2023/07/14 10:01

> なにか実装の定石 1つの関数を連続を一定間隔で実行するために afterを使う方法を提案しました。
miyatakataka

2023/07/14 22:39

動きました!!。ありがとうございます。 この箇所で数時間詰まっていたのでとても助かります。 やろうとしていることを過不足なく簡潔に表現するというのは なかなか難しいものですね。
TakaiY

2023/07/15 14:16

特にGUIのプログラムは通常のプログラムと違う制御構造を必要としているので、特有の考えかたをしなければならないので初めの内は悩んでしまいやすいですね。 イベントループ(mainloop)が処理の中心で、イベント=外部入力の処理を中心に作ることになるので、今回のような自律の動作というのは特殊な扱いが必要になりますね。 慣れてしまえば、それほど難しくありません。また、他のプログラムをそういう目で見てみると勉強になると思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.30%

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

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

質問する

関連した質問