Skip to main content
 首页 » 编程设计

python之为什么当我从 lambda 调用 tkSimpleDialog.askstring 时 Tkinter 挂起

2025年05月04日11mfrbuaa

我正在开发一个模拟文章的 GUI 应用程序。除此之外,用户可以创建一个新主题,然后用注释填充该主题。目前,我有两种创建新主题的方法:通过菜单中的下拉选项(菜单命令)和通过主屏幕上的按钮(按钮命令)。该按钮以文本“新主题”开始。当用户按下按钮时,程序创建一个新主题,要求用户使用 tkSimpleDialog.askstring 命名主题。 , 然后将按钮的文本设置为主题名称和该主题中的注释数。该按钮的命令随后更改为向该主题添加注释。

在开发程序时,我首先验证了菜单命令是否有效。它调用askstring成功地创建了一个新的弹出窗口,以我想要的方式处理输入。但是,一旦我添加了按钮命令,就会调用 askstring失败,即使通过菜单命令调用也是如此。应该有 askstring 对话框的窗口变白了,程序挂起。如果我注释掉按钮命令,它会再次起作用。如果我注释掉菜单命令,它就会挂起。

这是我将命令添加到菜单的代码:

        TopicBtn.menu.add_command(label="New Topic", underline=0, 
                                  command=self.newTopic) 

这是 newTopic() 的代码:

 def newTopic(self, button=None): 
     """ Create a new topic. If a Button object is passed, associate that Button 
          with the new topic. Otherwise, create a new Button for the topic. """ 
 
     topicPrompt = "What would you like to call your new topic?" 
     topicName = tkSimpleDialog.askstring("New Topic", topicPrompt) 
 
     if topicName in self.topics.keys(): 
         print "Error: topic already exists" 
 
     else: 
         newTopic = {} 
         newTopic["name"] = topicName 
         newTopic["notes"] = [] 
         newTopic["button"] = self.newTopicButton(newTopic, button) 
 
         self.topics[topicName] = newTopic 
         self.addToTopicLists(newTopic) 

这是 newTopicButton() 的代码:

 def newTopicButton(self, topic, button=None): 
 """ If a Button object is passed, change its text to display the topic name. 
      Otherwise, create and grid a new Button with the topic name. """ 
 
     if button is None: 
         button = Button(self.topicFrame) 
         index = len(self.topics) 
         button.grid(row=index/self.TOPICS_PER_ROW, column=(index % 
             self.TOPICS_PER_ROW), sticky=NSEW, padx=10, pady=10) 
     else: 
         button.unbind("<Button-1>") 
 
     buttonText = "%s\n0 notes" % topic["name"] 
     button.config(text=buttonText) 
     button.config(command=(lambda s=self, t=topic: s.addNoteToTopic(t))) 
 
     return button 

最后,这是按钮命令的代码:

for col in range(self.TOPICS_PER_ROW): 
     button = Button(self.topicFrame, text="New Topic") 
     button.bind("<Button-1>", (lambda e, s=self: s.newTopic(e.widget))) 
     button.grid(row=0, column=col, sticky=NSEW, padx=10, pady=10) 

任何人都知道为什么将 lambda 表达式绑定(bind)到按钮会生成 askstring挂?

编辑:感谢您的评论。这是展示该行为的最小示例:

from Tkinter import * 
import tkSimpleDialog 
 
class Min(): 
 
    def __init__(self, master=None): 
        root = master 
        frame = Frame(root) 
        frame.pack() 
 
        button = Button(frame, text="askstring") 
        button.bind("<Button-1>", (lambda e, s=self: s.newLabel())) 
        button.grid() 
 
    def newLabel(self): 
        label = tkSimpleDialog.askstring("New Label", "What should the label be?") 
        print label 
 
root = Tk() 
m = Min(root) 
root.mainloop() 

请注意,从 button.bind("<Button-1>", (lambda e, s=self: s.newLabel())) 切换至 button = Button(frame, text="askstring", command=(lambda s=self: s.newLabel()))修复了错误(但没有给我按下按钮的引用)。我认为问题与将事件捕获为 lambda 的输入之一有关。

请您参考如下方法:

您在这里遇到的问题是因为调用了wait_window。在您正在使用的对话框中(您永远不会自己调用它,但实现该对话框的代码会调用它)。例如,以下代码在(可能)单击两次按钮后重现了该问题:

import Tkinter 
 
def test(event=None): 
    tl = Tkinter.Toplevel() 
    tl.wait_window(tl) 
 
root = Tkinter.Tk() 
btn = Tkinter.Button(text=u'hi') 
btn.bind('<Button-1>', test) 
btn.pack(padx=10, pady=10) 
root.mainloop() 

此调用 wait_window有效地做了 update 的事情命令可以,并且是为什么调用 update 的典型示例是一件坏事。它与 <Button-1> 冲突正在处理的事件,然后挂起。问题是你将不得不忍受 wait_window正在使用,因为它属于对话框的代码。显然,如果你绑定(bind)到 <ButtonRelease-1>那么这种冲突永远不会发生。您也可以使用 command按钮中的参数,也可以正常工作。

最后,我建议根据您想要实现的目标以更简洁的方式创建按钮:

for i in range(X): 
    btn = Tkinter.Button(text=u'%d' % i) 
    btn['command'] = lambda button=btn: some_callback(button)