Domanda

Ho un MenuButton, che se cliccato dovrebbe visualizzare un menu contenente una sequenza specifica di stringhe. Esattamente quello che le stringhe sono in quella sequenza, non sappiamo fino al runtime, in modo che il menu che si apre deve essere generata in quel momento. Ecco quello che ho:

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 parte importante è la roba in "se menu_tags:" - menu_tags Supponiamo che l 'elenco [ 'pila', 'over', 'flusso']. Poi quello che voglio fare è effettivamente questo:

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)

dove add_tag_stack () è definito come:

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

e così via.

Il problema è che il 'tag' variabile assume 'pila' il valore e quindi il valore 'over' e così via, e che non venga valutato fino new_command si chiama, a quel punto il 'tag' variabile è solo 'flusso'. Così il tag che viene aggiunto è sempre l'ultimo sul menu, non importa ciò che l'utente fa clic su.

Mi è stato originariamente usando un lambda, e ho pensato che forse definire esplicitamente la funzione di cui sopra potrebbe funzionare meglio. In entrambi i casi il problema si verifica. Ho provato con una copia del 'tag' variabile (sia con "current_tag = tag" o utilizzando il modulo di copia), ma che non lo risolve. Io non so perché.

La mia mente sta cominciando a vagare verso cose come "eval" ma sto sperando che qualcuno possa pensare ad un modo intelligente che non coinvolge queste cose orribili.

Molto grazie!

(In caso è rilevante, Tkinter .__ version__ restituisce '$ Revision: 67083 $. E sto usando Python 2.6.1 su Windows XP)

È stato utile?

Soluzione

Prima di tutto, il problema non ha nulla a che fare con Tkinter; è meglio se si riduce verso il basso per un semplice pezzo di codice che illustrano il problema, in modo da poter sperimentare con più facilmente. Ecco una versione semplificata di quello che stai facendo, che ho sperimentato con. Sto sostituendo un dict al posto del menù, per rendere più facile scrivere un piccolo banco di prova.

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

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

    map[item] = new_command

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

Ora, quando eseguiamo questo, come hai detto, otteniamo:

flow
flow
flow

Il problema qui è la nozione di Python di portata. In particolare, la dichiarazione for non introduce un nuovo livello di portata, né una nuova associazione per item; quindi viene aggiornata la stessa variabile item ogni iterazione del ciclo, e tutte le funzioni new_command() riferiamo a quello stesso elemento.

Quello che dovete fare è introdurre un nuovo livello di portata, con una nuova associazione, per ognuno dei items. Il modo più semplice per farlo è quello di avvolgerla in una nuova definizione di funzione:

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

    map[item] = item_command(item)

Ora, se si sostituisce che nel programma precedente, si ottiene il risultato desiderato:

stack
over
flow

Altri suggerimenti

Questo genere di cose è piuttosto un problema comune in Tkinter, credo.

Prova questo (al momento opportuno):

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

Ho avuto un errore simile. è stato mostrato solo l'ultimo elemento della lista. Fisso impostando

command=lambda x=i: f(x)

Si noti la x=i dopo lambda. Questa assegnazione rende la variabile locale i andare a destra nella funzione f(x) del vostro command. Speranza, questo semplice esempio aiuterà:

# 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()
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top