小组件绑定中的 Lambda 函数使用最后一次迭代的值

问题描述 投票:0回答:1

我有一个循环来动态生成小部件,并且按钮的命令是使用 lambda 设置的,但是

Listbox
es 的事件绑定似乎效果不佳:

def fn1():

    def cbf(e, param1, param2):
        val = param2.get(param2.curselection())
        param1.delete(0, tk.END)
        param1.insert(0, val)
        
    
    def fn2():

        for x in range(n):

            entry = Entry(root, textvariable=sometextvar, bg="somecolour")
            
            lb = Listbox(root, height=someheight)
            lb.insert(0, *["Some", "values"])
            entry.bind("<FocusIn>", lambda e, entry=entry: lb.grid(row=entry.grid_info()["row"] - 1, column=2, pady=0, sticky=""))
            entry.bind("<FocusOut>", lambda e: lb.grid_remove())
            lb.bind("<<ListboxSelect>>", lambda e, param1=entry, param2=lb: cbf(e, param1=param1, param2=param2))

fn1()

我不知道如何,但是

lb.grid_remove()
<FocusOut>
grid(...)
<FocusIn>
可以正常工作。
<<ListboxSelect>>
无法正常工作。

它仅适用于最后一个小部件,因为不知何故,尽管我在定义 lambda 时捕获了变量,但在循环后仅保留了最后一个小部件 (

lambda e, param1=entry, param2=lb:
)。它也仅适用于
<KeyRelease>
的最后一个小部件。

我也尝试过

functools.partial
,例如:

lb.bind("<<ListboxSelect>>", partial(cbf, None, param1=entry, param2=lb))

既作为关键字参数又作为位置参数。我对这个有

TypeError: mainfile.<locals>.cbf() got multiple values for argument 'param1' tkinter

我还尝试了大约 25 种其他函数定义的排列和组合、包装它们和嵌套回调等。我也不想使用类,我想要像我当前正在做的那样的东西。像

functools.partial
这样的库函数也不是首选。

为什么它适用于

command
的按钮和某些绑定,但不适用于其他?

python loops tkinter lambda
1个回答
0
投票

出现此问题的原因在于 Tkinter 处理事件绑定与命令回调的方式。使用

bind()
时,Tkinter 自动传递事件对象作为第一个参数,这与不接收自动参数的按钮
command
回调不同。

在您的原始代码中,存在参数计数不匹配 - 回调需要 3 个参数

(cbf(e, param1, param2))
,但绑定实际上尝试传递 4:

  • 事件对象(自动从 Tkinter)
  • e
    (来自 lambda 参数)
  • param1
    (关键字参数)
  • param2
    (关键字参数)

让我们试试这个

def fn1():
    def cbf(e, param1, param2):
        if not param2.curselection():  # Guard against empty selection
            return
        val = param2.get(param2.curselection())
        param1.delete(0, tk.END)
        param1.insert(0, val)
    
    def fn2():
        for x in range(n):
            entry = Entry(root, textvariable=sometextvar, bg="somecolour")
            lb = Listbox(root, height=someheight)
            lb.insert(0, *["Some", "values"])
            
            # FocusIn/Out bindings remain unchanged as they work correctly
            entry.bind("<FocusIn>", lambda e, entry=entry: lb.grid(row=entry.grid_info()["row"] - 1, column=2, pady=0, sticky=""))
            entry.bind("<FocusOut>", lambda e: lb.grid_remove())
            
            # Fixed ListboxSelect binding
            lb.bind("<<ListboxSelect>>", lambda e, e1=entry, lb1=lb: cbf(e, e1, lb1))

fn1()

按钮命令回调的工作方式不同,因为它们不会自动接收事件对象 - 它们是直接调用的,没有任何自动参数。

原始代码中只有最后一个小部件起作用的原因是Python在闭包中的后期绑定。通过显式捕获 lambda 默认参数中的值(e1=entry,lb1=lb),我们确保每个回调都有自己的引用副本。

让我知道进展如何

© www.soinside.com 2019 - 2024. All rights reserved.