修改第一个OptionMenu时第二个OptionMenu异常变更的问题排查
问题原因分析
你碰到的这个问题确实是lambda闭包的经典“坑”——延迟绑定特性在搞鬼!
在update_file_list_selection的循环里,你创建的lambda函数并没有在定义时就绑定当前循环的file_selection实例,而是要等到用户点击菜单选项时,才会去查找file_selection变量的当前值。这时候循环已经跑完了,file_selection指向的是最后一个实例(也就是self.view.file_two),所以第一个OptionMenu的所有选项实际上都是在修改第二个的变量,自然就出现了你看到的奇怪现象。
解决方案
解决这个问题的核心是让lambda在定义时就绑定当前循环的file_selection实例,这里有两种常用且清晰的方法:
方法1:利用lambda默认参数绑定
给lambda增加一个默认参数,把当前的file_selection实例传进去——默认参数会在lambda定义时就完成绑定,避免延迟绑定的问题:
self.menu.add_command(label=x, command=lambda value=x, fs=file_selection: fs.variable_file.set(value))
方法2:使用functools.partial包装函数
如果觉得lambda的写法不够直观,可以用functools.partial来明确绑定参数,逻辑更清晰:
首先导入functools模块:
import functools
然后修改添加菜单命令的代码:
self.menu.add_command(label=x, command=functools.partial(file_selection.variable_file.set, x))
修改后的完整代码
这里给出用第一种方法优化后的完整代码,同时还修复了option_list重复添加内容的小问题:
import tkinter as tk from tkinter import ttk class File_Selection(): def __init__(self, frame, text): self.frame = frame self.text = text self.label_file = tk.Label(self.frame, text=text) self.label_file.pack() self.variable_file = tk.StringVar(self.frame) self.option_list = ["no file loaded"] self.variable_file.set(self.option_list[0]) self.optionmenu_file = tk.OptionMenu(self.frame, self.variable_file, *self.option_list) self.optionmenu_file.pack() class View: def __init__(self, view, update_list): self.view = view self.view.title("Test") self.view.geometry("320x240") self.view.resizable(False, False) self.frame = tk.Frame(self.view) self.frame.pack() self.button = tk.Button(self.frame, text="Update", command=update_list) self.button.pack() self.file_one = File_Selection(self.frame, "File 1") self.file_two = File_Selection(self.frame, "File 2") class Controller: def __init__(self): self.root = tk.Tk() self.view = View(self.root, lambda: self.update_file_list_selection()) self.files = ["File 1", "File 2", "File 3", "File 4"] def run(self): self.root.mainloop() def update_file_list_selection(self): self.active_file_selection = [self.view.file_one, self.view.file_two] for file_selection in self.active_file_selection: self.menu = file_selection.optionmenu_file["menu"] self.menu.delete(0, "end") # 清空原有列表,避免重复添加文件条目 file_selection.option_list.clear() file_selection.option_list.extend(self.files) for x in self.files: # 用默认参数绑定当前的file_selection实例 self.menu.add_command(label=x, command=lambda value=x, fs=file_selection: fs.variable_file.set(value)) file_selection.variable_file.set(self.files[0]) if __name__ == "__main__": c = Controller() c.run()
内容的提问来源于stack exchange,提问作者Bakira
相关产品推荐
相关产品推荐

