今回は Python の tkinter で「大量にあるボタンの中からどのボタンが押されたかを判別する」方法について解説します。
どのボタンが押されたを判別し、「そのボタンの文字の色を赤色に変更する例」と「そのボタンのテキストを出力する例」を用いて解説していきたいと思います。
一番シンプルな方法は下記のスクリプトのように全てのボタンに対して別々の関数を自力で用意することです。
import tkinter
# ボタン1が押された時に実行する関数
def button1_click():
global button1
button1.config
button1.config(fg="red")
print(button1.cget("text") + "を赤くしました")
# ボタン2が押された時に実行する関数
def button2_click():
global button2
button2.config
button2.config(fg="red")
print(button2.cget("text") + "を赤くしました")
app = tkinter.Tk()
# ボタン1のインスタンス作成
button1 = tkinter.Button(
app,
text="ボタン1",
command=button1_click
)
button1.grid(column=0, row=0)
# ボタン2のインスタンス作成
button2 = tkinter.Button(
app,
text="ボタン2",
command=button2_click
)
button2.grid(column=1, row=0)
# メインループ
app.mainloop()
このスクリプトにはどのような問題があるでしょうか?
確かにそれでも判別できるね!
でもボタンが50あったら、その関数を50個も自分で作らないとダメだね…
そう、上記スクリプトでもできるのですが、数が多いとその分用意する関数も多くなってしまいます。
今回は大量にボタンがある場合でも簡潔なスクリプトで目的を達成する方法を紹介します。
Contents
bind
を使う
ボタンウィジェットではコンストラクタに command
を指定でき、ボタンクリック時の動作(実行する関数・イベントハンドラ)を設定することができます(イベントハンドラとは、イベント発生時に実行する関数・メソッドのことです)。
ですので、ついつい command
を利用してボタンクリック時の動作を設定しがちです。
ですが、bind
でもボタンクリック時の動作(実行する関数・イベントハンドラ)の設定は可能ですし、どのボタンが押されたかを判別したいのであれば bind
を使った方が楽です。
bind
を使う理由
なぜなら bind
で設定したイベントハンドラには引数としてイベントの詳細な情報が渡されるからです。
そして、その情報の中にはイベントがどのウィジェットから発生したイベントであるかの情報も含まれます。
ですので、どのボタンが押されてそのイベントハンドラが実行されたかの判別は、その引数に含まれるウィジェットの情報を利用することで実現することができます。
つまり、ボタンが押された時に実行されるイベントハンドラは1つしか用意しませんが、引数からどのボタンが押されたかを判別します。
bind
やイベント、イベントハンドラに引数として渡される情報は下記ページで解説していますので、こちらも合わせて読んでいただけると幸いです。
スポンサーリンク
bind
で押されたボタンを判別する手順
では bind
を利用して押されたボタンを判別する手順を解説していきます。
手順は下記のようになります。
- イベントハンドラの作成
- 以下をボタンの数だけ繰り返す
- ボタンを作成する
bind
を実行する
イベントハンドラの作成
まずはボタンが押された時に実行するイベントハンドラを作成します。
このイベントハンドラは通常の関数と同様に作成することが可能ですが、必ず “1つ” の引数を持たせます。
def button_func(event):
# ボタンが押された時に実行する処理
イベントハンドラが実行される時には、その引数として tkinter.Event
クラスのオブジェクトとして「イベントの情報」が渡されます。
引数で渡されるこの「イベントの情報」がどのようなものであるかは下記ページで解説しています詳しくはコチラを参照してください。
Tkinterの使い方:イベント処理を行う「このイベントの情報(tkinter.Event
クラスのオブジェクト)」の中で、どのボタンが押されたかを判別するのに重要なのは widget
メンバです。
event.widget
この widget
はイベント発生のトリガーとなったウィジェットのインスタンスを参照するメンバです。
したがって widget
メンバからウィジェットが実行可能なメソッドも実行するができます。
なので、ボタンの文字の色を赤色にするのであれば、下記のように config
メソッドにより設定することができます。
event.widget.config(fg="red")
またボタンのテキストは下記のように cget
メソッドにより取得することができます。
event.widget.cget("text")
イベントハンドラ全体の例を示すと下記のようになります。
def button_func(event):
event.widget.config(fg="red")
print(event.widget.cget("text") + "を赤くしました")
実際にボタン後された時にこのイベントハンドラが実行されるように bind
を実行すれば、ボタンが押された時に、その押されたボタンの文字の色が赤色になり、そのボタンに表示されているテキスト + "を赤くしました"
が標準出力に出力されるようになります。
ボタンの作成と bind
の実行
あとはボタンの作成と bind
を実行するだけです。
ボタンの作成を行い、そのボタンが押された時に先ほど作成したイベントハンドラが実行されるように bind
を実行します。
50個のボタン(横方向に5個・縦方向に10個)をループ処理で作成し、それぞれのボタンに対して bind
を実行する例は下記のようになります。
# ボタンを50個配置
for j in range(10):
for i in range(5):
# ボタンの名前(テキスト)を設定
button_name = "ボタン(" + str(i) + "," + str(j)+ ")"
# ボタンのインスタンス作成
button = tkinter.Button(
app,
text=button_name
)
# ボタンを2次元的に配置
button.grid(column=i, row=j)
# ボタンクリック時のイベント設定
button.bind("<ButtonPress>", button_func)
下記でボタンを作成し、
# ボタンのインスタンス作成
button = tkinter.Button(
app,
text=button_name
)
下記でその作成したボタンのイベント設定を行っています。
# ボタンクリック時のイベント設定
button.bind("<ButtonPress>", button_func)
上記により "<ButtonPress>"
のイベントが発生した時(マスボタンのクリックが発生した時)に、button_func
関数が実行されるようになります。
どのボタンを押しても同じ button_func
関数が実行されることになりますが、button_func
で widget
メンバからどのボタンが押されたかは判別できるので問題ありません。
bind
で押されたボタンを判別するスクリプト例
bind
で押されたボタンを判別するスクリプト全体の例は下記のようになります。
import tkinter
def button_func(event):
event.widget.config(fg="red")
print(event.widget.cget("text") + "を赤くしました")
app = tkinter.Tk()
# ボタンを50個配置
for j in range(10):
for i in range(5):
# ボタンの名前(テキスト)を設定
button_name = "ボタン(" + str(i) + "," + str(j)+ ")"
# ボタンのインスタンス作成
button = tkinter.Button(
app,
text=button_name
)
# ボタンを2次元的に配置
button.grid(column=i, row=j)
# ボタンクリック時のイベント設定
button.bind("<ButtonPress>", button_func)
# メインループ
app.mainloop()
スクリプトを実行すれば下のようなボタンが50個配置された画面が表示されます。
ボタンを押すと、押したボタンの文字の色が赤色に変化します。
さらに「ボタン(x,y)を赤くしました」と、どのボタンが押されてどのボタンを赤くしたかの情報が標準出力に出力されます。
この動きから、どのボタンが押されたかを判別し、判別した結果からそのボタンの設定変更や設定取得ができていることが確認できると思います。
クロージャを利用する
2つ目の方法は「クロージャを利用する」方法になります。
クロージャと聞くと難しそうに感じるかもしれませんが、実は考え方は簡単です。
最初に紹介したスクリプトと考え方は同じで、ボタンの数だけ関数を用意し、どの関数が実行されたかによってボタンを判別します。
ただし自力で関数を作る(ボタンの数だけ自分で関数を記述する)のは大変なので、クロージャの仕組みを利用して自動的に関数を作成するようにしているだけです。
スポンサーリンク
クロージャでボタンが判別できる理由
まず前置きとしてはクロージャについて簡単に解説しておきます。
クロージャとは
クロージャとは他の関数によって動的に作成される関数で、作成された時の変数の値を覚えているという特徴があります。
クロージャを利用した動的な関数の作成
例えば下記のような構造の outer
関数を作成したとします。
def outer(x):
def inner():
print("xは" + str(x) + "です")
return inner
この時、inner
関数がクロージャとはで解説した「クロージャ」に当てはまり、outer
関数がクロージャとはで解説した「他の関数」に当てはまります。つまり outer
関数が inner
関数を動的に作成します。
outer
関数実行時には inner
関数は実行されず、inner
関数を新たに作成して返却するだけの関数になります。
inner
関数は outer
関数の引数 x
を利用しており、作成される時にこの引数 x の値を覚えた状態で作成されることになります。
例えば引数 x
が 3
であれば、outer
関数実行時に作成される inner
関数は下記のような関数になるイメージです。
def inner():
x = 3
print("xは" + str(x) + "です")
引数 x
が 10
であれば、outer
関数実行時に作成される inner
関数は下記のような関数になります。
def inner():
x = 10
print("xは" + str(x) + "です")
こんな感じで、outer
関数から返却されるのは同じ inner
関数ですが、outer
関数実行時に指定された x
の値を覚えているので、異なる関数として動作することになります。
outer
関数を実行するたびに異なる関数が作成されますので、例えばループの中で outer
関数に渡す引数を変化させながら outer
関数を実行することで、自動的に多くの関数を簡単に作成することができます。
また、outer
関数からは作成した inner
関数が返却されます。
ですので、その戻り値を参照させることで、後から作成した inner
関数を、outer
関数実行時に指定した引数を覚えた状態で動作させることができます。
下記がその例となるスクリプトになります。
def outer(x):
def inner():
print("xは" + str(x) + "です")
return inner
a = outer(3)
b = outer(10)
b()
a()
実行すると、下記のような文字列が出力されます。
xは10です xは3です
outer
関数実行時の引数を覚えた状態の inner
関数が b()
と a()
により実行されていることが確認できると思います。
クロージャを利用した押されたボタンの判別
つまり、押されたボタンがどのボタンであるかを判別するためには、ボタン毎にそのボタン専用のクロージャを動的に作成してやれば良いです。
これにより、押されたボタンに応じて実行される関数が変わるので、実行される関数によってどのボタンが押されたかが判別可能になります。
もちろん、その押されたボタンに対して、ボタンの色を変更したり、ボタンに表示されているテキストを取得するなどの処理を行うこともできます。
クロージャで押されたボタンを判別する手順
ではクロージャを利用して押されたボタンを判別する手順を解説していきます。
手順は下記のようになります。
- クロージャを作成する関数の用意
- 以下をボタンの数だけ繰り返す
- ボタンを作成する
- クロージャを作成する
command
を設定する
クロージャを作成する関数の用意
まずはクロージャを作成する関数を用意します。用意するのは、ここまでの例でいうところの outer
関数になります。
関数自体は通常の関数同様に定義してやれば良いですが、下記の2点を行うところがポイントです。
- 内部にさらに関数をもたせ、その中にボタンが押された時に実行したい処理を記述する
- その関数を
return
させる
今回は、押されたボタンの文字の色を赤色にし、さらにそのボタンに表示されているテキストを出力したいため、下記のように関数を作成します。
# クロージャを作成する関数
def outer(button):
# ボタン専用の関数
def inner():
button.config(fg="red")
print(button.cget("text") + "を赤くしました")
return inner
上記で作成される関数は、outer
関数実行時に引数で指定したボタンウィジェットに対して処理を行う関数になります。
outer
関数実行に異なるボタンウィジェットを指定してやれば、異なるボタンウィジェットに対して処理を行う関数を作成することができます。
ボタンの作成・クロージャの作成・command
の設定
クロージャを作成する関数が用意できれば、あとはボタンの数だけ下記を行えば良いです。
- ボタンウィジェットを作成する
- そのボタンを引数に指定して
outer
関数を実行してそのボタン専用の関数(クロージャ)を作成する - ボタンが押された時に実行する関数をその作成した関数に設定する
50個のボタン(横方向に5個・縦方向に10個)をループ処理で作成し、それぞれのボタンに対してクロージャの作成を行う例は下記のようになります。
# ボタンを50個配置
for j in range(10):
for i in range(5):
# ボタンの名前(テキスト)を設定
button_name = "ボタン(" + str(i) + "," + str(j)+ ")"
# ボタンのインスタンス作成
button = tkinter.Button(
app,
text=button_name,
)
# ボタン(i,j)専用の関数を作成
func = outer(button)
# その関数をcommandにセット
button.config(command=func)
# ボタンを2次元的に配置
button.grid(column=i, row=j)
tkinter.Button()
はボタンウィジェットのコンストラクタなので、実行することで新たなボタンインスタンスが作成されて button
がそのインスタンスを参照することになります。
なので、クロージャを作成する outer
関数の引数には毎回異なるボタンが指定されることになります。
したがって、outer
関数を実行するたびに、毎回異なるボタンに対して処理(文字を赤くする・テキストを取得する)が実行されるクロージャが作成されることになります(要は作成されるクロージャは outer
関数実行時に引数で指定された button
を覚えている)。
さらに下記で、作成したボタンが押された時に作成したクロージャが実行されるように command
を設定しています。
button.config(command=func)
クロージャで押されたボタンを判別するスクリプト例
下記がクロージャを利用して押されたボタンを判別してボタンごとに処理を切り替えるスクリプト全体の例になります。
import tkinter
# クロージャを作成する関数
def outer(button):
# ボタン専用の関数
def inner():
button.config(fg="red")
print(button.cget("text") + "を赤くしました")
return inner
app = tkinter.Tk()
# ボタンを50個配置
for j in range(10):
for i in range(5):
# ボタンの名前(テキスト)を設定
button_name = "ボタン(" + str(i) + "," + str(j)+ ")"
# ボタンのインスタンス作成
button = tkinter.Button(
app,
text=button_name,
)
# ボタン(i,j)専用の関数を作成
func = outer(button)
# その関数をcommandにセット
button.config(command=func)
# ボタンを2次元的に配置
button.grid(column=i, row=j)
# メインループ
app.mainloop()
実行時の動作は bind で押されたボタンを判別するスクリプト例と同じですので紹介は省略しますが、実際に実行してみれば、このクロージャを用いる方法でも押されたボタンの判別ができ、押されたボタンに対して処理が実行できていることを確認できると思います。
スポンサーリンク
まとめ
このページでは Python tkinter において「押されたボタンを判別する方法」について解説しました。おそらくボタンを判別したい場面は多いと思いますので、是非この機会にこの方法を身につけておいてください!
コレ、実は簡単そうに思えて実はちょっと難しいです。
私もなかなか出来なくてハマりました…。
ボタンウィジェットは command
で押された時に実行される関数を指定できるので便利ですが、引数を渡そうと思うと難易度が上がります。
command
に lambda
式を利用して引数を渡そうと思えば渡すことも可能です。
ですが、lambda
式で引数を渡しても、ボタンを押されて関数が実行される際には、command
その時点の変数の値が引数として渡されてしまうので意図した通りに動かない可能性が高いです(ループ中の値が渡せない)。
とはいえ、理屈がわかってしまえば bind
やクロージャで簡単に押されたボタンを判別することは可能ですので、tkinter でのアプリ開発でどんどん活用して行ってください!
[…] クロージャの解説はこのサイトがとても丁寧に解説してくれている。参考にしてみるとよいだろう。 […]