【问题标题】:How can I get TK buttons, generated by a for loop, to pass input to their command? (Python)如何让 for 循环生成的 TK 按钮将输入传递给他们的命令? (Python)
【发布时间】:2012-06-14 18:50:06
【问题描述】:

我有一个动态生成 GUI 的程序。我不知道我会有多少个按钮,如果有的话。

具体问题是这样的:

for varname in self.filetextboxes:
    if self.varDict[varname]=='':
        self.varDict[varname] = (StringVar(),)
        self.varDict[varname][0].set('')

    fileButton = Button(self, text=" ", command = lambda:self.varDict[varname][0].set(tkFileDialog.askopenfilename()), image=self.filephoto)

    ftb = Entry(self, textvariable = self.varDict[varname][0],width=40,background='white')

我有一个创建文本框和按钮的 for 循环。 StringVar() 存储在带有键 varname 的字典中。

因为我无法在按钮的回调函数中传递参数,所以我改为在每个按钮中定义一个 lambda。这会将与在此循环中创建的文本框关联的 StringVar() 设置为文件对话框的输出。

问题是,传递给 lambda 的 varname 没有传递值,而只是传递了变量的名称。因此,虽然文本框与在 for 循环中创建它们的变量相关联,但按钮中的 lambdas 始终使用 varname 的当前值。

也就是说,每个文本框只链接一个变量,但所有按钮只设置最终创建的文本框的文本,即最终值为varname的文本框。

还有其他方法可以解决这个问题吗?我可以让 lambda 以某种方式只使用定义的 varname 的值,而不使用 varname 的未来值吗?

【问题讨论】:

    标签: python user-interface tkinter tk openfiledialog


    【解决方案1】:

    通常,您会通过字典将 按钮 与 varname 和 StringVar() 相关联,键是按钮的 id 或编号,指向任何内容。由于您使用的是 StringVar,我假设您正在使用 Tkinter 执行此操作。下面是一个简单的示例,它将按钮的编号传递给访问字典的函数。

    from Tkinter import *
    from functools import partial
    
    class ButtonsTest:
       def __init__(self):
          self.top = Tk()
          self.top.title('Buttons Test')
          self.top_frame = Frame(self.top, width =400, height=400)
          self.button_dic = {}
          self.buttons()
          self.top_frame.grid(row=0, column=1)
    
          Button(self.top_frame, text='Exit', 
                  command=self.top.quit).grid(row=10,column=1, columnspan=5)
    
          self.top.mainloop()
    
       ##-------------------------------------------------------------------         
       def buttons(self):
          b_row=1
          b_col=0
          for but_num in range(1, 11):
             ## note that the correct "but_num" is stored
             self.button_dic[but_num] = "self.cb_button_%d()" % (but_num)
             b = Button(self.top_frame, text = str(but_num), 
                        command=partial(self.cb_handler, but_num))
             b.grid(row=b_row, column=b_col)
    
             b_col += 1
             if b_col > 4:
                b_col = 0
                b_row += 1
    
       ##----------------------------------------------------------------
       def cb_button_1(self):
          print "push button 1 and this code is executed"
    
       ##----------------------------------------------------------------
       def cb_button_2(self):
          print "push button 2 and this code is executed"
    
       ##----------------------------------------------------------------
       def cb_button_3(self):
          print "push button 3 and this code is executed"
    
       ##----------------------------------------------------------------
       def cb_handler( self, cb_number ):
          print "cb_handler", cb_number, self.button_dic[cb_number]                
          if cb_number < 4:
             exec(self.button_dic[cb_number])
    
    ##===================================================================
    BT=ButtonsTest() 
    

    【讨论】:

    • 我不喜欢在这里使用exec -- 你可以不用它。只需将要执行的函数传递给 cb_handler:partial(self.cb_handler, getattr(self,'cb_button_%d'%i,lambda:None)) 似乎应该可以工作......但为什么 lambda 不能?
    • 我不可能为每个按钮硬编码功能,因为我不知道我将拥有多少个按钮,或者我是否会有任何按钮。
    【解决方案2】:

    我不得不承认——这个让我有点难过。由于您将 Button 与 Entry 和 stringvar 配对,因此一种解决方法是将它们包装在一个类中。 (我可能会争辩说这更优雅一点......)

    import Tkinter as tk
    
    class ButtonEntry(tk.Frame):
        def __init__(self,master,ss):
            tk.Frame.__init__(self)
            self.var=tk.StringVar()
            self.var.set(ss)
            self.Button=tk.Button(self,text='Button',command=lambda :self.var.set("foo!"))
            self.Entry=tk.Entry(self,textvariable=self.var)
            self.Button.grid(row=0,column=0)
            self.Entry.grid(row=0,column=1)
    
    
    class App(tk.Frame):
        def __init__(self,master=None):
            tk.Frame.__init__(self,master)
            self.BEs=[]
            for i in range(10):
                b=ButtonEntry(self,'Button %d'%i)
                b.grid(row=i,column=0)
                self.BEs.append(b)
    
    
    if __name__ == '__main__':
        root=tk.Tk()
        f=App(root)
        f.grid(row=0,column=0)
        root.mainloop()
    

    但是,我真的很想知道 lambda 的行为方式为何如此。我确信你所拥有的应该可以工作。

    编辑

    我已经追踪了 lambda 函数的行为。 https://stackoverflow.com/a/10452819/748858 给出了一个很好的例子来说明如何正确地做到这一点。问题是 lambda 函数中的变量仍然绑定到声明 lambda 的范围。为了解除它们与该范围的关联,您需要将它们设置为函数的关键字参数。好的!今天早上我学到了一些新东西。

    【讨论】:

    • 谢谢,创建一个单独的类是一个很好的解决方案。虽然现在当我尝试在我的新类上调用 grid() 时,Filetextboxes 总是放在框架中所有其他小部件的下方。
    • @user1442389 -- 今天早上我终于发现了 lambda 的问题。我已经发布了链接和解释。真的很简单。
    • 我非常喜欢这个解决方案。也许我会切换回使用 lambda,尽管我已经更改了所有代码。
    • @user1442389 -- 对不起,我昨晚不知道 lambdas。很高兴我能有用。
    猜你喜欢
    • 2015-11-15
    • 2023-02-21
    • 2019-09-07
    • 2014-11-29
    • 2021-01-19
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 2022-10-15
    相关资源
    最近更新 更多