##今回紹介すること
TkinterでButtonウィジェット等のオプション:commnadに引数を渡す方法を紹介します。
サンプルコード(成功例)
import tkinter as tk
import tkinter.ttk as ttk
class Application(ttk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.master.title("Title")
self.master.geometry("400x300")
self.create_widgets()
def create_widgets(self):
for i in range(1, 11):
ttk.Button(self, text=f"ボタン{i}", command=self.show_message(i)).pack()
def show_message(self, index):
def inner():
print(f"ボタン{index}がクリックされました")
return inner
def main():
root = tk.Tk()
app = Application(master=root)
app.mainloop()
if __name__ == "__main__":
main()
やりがちな失敗例と解決法
なぜ失敗するのか
普通に書こうとすると,以下のように書いてしまいがちです:
import tkinter as tk
import tkinter.ttk as ttk
class Application(ttk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.master.title("Title")
self.master.geometry("400x300")
self.create_widgets()
def create_widgets(self):
for i in range(1, 11):
ttk.Button(self, text=f"ボタン{i}", command=self.show_message(i)).pack()
def show_message(self, index):
print(f"ボタン{index}がクリックされました")
def main():
root = tk.Tk()
app = Application(master=root)
app.mainloop()
if __name__ == "__main__":
main()
残念ながら,これでは期待どおりの動作はしません。
動かしてみればわかりますが,ボタン生成時にself.show_message(i)
が実行され,ボタン{index}がクリックされました
がすべてプリントされてしまいます。そして,肝心のボタンクリック時には,なんの動作もなされません。これは,commandオプションの挙動が下記のようになっていることに関係しています。
-
ウィジェット生成時:
self.show_message(i)
が実行される -
ボタンクリック時:
self.show_message(i)()
が実行される
解決法
上記のような面倒くさい仕様のため,callback関数を定義する際にも一工夫が必要となります。必要な部分のコード(今回の場合はprint(f"ボタン{index}がクリックされました")
)をself.show_message(i)
で必要な部分のコードを実行させず,self.show_message(i)()
で実行させる必要があるのです。
そのために,**Inner Function(内部関数)**を使用します。下記のようになっていると考えるとわかりやすいです。
def show_message(index):
def inner():
print(f"ボタン{index}がクリックされました")
return inner
command = show_message(1)
command()
show_message関数にはinner関数(内部関数)が定義されており,それをリターンしています。つまり,下から2行目のcommand
にはinner関数が入っています。ただし,inner関数の中身は実行されていませんので,printはされません。そして最後の行で,inner関数を実行しています。
つまり,show_message(1)
では見た目上何も起こらないが,show_message(1)()
とすることで目的の部分を実行できるようになるということです。
##参考
- python tkinter どのボタンが押されたか判定する方法 - memopy http://memopy.hatenadiary.jp/entry/2017/06/11/220452
- Tkinter の command option で引数を渡すときには関数でネストを定義すると思ったとおりに動く https://qiita.com/yukid/items/a0140b5c1a3e28f636f0