LoginSignup
3
5

More than 1 year has passed since last update.

Python Tkinterの大量のボタンをpartialを使って楽に設置

Last updated at Posted at 2022-01-06

#Python Tkinterの大量のボタンをpartialを使って楽に設置

##この記事の目的
Python GUIのTkinterで大量のボタンをループして設置するにはpartialが一番楽に感じたという記事。
楽に設置」を目的として調査した結果です。

##背景
たくさんのボタンを配置する。
そのため、Tkinterにてtkinter.Button()のループで生成した。

ボタン押下した際の**「どのボタンを押したか」という挙動がうまくいかなかった**。

いくつか試した。

functoolsのpartialが一番楽に感じた

という書き置き。

##まずはcommand属性へ関数名を記載してみる

単純に5つのボタンをループで設置して、ボタンを押下したら関数を実行するプログラム。

tkinter_button_test1.py
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"を記載。

###実行結果
image.png

実行結果(起動時)
$ python tkinter_button_test1.py
1:loop-click1
0ボタンを押した
click1
1ボタンを押した
click1

(2~4ボタンも1と同じだったので省略)

注目点: ちゃんとボタン押下のイベントを受け付けている

##次にcommand属性へ関数へ引数を渡そうとして勘違いした記載
ボタンを複数設置しても、どのボタンかを判別できないと意味がない

ボタンに引数を渡してみる

tkinter_button_test1.py
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が動いてしまっている

##変数を渡し方をぐぐる
ここでぐぐってみた。
いくつか手段はあるらしい

  1. lambdaで記載
  2. functoolのpartialを利用
  3. ループせすに「ボタン毎の関数とcommand属性を全てつくる」
  4. 「ボタン毎に関数をつくって」、生成時にcallbackするハンドラを記載

目的は楽に設置なので「ややこしい」とか「力技」は却下したい。
→ 3, 4は却下として外します。

1, 2をやってみました。

lambdaで記載

tkinter_button_test3.py
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)としたものの、生成時には実行されずにいる。
ところが。

0ボタンを押した
on_click3:4
1ボタンを押した
on_click3:4

(2~4ボタンも同じ結果なので省略)

押下した際にlambdaでon_click(i)を実行しようとするが、この段階での変数iはループを終えて4になっているというオチ。
これを解決するためには、ややこしいことをしないといけなそう。
楽に設置の目的のため、別法方法を模索。

##たどりついたpartial

tkinter_button_test4.py
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
ボタン0を押した
on_click4:0
ボタン1を押した
on_click4:1
ボタン2を押した
on_click4:2
ボタン3を押した
on_click4:3
ボタン4を押した
on_click4:4

正しい挙動ができました。
変にcallbackすることもなく、importとcommand属性の中だけで解決できたので、
個人的には「楽に設置」という目的達成です。

例えば、midi情報chをリアルタイムにオンオフするフィルターを作ってみたのですが、
16ch分のボタンもループで楽に設置できました。
image.png

##まとめ
Python tkinterにて大量なボタンとかをループで生成する際に、partialを使うと楽に設置できる。
楽って大事。はい。

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5