循环创建Tkinter按钮后无法关联对应数据的技术求助
Tkinter项目:动态绑定JSON数据到按钮并实现修改保存功能
问题描述
我正在开发一个Tkinter项目,目标是生成窗口展示JSON文件中的所有数据,点击对应元素可修改数据并保存回JSON,同时支持添加新等级。
为了实现未来兼容,我用循环创建按钮,但后续无法引用按钮关联的数据。试过cget()、Tkinter验证机制、focus_get()都没得到正确结果;怀疑.grid()和按钮创建写一行导致引用错误,修改后问题仍在,用cget()获取信息返回“.”。
不局限于当前格式,可使用弹窗、文本框,但不想用列表框展示,避免破坏JSON结构。
简化后的JSON数据
{ "fabrication_cost": 17, "mark_up": 0.7, "stone_levels": { "granite1": { "Name": "Granite Level 1", "Colors": [ "Azul Platino", "Blanco Tulum", "Caledonia", "Luna Pearl" ], "Price": 7.5 }, "granite2": { "Name": "Granite Level 2", "Colors": [ "Fantasy Brown", "Gray Mist", "Santa Cecilia", "New Venetian Gold" ], "Price": 10 }, "granite3": { "Name": "Granite Level 3", "Colors": [ "Nero Mist Honed", "Salinas White", "Steel Grey Polished", "Steel Grey Brushed" ], "Price": 12 }, "quartz1": { "Name": "Quartz Level 1", "Colors": [ "Frost White", "Iced White", "Manhattan Gray", "Peppercorn White" ], "Price": 17 }, "quartz2": { "Name": "Quartz Level 2", "Colors": [ "Carrara Grigio", "Carrara Mist", "Fossil Gray", "Midnight Majesty" ], "Price": 22.5 }, "quartz3": { "Name": "Quartz Level 3", "Colors": [ "Babylon Gray", "Calacatta Classique", "Calacatta Vicenza", "Gray Lagoon" ], "Price": 25.5 } }, "edge_pricing": { "Roundover/Bevel": 8, "Half Bullnose": 10, "Full Bullnose": 15, "Ogee": 30, "Mitered": 50 }, "add_ons": { "trip_Charge": 250, "raw_Cutout": 50, "radii": 50, "arcs": 100, "splay": 100, "waterfall": 650, "farm_sink_cutout": 100, "undermount_Sink_Install": 100, "standard_18ga_Sink": 110, "upgrade_18ga_Sink": 240, "porcelain_Vanity_Sink": 45, "customer_Sink": 100 } }
原代码
import json import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() #load data from json file with open("pricing_data.json","r") as pricing_data_json: pricing_data = json.load(pricing_data_json) self.stone_levels = pricing_data['stone_levels'] #create main frame self.main_frame = tk.Frame() self.main_frame.grid(row=0,column=0) self.create_widgets() def create_widgets(self): r=0 for granite_level in self.stone_levels: lvl_name = self.stone_levels[granite_level]['Name'] lvl_colors = self.stone_levels[granite_level]['Colors'] tk.Button(master=self.main_frame, text=lvl_name).grid(row=r, column=0, sticky='news') tk.Button(master=self.main_frame, text=lvl_colors).grid(row=r+1, column=0, sticky='news') tk.Button(master=self.main_frame,text='Add New Level').grid(row=r+2, column=0) r+=3 app= App() app.mainloop()
解决方案
核心问题是循环创建按钮时没有绑定对应的数据,导致点击时无法关联到JSON中的条目。可以通过lambda绑定参数传递数据,同时把按钮对象存储起来方便后续操作,再实现修改弹窗和保存逻辑。
修改后的完整代码
import json import tkinter as tk from tkinter import ttk, messagebox class App(tk.Tk): def __init__(self): super().__init__() self.title("Stone Pricing Editor") self.geometry("600x800") # 加载完整JSON数据,方便后续保存所有内容 with open("pricing_data.json", "r") as f: self.pricing_data = json.load(f) self.stone_levels = self.pricing_data['stone_levels'] # 存储所有创建的按钮,方便后续引用 self.level_buttons = {} # 创建主框架 self.main_frame = tk.Frame(self, padx=10, pady=10) self.main_frame.pack(fill=tk.BOTH, expand=True) self.create_widgets() def save_to_json(self): """将修改后的数据保存回JSON文件""" try: with open("pricing_data.json", "w") as f: json.dump(self.pricing_data, f, indent=4) messagebox.showinfo("成功", "数据已保存") except Exception as e: messagebox.showerror("错误", f"保存失败:{str(e)}") def open_edit_dialog(self, level_key): """打开修改等级数据的弹窗""" level_data = self.stone_levels[level_key] # 创建弹窗 dialog = tk.Toplevel(self) dialog.title(f"修改 {level_data['Name']}") dialog.geometry("400x300") dialog.resizable(False, False) # 表单控件 ttk.Label(dialog, text="等级名称:").grid(row=0, column=0, padx=5, pady=5, sticky="w") name_entry = ttk.Entry(dialog, width=30) name_entry.grid(row=0, column=1, padx=5, pady=5) name_entry.insert(0, level_data['Name']) ttk.Label(dialog, text="价格:").grid(row=1, column=0, padx=5, pady=5, sticky="w") price_entry = ttk.Entry(dialog, width=30) price_entry.grid(row=1, column=1, padx=5, pady=5) price_entry.insert(0, str(level_data['Price'])) ttk.Label(dialog, text="颜色(逗号分隔):").grid(row=2, column=0, padx=5, pady=5, sticky="nw") colors_text = tk.Text(dialog, width=30, height=6) colors_text.grid(row=2, column=1, padx=5, pady=5) colors_text.insert("1.0", ", ".join(level_data['Colors'])) def save_changes(): """保存弹窗中的修改""" try: # 获取输入值 new_name = name_entry.get().strip() new_price = float(price_entry.get().strip()) new_colors = [c.strip() for c in colors_text.get("1.0", tk.END).strip().split(",") if c.strip()] # 更新数据 self.stone_levels[level_key]['Name'] = new_name self.stone_levels[level_key]['Price'] = new_price self.stone_levels[level_key]['Colors'] = new_colors # 更新按钮文本 self.level_buttons[level_key]['name_btn'].config(text=new_name) self.level_buttons[level_key]['colors_btn'].config(text=", ".join(new_colors)) # 保存到文件 self.save_to_json() dialog.destroy() except ValueError: messagebox.showerror("错误", "价格必须是数字") except Exception as e: messagebox.showerror("错误", f"修改失败:{str(e)}") # 弹窗按钮 ttk.Button(dialog, text="保存修改", command=save_changes).grid(row=3, column=0, columnspan=2, pady=10) ttk.Button(dialog, text="取消", command=dialog.destroy).grid(row=4, column=0, columnspan=2) def open_add_level_dialog(self): """打开添加新等级的弹窗""" dialog = tk.Toplevel(self) dialog.title("添加新等级") dialog.geometry("400x300") dialog.resizable(False, False) # 表单控件 ttk.Label(dialog, text="等级Key(如granite4):").grid(row=0, column=0, padx=5, pady=5, sticky="w") key_entry = ttk.Entry(dialog, width=30) key_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Label(dialog, text="等级名称:").grid(row=1, column=0, padx=5, pady=5, sticky="w") name_entry = ttk.Entry(dialog, width=30) name_entry.grid(row=1, column=1, padx=5, pady=5) ttk.Label(dialog, text="价格:").grid(row=2, column=0, padx=5, pady=5, sticky="w") price_entry = ttk.Entry(dialog, width=30) price_entry.grid(row=2, column=1, padx=5, pady=5) ttk.Label(dialog, text="颜色(逗号分隔):").grid(row=3, column=0, padx=5, pady=5, sticky="nw") colors_text = tk.Text(dialog, width=30, height=4) colors_text.grid(row=3, column=1, padx=5, pady=5) def add_new_level(): try: level_key = key_entry.get().strip() if not level_key: messagebox.showerror("错误", "等级Key不能为空") return if level_key in self.stone_levels: messagebox.showerror("错误", "该等级Key已存在") return new_name = name_entry.get().strip() new_price = float(price_entry.get().strip()) new_colors = [c.strip() for c in colors_text.get("1.0", tk.END).strip().split(",") if c.strip()] # 添加新等级数据 self.stone_levels[level_key] = { "Name": new_name, "Price": new_price, "Colors": new_colors } # 保存到文件 self.save_to_json() dialog.destroy() # 刷新界面(重新创建控件) for widget in self.main_frame.winfo_children(): widget.destroy() self.create_widgets() except ValueError: messagebox.showerror("错误", "价格必须是数字") except Exception as e: messagebox.showerror("错误", f"添加失败:{str(e)}") # 弹窗按钮 ttk.Button(dialog, text="添加", command=add_new_level).grid(row=4, column=0, columnspan=2, pady=10) ttk.Button(dialog, text="取消", command=dialog.destroy).grid(row=5, column=0, columnspan=2) def create_widgets(self): r = 0 # 先添加统一的"添加新等级"按钮 add_btn = ttk.Button(self.main_frame, text="添加新等级", command=self.open_add_level_dialog) add_btn.grid(row=r, column=0, columnspan=2, pady=(0, 10), sticky="ew") r +=1 # 循环创建每个等级的按钮 for level_key in self.stone_levels: level_data = self.stone_levels[level_key] # 创建名称按钮,点击打开修改弹窗 name_btn = ttk.Button( self.main_frame, text=level_data['Name'], command=lambda k=level_key: self.open_edit_dialog(k) ) name_btn.grid(row=r, column=0, padx=5, pady=2, sticky="ew") # 创建颜色按钮,同样绑定修改弹窗 colors_btn = ttk.Button( self.main_frame, text=", ".join(level_data['Colors']), command=lambda k=level_key: self.open_edit_dialog(k) ) colors_btn.grid(row=r, column=1, padx=5, pady=2, sticky="ew") # 存储按钮引用 self.level_buttons[level_key] = { "name_btn": name_btn, "colors_btn": colors_btn } r +=1 # 配置列权重,让按钮自适应窗口宽度 self.main_frame.grid_columnconfigure(0, weight=1) self.main_frame.grid_columnconfigure(1, weight=1) if __name__ == "__main__": app = App() app.mainloop()
关键改进点
- 绑定数据到按钮:使用
lambda k=level_key: self.open_edit_dialog(k)将每个按钮对应的JSON键传递到回调函数,解决循环中变量绑定的问题 - 存储按钮引用:用字典
self.level_buttons保存所有按钮对象,修改数据后可以直接更新按钮文本 - 完整数据保存:加载整个JSON文件而非仅
stone_levels,确保所有数据都能正确保存 - 弹窗交互:用
Toplevel创建修改/添加弹窗,输入验证后更新数据并保存到文件 - 界面刷新:添加新等级后自动刷新界面,展示新的按钮
内容的提问来源于stack exchange,提问作者Greg Humphries
相关产品推荐
相关产品推荐

