Search code examples
pythondictionaryvariablestkinter

Python3 - Tkinter - Widget creation with a dict


Im trying to create widgets dynamically onto the root window of tkinter with a json file as a widget config. I simplified the code so you can also test things out. (Im using grid() in my main code but its not necessary here)

The json file contains an items list with each widget in a seperate dict, it can look for example like this.

new_dict = {
    "items": [
        {
            "type": "frame",
            "id": "f1"
        },
        {
            "type": "button",
            "id": "b1",
            "text": "Button1"
        }
    ]
}

My goal here to create a widget, stored in the value of the id field, so i can later on change widgets states or other things via .config()

( Example: f1 = Frame(root) )

For this example my code looks like this:

( Note: im using locals() to create the specific variables, if there is a better way, please let me know )

# Example: Change Frame Color to Blue
def change_background(frame_id):
    locals()[frame_id].config(bg=blue)

# Root Window
root = Tk()
root.geometry("800x600")

# Widget Creation
for item in new_dict["items"]:
    if item["type"] == "frame":
        frame_id = item["id"]
        locals()[item["id"]] = Frame(root, width=200, height=200, bg="green")
        locals()[item["id"]].pack(side=BOTTOM, fill=BOTH)
    elif item["type"] == "button":
        locals()[item["id"]] = Button(root, text=item["text"], command=lambda: change_background(frame_id))
        locals()[item["id"]].place(x=50, y=50, anchor=CENTER)

root.mainloop()

Now my problem here is that i cant give the frame id into the change_background function. If i do that im getting following Error:

KeyError: 'f1'

I dont quite understand the problem here, because pack(), place() and grid() works fine with each widget.


Solution

  • As what the name locals means, it stores only local variables. So locals() inside the function just contains local variables defined inside the function.

    It is not recommended to use locals() like this. Just use a normal dictionary instead:

    ...
    # Example: Change Frame Color to Blue
    def change_background(frame_id):
        widgets[frame_id].config(bg="blue")
    
    # Root Window
    root = Tk()
    root.geometry("800x600")
    
    # Widget Creation
    # use a normal dictionary instead of locals()
    widgets = {}
    for item in new_dict["items"]:
        if item["type"] == "frame":
            frame_id = item["id"]
            widgets[item["id"]] = Frame(root, width=200, height=200, bg="green")
            widgets[item["id"]].pack(side=BOTTOM, fill=BOTH)
        elif item["type"] == "button":
            widgets[item["id"]] = Button(root, text=item["text"], command=lambda: change_background(frame_id))
            widgets[item["id"]].place(x=50, y=50, anchor=CENTER)
    ...