문제

클릭하면 특정 문자열 시퀀스가 ​​포함된 메뉴가 표시되는 메뉴 버튼이 있습니다.해당 시퀀스에 정확히 어떤 문자열이 있는지 런타임까지는 알 수 없으므로 팝업 메뉴는 그 순간에 생성되어야 합니다.내가 가진 것은 다음과 같습니다.

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

중요한 부분은 "if menu_tags:" 아래에 있는 내용입니다. -- menu_tags가 ['stack', 'over', 'flow'] 목록이라고 가정합니다.그러면 내가 하고 싶은 일은 사실상 다음과 같습니다.

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)

여기서 add_tag_stack()은 다음과 같이 정의됩니다.

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

등등.

문제는 'tag' 변수가 'stack' 값과 'over' 값 등을 취하고 new_command가 호출될 때까지 평가되지 않는다는 것입니다. 이때 변수 'tag'는 ' 흐름'.따라서 추가되는 태그는 사용자가 무엇을 클릭하든 상관없이 항상 메뉴의 마지막 태그입니다.

나는 원래 람다를 사용하고 있었고 위와 같이 함수를 명시적으로 정의하는 것이 더 나을 것이라고 생각했습니다.어느 쪽이든 문제가 발생합니다.변수 'tag'의 복사본을 사용해 보았지만("current_tag = tag"를 사용하거나 복사 모듈을 사용하여) 문제가 해결되지 않습니다.왜 그런지 잘 모르겠습니다.

내 마음은 "평가"와 같은 쪽으로 방황하기 시작했지만 누군가가 그런 끔찍한 일을 포함하지 않는 영리한 방법을 생각해 낼 수 있기를 바랍니다.

정말 감사합니다!

(관련된 경우 Tkinter.__version__은 '$Revision:67083 $' 그리고 저는 Windows XP에서 Python 2.6.1을 사용하고 있습니다.)

도움이 되었습니까?

해결책

우선, 당신의 문제는 Tkinter와 아무 관련이 없습니다.문제를 보여주는 간단한 코드 조각으로 줄여서 더 쉽게 실험해 볼 수 있도록 하는 것이 가장 좋습니다.다음은 제가 실험한 작업의 단순화된 버전입니다.작은 테스트 사례를 쉽게 작성할 수 있도록 메뉴 대신 사전을 대체하고 있습니다.

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

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

    map[item] = new_command

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

이제 이것을 실행하면 말씀하신 대로 다음과 같은 결과를 얻습니다.

flow
flow
flow

여기서 문제는 Python의 범위 개념입니다.특히, for 문은 새로운 범위 수준을 도입하지 않으며 item;그래서 똑같이 업데이트 중이야 item 루프를 통과할 때마다 변수가 변하며 모든 new_command() 함수는 동일한 항목을 참조합니다.

당신이 해야 할 일은 각 항목에 대해 새로운 바인딩을 사용하여 새로운 수준의 범위를 도입하는 것입니다. item에스.이를 수행하는 가장 쉬운 방법은 이를 새 함수 정의로 래핑하는 것입니다.

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

    map[item] = item_command(item)

이제 이를 앞의 프로그램으로 대체하면 원하는 결과를 얻을 수 있습니다.

stack
over
flow

다른 팁

그런 종류의 일은 Tkinter에서 매우 흔한 문제라고 생각합니다.

(적절한 시점에) 다음을 시도해 보세요.

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

비슷한 오류가 발생했습니다.목록의 마지막 항목만 표시되었습니다.설정으로 해결됨

command=lambda x=i: f(x)

참고하세요 x=i ~ 후에 lambda.이 할당으로 인해 지역 변수가 i 바로 들어가 f(x) 당신의 기능 command.다음의 간단한 예가 도움이 되기를 바랍니다.

# 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()
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top