基于列表生成Tkinter Button-Entry对实现单片机通信GUI并解决Lambda提前执行问题的技术问询
当然可以实现你的需求!问题出在按钮的command参数赋值方式上——你现在是直接调用了函数,而不是把函数对象传给command,所以程序启动时就执行了lambda,而不是点击按钮时才触发。
问题根源
在你的ButtonEntryFrame类中,这一行代码是关键:
self.button = tk.Button(self.frame, text=self.txt, command=self.fn(self.txtVar))
当Python执行到这里时,会立刻计算self.fn(self.txtVar)的值(也就是执行那个lambda函数并打印内容),然后把返回值(这里是None,因为print()返回None)赋值给command。所以按钮点击时实际上没有绑定任何有效命令。
解决方案:延迟函数执行
我们需要把函数调用包装成一个可调用对象,让它只有在按钮点击时才执行。有两种常用方法:
方法1:用Lambda包装调用
修改按钮的command参数,用lambda来延迟执行self.fn:
self.button = tk.Button(self.frame, text=self.txt, command=lambda: self.fn(self.txtVar))
这里lambda本身是一个可调用对象,只有点击按钮时才会执行它内部的self.fn(self.txtVar)。
方法2:用functools.partial(更清晰,避免lambda陷阱)
如果你担心lambda的变量捕获问题(比如循环中复用变量的场景),可以用functools.partial来绑定参数:
首先导入partial:
from functools import partial
然后修改按钮的command:
self.button = tk.Button(self.frame, text=self.txt, command=partial(self.fn, self.txtVar))
partial会创建一个新的可调用对象,把self.txtVar作为参数绑定到self.fn上,点击按钮时就会执行这个绑定后的函数。
修改后的完整代码
这里用方法1的lambda方案,同时修正了获取Entry输入值的细节(需要调用StringVar的get()方法),调整后的代码如下:
import tkinter as tk class ButtonEntryFrame(): def __init__(self, parent, params, txtVar) -> None: self.txt = params[0] self.fn = params[1] self.txtVar = txtVar self.frame = tk.Frame(parent) # 用lambda包装,延迟执行函数 self.button = tk.Button(self.frame, text=self.txt, command=lambda: self.fn(self.txtVar)) self.entry = tk.Entry(self.frame, textvariable=self.txtVar) self.button.pack(side=tk.LEFT) self.entry.pack() self.frame.grid() root = tk.Tk() frameInput = tk.Frame(root) frameInput.pack(side=tk.LEFT) inputFunctions = [ ('Func A', lambda x: print(f"startMotor {x.get()} 0 1")), ('Func B', lambda x: print(f"stopMotor")), ('Func C', lambda x: print(f"increaseSpeed 100 {x.get()} 3")) ] frames = [] for i, params in enumerate(inputFunctions): txtVar = tk.StringVar() f = ButtonEntryFrame(frameInput, params, txtVar) frames.append(f) root.mainloop()
这样修改后,程序启动时不会提前打印内容,只有点击对应按钮时,才会读取Entry的输入值并按格式生成命令打印。
内容的提问来源于stack exchange,提问作者RedSmolf

