LoginSignup
3
4

More than 3 years have passed since last update.

Tkinter の command option で引数を渡すときには関数でネストを定義すると思ったとおりに動く

Last updated at Posted at 2019-06-16

最初に

Pythonを勉強し始めて1ヶ月くらい経過し、GUIでなにか作ってみたいなぁと考えTkinterに手を出した際に
command optionの挙動でハマったので備忘録的に記事にしてみました。

実現したいこと

1.png
要はチェックボタンにチェックを付けた/外した際に、自分の左側にあるチェックボックスも
動的に自動でチェックが付く/外れるようにしたかったんです。
実行環境はPython3.7です。

実装したコード

とりあえず何も考えずに「これでえやろ!」と下記のコードを書きましたが、想定どおりに動きませんでした。
各チェックボックスにチェックを入れても、それより左側のチェックボックスは動かざること山のごとし。

import tkinter as tk

#GUI#
root = tk.Tk()

cb = {} #チェックボックスの保管#
cb_val = {} #各チェックボックスの値(BooleanVar)を保管#

#チェックボックスの値を生成#
for i in range(4):
   for j in range(3):
       cb_val[i,j] = tk.BooleanVar()

#チェックボックスがクリックされたときのアクション#
def Check(i,j):
    if cb_val[i,j].get() == True:
        for k in range(j):
            cb_val[i,k].set(True)#自分より左のチェックボックスを自分と同じ状態にする#
    else:
        for k in range(j):
            cb_val[i,k].set(False)#自分より左のチェックボックスを自分と同じ状態にする#

#チェックボックス生成#
for i in range(4):
   for j in range(3):
       cb[i,j] = tk.Checkbutton(text=u"チェックボックス"+ str(i) + "-"+ str(j),\
                                variable=cb_val[i,j],command=Check(i,j))

#アイテム配置
for i in range(4):
   for j in range(3):
       cb[i,j].grid(row=i,column=j)

root.mainloop()

試したこと

動かないのでいろいろ調べたのですが、Checkbutton の command otption は引数が渡せないらしい。
※というか多分 Tkinter の widget で command option がとれるやつは全部引数渡せない?
それならlambda使ったらええやんけと考えて下記のように修正してみましたがだめでした。

#チェックボックス生成#
for i in range(4):
   for j in range(3):
       cb[i,j] = tk.Checkbutton(text=u"チェックボックス"+ str(i) + "-"+ str(j),\
                                variable=cb_val[i,j],command=lambda :Check(i,j))

正確に言うと、一番右下のチェックボックスを押したときのみ想定動作をします(一歩前進)
ただし、一番左下、真ん中一番下のチェックボックスは押してもチェックがつかなくなりました(一歩後退)

何がどうなってるのか

原因切り分けのために、下記のようにCheck(i,j)print()を加えます。

def Check(i,j):
    print(i)
    print(j)
    if cb_val[i,j].get() == True:
        for k in range(j):
            cb_val[i,k].set(True)#自分より左のチェックボックスを自分と同じ状態にする#
    else:
        for k in range(j):
            cb_val[i,k].set(False)#自分より左のチェックボックスを自分と同じ状態にする#

この状態でcommand=lambda :Check(i,j)入りのチェックボックスにチェックをつけてみます。

2019-06-16 (3).png

どのチェックボックスを押しても3,2が表示されます。
つまり、command=lambda :Check(i,j)がすべてのチェックボックスでcommand=lambda :Check(3,2)
になってしまっているということを表している(多分
よって、下記のような挙動になる。

 ・3-2をチェックすると3-0,3-1もチェックが入る。これはCheck(3,2)の想定動作
 ・3-0,3-1はクリックできない(ように見える)。
  これは、クリックした瞬間にCheck(3,2)が実行されて、3-2と同じ状態に変化するから
 ・0-0から2-2まではチェックすると自分だけ変化する。
  これはCheck(3,2)が実行されるものの、3-2のチェック状態は変化がないため

なので、とにかくlambdaは今回使えなさそう。
また、command=Check(i,j)のときには下記のようになります。
2019-06-16 (2).png

今度は、コード実行時に00~32までprintされます。
なのでcommand optionはwidget生成時に一度実行されることがわかりました。
ちなみに、チェックボックスをクリックしても何もprintされません。

つまりどうすればいい

さっぱりわかんねぇ!!!
だと悲しいので、もっと調べてみると、どうも command option が呼び出されるときには以下が起きてるらしい。

 ・command = Check(i,j)と書かれている場合、実際に実行されるのはCheck(i,j)()

コード上でCheck(i,j)()は定義されていないため、何も起きなかったんですね(ではなぜエラーコードが出ないんだ?謎
よって、Check(i,j)()を定義してあげれば万事解決する気がします。

#チェックボックスがクリックされたときのアクション#
def Check(i,j):
    def x():
        if cb_val[i,j].get() == True:
            for k in range(j):
                cb_val[i,k].set(True)#自分より左のチェックボックスを自分と同じ状態にする#
        else:
            for k in range(j):
                cb_val[i,k].set(False)#自分より左のチェックボックスを自分と同じ状態にする#
    return x

#チェックボックス生成#
for i in range(4):
   for j in range(3):
       cb[i,j] = tk.Checkbutton(text=u"チェックボックス"+ str(i) + "-"+ str(j),\
                                variable=cb_val[i,j],command=Check(i, j))

Check(i,j)にネストでx()を定義しました。
これでCheck(i,j)()が定義できたことになります。
上記コードで実行すると、想定どおりにチェックボックスが動作しました。やったぜ。

最後に

GUIが簡単に作れるのでTkinter非常に便利なのですが、
奥が深すぎて全然使いこなせてない感がはんぱないです。

3
4
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
4