You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

循环创建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()

关键改进点

  1. 绑定数据到按钮:使用lambda k=level_key: self.open_edit_dialog(k)将每个按钮对应的JSON键传递到回调函数,解决循环中变量绑定的问题
  2. 存储按钮引用:用字典self.level_buttons保存所有按钮对象,修改数据后可以直接更新按钮文本
  3. 完整数据保存:加载整个JSON文件而非仅stone_levels,确保所有数据都能正确保存
  4. 弹窗交互:用Toplevel创建修改/添加弹窗,输入验证后更新数据并保存到文件
  5. 界面刷新:添加新等级后自动刷新界面,展示新的按钮

内容的提问来源于stack exchange,提问作者Greg Humphries

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 15:20:25