【问题标题】:Why does Tkinter hang when I call tkSimpleDialog.askstring from a lambda?当我从 lambda 调用 tkSimpleDialog.askstring 时,为什么 Tkinter 会挂起?
【发布时间】:2013-01-30 20:58:01
【问题描述】:

我正在开发一个模拟文章的 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 表达式绑定到按钮会导致 askstring 挂起吗?

编辑:感谢 cmets。这是一个展示该行为的最小示例:

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("&lt;Button-1&gt;", (lambda e, s=self: s.newLabel())) 切换到button = Button(frame, text="askstring", command=(lambda s=self: s.newLabel())) 可以修复该错误(但没有给我一个被按下按钮的引用)。我认为这个问题与将事件捕获为 lambda 的输入之一有关。

【问题讨论】:

  • 从包含的来源很难判断。尝试用(新的)常规方法替换 lambda 快递,看看情况是否有变化。
  • 解决这个问题的最好方法是首先构建一个简化的例子,它给出了你现在遇到的同样的问题。到那时你可能会发现你遇到的错误,否则用它更新你的问题。

标签: python lambda tkinter


【解决方案1】:

您在此处遇到的问题是由于在您正在使用的对话框中调用了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 是一件坏事的典型示例。它与正在处理的&lt;Button-1&gt; 事件发生冲突并挂起。问题是您将不得不忍受使用wait_window,因为它属于对话框的代码。显然,如果你绑定到&lt;ButtonRelease-1&gt;,那么这个冲突永远不会发生。您也可以在按钮中使用command 参数,也可以正常工作。

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

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

【讨论】:

  • 谢谢,我认为 Tkinter 机器中发生了一些我不理解的事情。我也比数组更喜欢这个两步过程。我假设btn['command'] = myCommand 等价于btn.config(command=myCommand),对吧?
【解决方案2】:

我想出了一个解决方法。从最小示例测试来看,问题似乎来自对 bind 的单独调用,从而接受事件作为 lambda 的输入。如果有人能解释为什么会发生这种情况,我会接受他们的回答而不是我的回答,但我现在会接受这个。

解决方法不是使用单独的绑定函数,而是创建一个按钮数组,然后将数组中的正确条目作为参数传递给 lambda 函数(您不能传递按钮本身,因为它正在创建在具有 lambda 函数的行中)。

代码如下:

from Tkinter import *
import tkSimpleDialog

class Min():

    def __init__(self, master=None):
        root = master
        frame = Frame(root)
        frame.pack()

        buttons = [None] * 2
        for i in range (2):
            buttons[i] = Button(frame, text="askstring",
                            command=(lambda s=self, var=i: s.newLabel(buttons[var])))
            buttons[i].grid()

    def newLabel(self, button):
        label = tkSimpleDialog.askstring("New Label", "What should the label be?")
        button.config(text=label)
        print label

root = Tk()
m = Min(root)
root.mainloop()

【讨论】:

    猜你喜欢
    • 2020-11-02
    • 1970-01-01
    • 2018-07-29
    • 1970-01-01
    • 2011-11-05
    • 2013-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多