2009-04-08 38 views
6

Tôi có một menubutton, khi được nhấp vào sẽ hiển thị một trình đơn chứa chuỗi chuỗi cụ thể. Chính xác những gì chuỗi trong chuỗi đó, chúng tôi không biết cho đến khi thời gian chạy, do đó, trình đơn bật lên phải được tạo ra tại thời điểm đó. Dưới đây là những gì tôi có:Tự động tạo menu trong Tkinter. (biểu thức lambda?)

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

Phần quan trọng là nội dung trong "if menu_tags:" - Giả sử menu_tags là danh sách ['stack', 'over', 'flow']. Sau đó, những gì tôi muốn làm là một cách hiệu quả này:

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) 

nơi add_tag_stack() được định nghĩa là:

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

và vân vân.

Vấn đề là, 'thẻ' biến mất trên giá trị 'ngăn xếp' và sau đó là giá trị 'quá' và cứ tiếp tục và nó không được đánh giá cho đến khi new_command được gọi, tại thời điểm đó thẻ biến 'chỉ là' dòng chảy '. Vì vậy, thẻ được thêm vào luôn là thẻ cuối cùng trên menu, bất kể người dùng nhấp vào gì.

Ban đầu tôi đã sử dụng lambda và tôi nghĩ có thể xác định rõ ràng chức năng như trên có thể hoạt động tốt hơn. Dù bằng cách nào vấn đề xảy ra. Tôi đã thử sử dụng một bản sao của biến 'tag' (hoặc với "current_tag = tag" hoặc sử dụng module sao chép) nhưng điều đó không giải quyết được. Tôi cung không chăc tại sao.

Tâm trí của tôi đang bắt đầu đi lang thang hướng tới những thứ như "eval" nhưng tôi hy vọng ai đó có thể nghĩ ra một cách thông minh không liên quan đến những điều kinh khủng như vậy.

Cảm ơn nhiều!

(Trong trường hợp nó có liên quan, Tkinter .__ version__ lợi nhuận '$ Xem xét lại: 67.083 $' và tôi đang sử dụng Python 2.6.1 trên Windows XP.)

Trả lời

6

Trước hết, vấn đề của bạn không có bất cứ điều gì để làm với Tkinter; tốt nhất là bạn nên giảm nó xuống một đoạn mã đơn giản thể hiện vấn đề của bạn, vì vậy bạn có thể thử nghiệm nó dễ dàng hơn. Đây là một phiên bản đơn giản của những gì bạn đang làm mà tôi đã thử nghiệm. Tôi thay thế một dict ở vị trí của menu, để làm cho nó dễ dàng để viết một trường hợp thử nghiệm nhỏ.

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

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

    map[item] = new_command 

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

Bây giờ, khi chúng ta thực hiện điều này, như bạn nói, chúng tôi nhận được:

flow 
flow 
flow 

Vấn đề ở đây là khái niệm về phạm vi của Python. Cụ thể, tuyên bố for không giới thiệu cấp độ phạm vi mới, cũng không phải ràng buộc mới cho item; do đó, nó đang cập nhật cùng một biến số item mỗi lần qua vòng lặp và tất cả các chức năng new_command() đều đề cập đến cùng một mục đó.

Những gì bạn cần làm là giới thiệu cấp độ phạm vi mới, với một ràng buộc mới, cho mỗi một trong số item s.Cách dễ nhất để làm điều đó là để bọc nó trong một định nghĩa chức năng mới:

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

    map[item] = item_command(item) 

Bây giờ, nếu bạn thay thế đó vào chương trình trước đó, bạn sẽ có được kết quả mong muốn:

stack 
over 
flow 
+0

Tôi nghĩ rằng có thể có một giải pháp cụ thể cho Tkinter. Có người nói "Không không không, cách bạn làm điều này là chức năng đặc biệt Tkinter.somethingOrOther()" Cảm ơn sự giúp đỡ! – MatrixFrog

+0

Không sao cả! Tôi chỉ muốn chỉ ra rằng một bước đầu tiên tốt là cố gắng tách biệt một ví dụ nhỏ về vấn đề này, để xem đó có phải là vấn đề ngôn ngữ hay vấn đề về API hay không. –

2

Đó là điều mà là một vấn đề khá phổ biến ở Tkinter , Tôi nghĩ.

Hãy thử điều này (tại thời điểm thích hợp):

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

Tôi hoàn toàn không biết tại sao điều này lại hiệu quả! Mặc dù tôi thích giải thích chi tiết về câu trả lời khác, tôi sẽ sử dụng câu trả lời này vì nó ngắn hơn một chút. Cảm ơn! – MatrixFrog

+0

Tôi nghĩ rằng nó hoạt động vì đối số mặc định tag = tag trong danh sách đối số hàm. Làm điều này tạo ra tên 'tag' trong phạm vi hàm, trỏ đến thẻ giá trị trong phạm vi của người gọi. Hãy sửa tôi nếu tôi sai, tôi hiện đang vật lộn với các câu hỏi về phạm vi tương tự. –

+0

Về cơ bản, cấu trúc 'cho x trong ..' là một trong số ít địa điểm ở trăn nơi tên (x) được phục hồi. Vì vậy, bạn cần tạo một tên mới có cùng giá trị với x tại điểm thích hợp. Bạn làm điều này với 'tag = tag' được đánh giá ở định nghĩa hàm, và sau đó không được thực thi hàm. –

0

Tôi đã có một lỗi tương tự. Chỉ mục cuối cùng trong danh sách được hiển thị. Cố định bằng cách thiết lập

command=lambda x=i: f(x)

Lưu ý x=i sau lambda. Bài tập này làm cho biến cục bộ của bạn i chuyển ngay vào hàm f(x) của số command của bạn. Hy vọng, ví dụ đơn giản này sẽ giúp:

# 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() 
Các vấn đề liên quan