Question

J'ai un bouton de menu, qui, une fois cliqué devrait afficher un menu contenant une séquence spécifique de chaînes. Exactement ce que les chaînes sont dans cet ordre, nous ne savons pas avant l'exécution, de sorte que le menu qui apparaît doit se faire à ce moment-là. Voici ce que j'ai:

class para_frame(Frame):
    def __init__(self, para=None, *args, **kwargs):
        # ...

        # menu button for adding tags that already exist in other para's
        self.add_tag_mb = Menubutton(self, text='Add tags...')

        # this menu needs to re-create itself every time it's clicked
        self.add_tag_menu = Menu(self.add_tag_mb,
                                 tearoff=0,
                                 postcommand = self.build_add_tag_menu)

        self.add_tag_mb['menu'] = self.add_tag_menu

    # ...

    def build_add_tag_menu(self):
        self.add_tag_menu.delete(0, END) # clear whatever was in the menu before

        all_tags = self.get_article().all_tags()
        # we don't want the menu to include tags that already in this para
        menu_tags = [tag for tag in all_tags if tag not in self.para.tags]

        if menu_tags:
            for tag in menu_tags:
                def new_command():
                    self.add_tag(tag)

                self.add_tag_menu.add_command(label = tag,
                                              command = new_command)
        else:
            self.add_tag_menu.add_command(label = "<No tags>")

La partie importante est la substance sous la rubrique « si menu_tags: » - menu_tags Supposer la liste [ « pile », « over », « flux »]. Alors ce que je veux faire est effectivement ceci:

self.add_tag_menu.add_command(label = 'stack', command = add_tag_stack)
self.add_tag_menu.add_command(label = 'over', command = add_tag_over)
self.add_tag_menu.add_command(label = 'flow', command = add_tag_flow)

où add_tag_stack () est définie comme suit:

def add_tag_stack():
    self.add_tag('stack')

et ainsi de suite.

Le problème est, le « tag » variable prend sur « pile » de la valeur et la valeur « plus » et ainsi de suite, et qu'il ne soit pas évalué jusqu'à ce que new_command est appelée, à quel point la variable « tag » est juste « flux ». Donc, l'étiquette qui est ajoutée est toujours le dernier dans le menu, peu importe ce que l'utilisateur clique sur.

J'utilisais à l'origine un lambda, et je pensais que peut-être définir explicitement la fonction comme ci-dessus pourrait mieux fonctionner. De toute façon le problème se produit. Je l'ai essayé d'utiliser une copie de la « étiquette » variable (soit avec « current_tag = tag » ou à l'aide du module de copie), mais cela ne résout pas. Je ne sais pas pourquoi.

Mon esprit commence à errer vers des choses comme « eval » mais j'espère que quelqu'un peut penser à une manière intelligente qui ne comporte pas de telles choses horribles.

Une grande merci!

(Dans le cas où il est pertinent, Tkinter .__ version__ retourne 'Révision $: 67083 $'. Et j'utilise Python 2.6.1 sous Windows XP)

Était-ce utile?

La solution

Tout d'abord, votre problème n'a rien à voir avec Tkinter; il est préférable si vous réduisez à un simple morceau de code illustrant votre problème, afin que vous puissiez expérimenter plus facilement. Voici une version simplifiée de ce que vous faites que j'expérimenté avec. Je remplace un dict en place du menu, pour le rendre facile d'écrire un test simple.

items = ["stack", "over", "flow"]
map = { }

for item in items:
    def new_command():
        print(item)

    map[item] = new_command

map["stack"]()
map["over"]()
map["flow"]()

Maintenant, quand nous exécutons cela, comme vous l'avez dit, nous obtenons:

flow
flow
flow

La question ici est la notion de champ de Python. En particulier, la déclaration de for ne présente pas un nouveau niveau de la portée, ni une nouvelle liaison pour item; il est donc mise à jour de la même variable item chaque fois dans la boucle, et toutes les fonctions de new_command() faites référence à ce même article.

Ce que vous devez faire est d'introduire un nouveau niveau de champ, avec une nouvelle liaison, pour chacun des items. La meilleure façon de le faire est de l'envelopper dans une nouvelle définition de la fonction:

for item in items:
    def item_command(name):
        def new_command():
            print(name)
        return new_command

    map[item] = item_command(item)

Maintenant, si vous substituez que dans le programme précédent, vous obtenez le résultat souhaité:

stack
over
flow

Autres conseils

Ce genre de chose est tout à fait un problème commun dans Tkinter, je pense.

Essayez ceci (au point approprié):

def new_command(tag=tag):
    self.add_tag(tag)

J'ai eu une erreur semblable. Seul le dernier élément de la liste a été affichée. Fixe en réglant

command=lambda x=i: f(x)

Notez le x=i après lambda. Cette affectation rend votre variable locale i aller à droite dans la fonction f(x) de votre command. Hope, cet exemple simple vous aidera:

# Using lambda keyword to create a dynamic menu.
import tkinter as tk

def f(x):
    print(x)

root = tk.Tk()
menubar = tk.Menu(root)
root.configure(menu=menubar)
menu = tk.Menu(menubar, tearoff=False)
l = ['one', 'two', 'three']
for i in l:
    menu.add_command(label=i, command=lambda x=i: f(x))
menubar.add_cascade(label='File', menu=menu)
root.mainloop()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top