web-dev-qa-db-fra.com

tkinter création de boutons pour la boucle en passant des arguments de commande

J'essaie de créer des boutons dans tkinter dans une boucle for. Et à chaque boucle, passez la valeur i count comme argument dans la valeur de commande. Ainsi, lorsque la fonction est appelée à partir de la valeur de commande, je peux dire quel bouton a été enfoncé et agir en conséquence. Le problème est, disons que len est 3, il créera 3 boutons avec les titres "Game 1" à "Game 3" mais lorsque l'un des boutons est pressé, la valeur imprimée est toujours 2, la dernière itération. Il semble donc que les boutons soient créés en tant qu'entités distinctes, mais la valeur i dans les arguments de commande semble être la même. Voici le code:

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)

Existe-t-il un moyen d'obtenir la valeur actuelle de i, à chaque itération, pour coller avec ce bouton particulier?

27
Marcel

Changez votre lambda en lambda i=i: self.open_this(i).

Cela peut sembler magique, mais voici ce qui se passe. Lorsque vous utilisez ce lambda pour définir votre fonction, l'appel open_this n'obtient pas la valeur de la variable i au moment où vous définissez la fonction. Au lieu de cela, il fait une fermeture, qui est un peu comme une note à elle-même disant "Je devrais chercher quelle est la valeur de la variable i au moment où je suis appelé". Bien sûr, la fonction est appelée après la fin de la boucle, donc à ce moment-là, je serai toujours égal à la dernière valeur de la boucle.

En utilisant le i=i astuce amène votre fonction à stocker la valeur actuelle de i au moment où votre lambda est défini, au lieu d'attendre de rechercher la valeur de i plus tard.

54
BrenBarn

C'est ainsi que fonctionnent les fermetures en python. J'ai rencontré ce problème moi-même une fois. Vous pouvez utiliser functools.partial pour cela.

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

Attachez simplement la portée de vos boutons dans une fonction lambda comme celle-ci:

btn["command"] = lambda btn=btn: click(btn)click(btn) est la fonction qui passe dans le bouton lui-même. Cela créera une portée de liaison du bouton à la fonction elle-même.

Fonctionnalités:

  • Personnaliser la taille de la grille
  • Redimensionnement réactif
  • Basculer l'état actif

#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