Pergunta

Eu tenho um botão MENU, que quando clicado deve exibir um menu contendo uma seqüência específica de cordas. Exatamente o que as cordas são nessa seqüência, não sabemos até a execução, de modo que o menu que aparece deve ser gerada naquele momento. Aqui está o que eu tenho:

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>")

A parte importante é o material em "se menu_tags:" - menu_tags Suponha que a lista [ 'pilha', 'over', 'fluir']. Então o que eu quero fazer é efetivamente o seguinte:

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)

onde add_tag_stack () é definido como:

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

e assim por diante.

O problema é que a variável 'tag' assume o valor 'pilha' e, em seguida, o valor 'over' e assim por diante, e ele não é avaliada até new_command é chamado, altura em que o 'tag' variável é apenas 'fluxo'. Assim, a tag que é adicionado é sempre o último no menu, não importa o que o usuário clica em.

Eu estava originalmente usando um lambda, e eu pensei que talvez definir explicitamente a função de como o trabalho de força acima melhor. De qualquer forma, o problema ocorre. Eu tentei usar uma cópia da variável 'tag' (ou com "current_tag = tag" ou usando o módulo de cópia), mas que não resolve-lo. Eu não sei por que.

A minha mente está começando a vagar em direção a coisas como "eval", mas eu estou esperando que alguém pode pensar em uma maneira inteligente que não envolve coisas tão horríveis.

Muito obrigado!

(No caso de ser relevante, Tkinter .__ version__ retorna '$ Revision: 67083 $'. E eu estou usando Python 2.6.1 no Windows XP)

Foi útil?

Solução

Em primeiro lugar, o seu problema não tem nada a ver com Tkinter; é melhor se você reduzi-lo para baixo a um simples pedaço de código demonstrando o seu problema, então você pode experimentar com ele mais facilmente. Aqui está uma versão simplificada do que você está fazendo que eu experimentei com. Estou substituindo um dicionário no lugar do menu, para tornar mais fácil para escrever um caso de teste pequeno.

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

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

    map[item] = new_command

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

Agora, quando nós executamos este, como você disse, temos:

flow
flow
flow

A questão aqui é a noção do escopo do Python. Em particular, a declaração for não introduz um novo nível de escopo, nem uma nova ligação para item; por isso está atualizando a mesma variável item cada vez através do loop, e todas as funções new_command() nos referindo a esse mesmo item.

O que você precisa fazer é introduzir um novo nível de escopo, com uma nova ligação, para cada um dos items. A maneira mais fácil de fazer isso é envolvê-lo em uma nova definição de função:

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

    map[item] = item_command(item)

Agora, se você substituir que no programa anterior, você obter o resultado desejado:

stack
over
flow

Outras dicas

Esse tipo de coisa é um problema comum em Tkinter, eu acho.

Tente isto (no ponto apropriado):

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

Eu tive um erro semelhante. Apenas o último item da lista foi mostrado. Fixado por definição

command=lambda x=i: f(x)

Observe o x=i após lambda. Esta tarefa faz com que a variável i ir direito local na função f(x) do seu command. Hope, este exemplo simples vai ajudar:

# 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()
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top