web-dev-qa-db-ja.com

tkinterがコマンド引数を渡すforループでボタンを作成する

Forループ内のtkinterでボタンを作成しようとしています。そして、各ループで、iカウント値をコマンド値の引数として渡します。したがって、関数がコマンド値から呼び出されたときに、どのボタンが押されたかがわかり、それに応じて動作します。問題は、lenが3であるとしましょう。「ゲーム1」から「ゲーム3」までのタイトルの付いた3つのボタンが作成されますが、いずれかのボタンが押されると、出力される値は常に2、最後の反復です。したがって、ボタンは個別のエンティティとして作成されているように見えますが、コマンド引数のi値はすべて同じように見えます。これがコードです:

def createGameURLs(self):
    self.button = []
    for i in range(3):
        self.button.append(Button(self, text='Game '+str(i+1),command=lambda:self.open_this(i)))
        self.button[i].grid(column=4, row=i+1, sticky=W)
def open_this(self, myNum):
    print(myNum)

特定のボタンを使い続けるために、反復ごとに現在のi値を取得する方法はありますか?

27
Marcel

ラムダをlambda i=i: self.open_this(i)に変更します。

これは不思議に見えるかもしれませんが、これが起こっていることです。そのラムダを使用して関数を定義する場合、open_this呼び出しは、関数を定義するときに変数iの値を取得しません。代わりに、それはクロージャを作成します。これは、「変数iの値が何であるかを調べる必要がある呼び出された時点で」と言うようなメモです。もちろん、関数はループが終了した後に呼び出されるので、そのとき私は常にループの最後の値に等しくなります。

i=iトリックを使用すると、ラムダの定義時に関数がiの現在の値を保存し、後でiの値の検索を待つのではありません。

54
BrenBarn

これは、Pythonでクロージャーが機能する方法です。私はこの問題に一度遭遇しました。 functools.partial このため。

for i in range(3):
    self.button.append(Button(self, text='Game '+str(i+1), command=partial(self.open_this, i)))
6
lukad

次のようなラムダ関数内にボタンスコープをアタッチするだけです。

btn["command"] = lambda btn=btn: click(btn)click(btn)は、ボタン自体に渡す関数です。これにより、ボタンから関数自体へのバインディングスコープが作成されます。

特徴:

  • グリッドサイズをカスタマイズする
  • レスポンシブなサイズ変更
  • アクティブ状態を切り替え

#Python2
#from Tkinter import *
#import Tkinter as tkinter
#Python3
from tkinter import *
import tkinter

root = Tk()
frame=Frame(root)
Grid.rowconfigure(root, 0, weight=1)
Grid.columnconfigure(root, 0, weight=1)
frame.grid(row=0, column=0, sticky=N+S+E+W)
grid=Frame(frame)
grid.grid(sticky=N+S+E+W, column=0, row=7, columnspan=2)
Grid.rowconfigure(frame, 7, weight=1)
Grid.columnconfigure(frame, 0, weight=1)

active="red"
default_color="white"

def main(height=5,width=5):
  for x in range(width):
    for y in range(height):
      btn = tkinter.Button(frame, bg=default_color)
      btn.grid(column=x, row=y, sticky=N+S+E+W)
      btn["command"] = lambda btn=btn: click(btn)

  for x in range(width):
    Grid.columnconfigure(frame, x, weight=1)

  for y in range(height):
    Grid.rowconfigure(frame, y, weight=1)

  return frame

def click(button):
  if(button["bg"] == active):
    button["bg"] = default_color
  else:
    button["bg"] = active

w= main(10,10)
tkinter.mainloop()

enter image description hereenter image description here

enter image description here

0
Joel