#Python Tkinterの大量のボタンをpartialを使って楽に設置
##この記事の目的
Python GUIのTkinterで大量のボタンをループして設置するにはpartialが一番楽に感じたという記事。
「楽に設置」を目的として調査した結果です。
##背景
たくさんのボタンを配置する。
そのため、Tkinterにてtkinter.Button()のループで生成した。
↓
ボタン押下した際の**「どのボタンを押したか」という挙動がうまくいかなかった**。
↓
いくつか試した。
↓
functoolsのpartialが一番楽に感じた。
という書き置き。
##まずはcommand属性へ関数名を記載してみる
単純に5つのボタンをループで設置して、ボタンを押下したら関数を実行するプログラム。
import tkinter
tk_root = tkinter.Tk()
frame_main = tkinter.Frame(tk_root)
frame_main.grid()
def on_click1() -> None:
print("click1")
print("1:loop-click1")
for i in range(5):
button = tkinter.Button(frame_main, text=i, command=on_click1)
button.grid(row=0, column=i)
tk_root.mainloop()
注目点
- tkinter.Buttonのcommand属性に"on_click1"を記載。
$ python tkinter_button_test1.py
1:loop-click1
click1
click1
(2~4ボタンも1と同じだったので省略)
注目点: ちゃんとボタン押下のイベントを受け付けている
##次にcommand属性へ関数へ引数を渡そうとして勘違いした記載
ボタンを複数設置しても、どのボタンかを判別できないと意味がない。
↓
ボタンに引数を渡してみる
import tkinter
tk_root = tkinter.Tk()
frame_main = tkinter.Frame(tk_root)
frame_main.grid()
def on_click2(num:int) -> None:
print("click2:%d" % num)
print("2:loop-click2")
for i in range(5):
button = tkinter.Button(frame_main, text=i, command=on_click2(i))
button.grid(row=0, column=i)
tk_root.mainloop()
注目点
- tkinter.Buttonのcommand属性を"on_click2(i)"に変更。
- on_click2関数を引数を受け付ける様に変更。
$ python tkinter_button_test2.py
2:loop-click2
click2:0
click2:1
click2:2
click2:3
click2:4
思ってたのと違う。
command属性に関数名でなく()のついた関数を記載したのでボタン生成時に動作した様子。
何が問題なのかわかりにくいので補足:ここではボタンをクリックしたらclick2:iが表示されてほしかった→が、ボタンをまだクリックしてないプログラム開始時にon_click2が動いてしまっている
##変数を渡し方をぐぐる
ここでぐぐってみた。
いくつか手段はあるらしい
- lambdaで記載
- functoolのpartialを利用
ループせすに「ボタン毎の関数とcommand属性を全てつくる」「ボタン毎に関数をつくって」、生成時にcallbackするハンドラを記載
目的は楽に設置なので「ややこしい」とか「力技」は却下したい。
→ 3, 4は却下として外します。
1, 2をやってみました。
lambdaで記載
import tkinter
tk_root = tkinter.Tk()
frame_main = tkinter.Frame(tk_root)
frame_main.grid()
def on_click3(num) -> None:
print("on_click3:%d" % num)
print("3:loop-click3")
for i in range(5):
button = tkinter.Button(frame_main, text=i, command=lambda:on_click3(i))
button.grid(row=0, column=i)
tk_root.mainloop()
注目点
- tkinter.Buttonのcommand属性に"lambda:on_click3(i)"と引数付きで記載。
$ python .\tkinter_button_test3.py
3:loop-click3
on_click(i)としたものの、生成時には実行されずにいる。
ところが。
on_click3:4
on_click3:4
(2~4ボタンも同じ結果なので省略)
押下した際にlambdaでon_click(i)を実行しようとするが、この段階での変数iはループを終えて4になっているというオチ。
これを解決するためには、ややこしいことをしないといけなそう。
楽に設置の目的のため、別法方法を模索。
##たどりついたpartial
import tkinter
from functools import partial
tk_root = tkinter.Tk()
frame_main = tkinter.Frame(tk_root)
frame_main.grid()
def on_click4(num) -> None:
print("on_click4:%d" % num)
print("4:loop-click4")
for i in range(5):
button = tkinter.Button(frame_main, text=i, command=partial(on_click4, i))
button.grid(row=0, column=i)
tk_root.mainloop()
注目点:
- tkinter.Buttonのcommand属性に"partial(on_click4, i)"と引数を渡して記載。
- partialをimport(from functools import partial)
$ python tkinter_button_test4.py
4:loop-click4
on_click4:0
on_click4:1
on_click4:2
on_click4:3
on_click4:4
正しい挙動ができました。
変にcallbackすることもなく、importとcommand属性の中だけで解決できたので、
個人的には「楽に設置」という目的達成です。
例えば、midi情報chをリアルタイムにオンオフするフィルターを作ってみたのですが、
16ch分のボタンもループで楽に設置できました。
##まとめ
Python tkinterにて大量なボタンとかをループで生成する際に、partialを使うと楽に設置できる。
楽って大事。はい。