Python Forum
[Tkinter] Label & OptionMenu widget not registered as dict keys
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Label & OptionMenu widget not registered as dict keys
#1
Currently making a financial tracking system, and ran into an issue with accessing 'item_category_optnmenu' and 'delitem_category_lbl' keys in a dictionary.
The widgets work fine, but I'm unable to change any properties (e.g. setting text for the label does nothing directly in the dictionary and raises a KeyError when set later). Where am I going wrong here?

# Import modules
import tkinter as tk
from tkinter import ttk
from tkinter import font
from PIL import ImageTk,Image
    show_widgets(export_screen_widgets, widget_options)

# Create widgets
def make_widgets(widget_dict):
    for name, place_args in widget_dict.items():
        suffix = name.split('_')[-1]
        factory = _factories.get(suffix, lambda p, n: tk.Label(p, text=n))
        w = factory(root, name)

        if name not in widget_options:
            widget_options[name] = {}
        opts = widget_options[name]

        if suffix == "txt":
            w.config(
                width=opts.get("width", 20),
                height=opts.get("height", 3),
                bg=opts.get("bg", "#ffffff"),
            )

        if suffix == "btn":
            image_obj = opts.get("image_obj")
            if image_obj:
                w.config(image=image_obj)
                w.image_obj = image_obj  # prevent garbage collection

        if suffix == "canvas":
            bg_colour = opts.get("background") or opts.get("bg")
            if bg_colour:
                w.config(bg=bg_colour)
            image_obj = opts.get("image_obj")
            if image_obj:
                w.create_image(0, 0, image=image_obj, anchor='nw')
                w.image_obj = image_obj  # hold reference
                w.config(width=image_obj.width(), height=image_obj.height())
            else:
                pass
        else:
            skip_keys = {"widget", "image_obj", "variable"}
            w.config(**{k: v for k, v in opts.items() if k not in skip_keys})

        opts["widget"] = w
        w.place(**place_args)

# Create tkinter window
root = tk.Tk()
root.wm_attributes('-fullscreen','true')

# Get screen dimensions
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()

# Set 'background.png' image as window background (Ignore this, just for context)
bg_img_original = Image.open('path\\to\\background.png')  # get background image
bg_img_resized = bg_img_original.resize((screen_width,screen_height),Image.Resampling.LANCZOS)  # Resize background image to fit screen dimensions
bg_img = ImageTk.PhotoImage(bg_img_resized)
background = tk.Canvas(root,highlightthickness=0,bd=0)  # Create canvas widget to display background image
background.place(x=0,y=0,relwidth=1,relheight=1)
background.create_image(0,0,image=bg_img,anchor='nw')

# Widget names and placement args (financial record screen)
record_screen_widgets = {
    "item_details_bg_canvas": {"x": 2*(screen_width//3)-10, "y": int(screen_height-5), "anchor": "sw"},
    "item_record_bg_canvas": {"x": (screen_width//3), "y": int(screen_height-5), "anchor": "sw"},
    "currentyear_lbl": {"x": (screen_width//3)+25, "y": screen_height-(int(44*(screen_width//3) * Image.open('path\\to\\items_record_bg.png').size[1] / Image.open('path\\to\\items_record_bg.png').size[0])//50), "anchor": "sw"},
    "consumables_category_lbl": {"x": 0, "y": 0, "anchor": "nw"},
    "hires_category_lbl": {"x": 0, "y": 0, "anchor": "nw"},
    "misc_category_lbl": {"x": 0, "y": 0, "anchor": "nw"},
"item_category_optnmenu": {"x": int(screen_width-25), "y": 5+(screen_height//4), "anchor": "ne"},
    "item_description_txt": {"x": 4*(screen_width//5), "y": (screen_height//3), "anchor": "e"},
    "item_quantity_entry": {"x": 0, "y": 0, "anchor": "nw"},
    "item_indivcost_entry": {"x": 0, "y": 0, "anchor": "nw"},
    "item_totalcost_entry": {"x": 0, "y": 0, "anchor": "nw"},
    "item_daterecord_lbl": {"x": 0, "y": 0, "anchor": "nw"},
    "item_accrecord_lbl": {"x": 0, "y": 0, "anchor": "nw"},
    "item_comments_txt": {"x": 4*(screen_width//5), "y": 4*(screen_height//5), "anchor": "se"}
}

# Widget names and placement args (deleted items screen)
delitems_screen_widgets = {
    "delitem_details_bg_canvas": {"x": 2*(screen_width//3)-10, "y": int(screen_height-5), "anchor": "sw"},
    "delitem_record_bg_canvas": {"x": (screen_width//3), "y": int(screen_height-5), "anchor": "sw"},
    "delitem_category_lbl": {"x": int(screen_width-25), "y": 5+(screen_height//4), "anchor": "ne"},
    "delitem_description_txt": {"x": 4*(screen_width//5), "y": (screen_height//3), "anchor": "e"},
    "delitem_quantity_lbl": {"x": 0, "y": 0, "anchor": "nw"},
    "delitem_indivcost_lbl": {"x": 0, "y": 0, "anchor": "nw"},
    "delitem_totalcost_lbl": {"x": 0, "y": 0, "anchor": "nw"},
    "delitem_daterecord_lbl": {"x": 0, "y": 0, "anchor": "nw"},
    "delitem_accrecord_lbl": {"x": 0, "y": 0, "anchor": "nw"},
    "delitem_comments_txt": {"x": 4*(screen_width//5), "y": 4*(screen_height//5), "anchor": "se"}
}

# Dictionary containing widget names and properties
widget_options = {
    # Record Screen
    "currentyear_lbl": {
        "text": "2025",
        "bg": "#000a58",
        "fg": "#ffffff"
    },
    "category_view_btns": {
        "text": "View Category",
        "background": "#000e72",
        "activebackground": "#000e72"
    },
    "consumables_category_lbl": {
        "text": "Consumables",
        "bg": "#000e72"
    },
    "hires_category_lbl": {
        "text": "Hires",
        "bg": "#000e72"
    },
    "misc_category_lbl": {
        "text": "Miscellaneous",
        "bg": "#000e72"
    },
    "item_category_optnmenu": {
        "background": "#ffffff"
    },
    "item_description_txt": {
        "background": "#ffffff"
    },
    "item_quantity_entry": {
        "background": "#ffffff"
    },
    "item_indivcost_entry": {
        "background": "#ffffff"
    },
    "item_totalcost_entry": {
        "background": "#ffffff"
    },
    "item_daterecord_lbl": {
        "text": "Date",
        "bg": "#000e72"
    },
    "item_accrecord_lbl": {
        "text": "Account",
        "bg": "#000e72"
    },
    "item_comments_txt": {
        "background": "#ffffff"
    },
    # Deleted Items Screen
    "delitem_category_lbl": {
        "bg": "#000e72",
        "fg": "#ffffff",
        "text": "Category Name"
    },
    "delitem_description_txt": {
        "background": "#ffffff"
    },
    "delitem_quantity_lbl": {
        "text": "Quantity",
        "bg": "#000e72"
    },
    "delitem_indivcost_lbl": {
        "text": "Cost",
        "bg": "#000e72"
    },
    "delitem_totalcost_lbl": {
        "text": "Total",
        "bg": "#000e72"
    },
    "delitem_daterecord_lbl": {
        "text": "Date",
        "bg": "#000e72"
    },
    "delitem_accrecord_lbl": {
        "text": "Account",
        "bg": "#000e72"
    },
    "delitem_comments_txt": {
        "background": "#ffffff"
    },

# Set more properties to widgets (to show how I tried adding properties to item_category_optnmenu/delitem_category_lbl)
widget_options["item_description_txt"]["width"] = 40
widget_options["item_description_txt"]["height"] = 5
widget_options["item_comments_txt"]["width"] = 40
widget_options["item_comments_txt"]["height"] = 5
widget_options["delitem_description_txt"]["width"] = 40
widget_options["delitem_description_txt"]["height"] = 5
widget_options["delitem_comments_txt"]["width"] = 40
widget_options["delitem_comments_txt"]["height"] = 5

# Widget factories based on suffix
_factories = {
    'lbl':     lambda p, n: tk.Label(p),
    'btn':     lambda p, n: tk.Button(p, highlightthickness=0, bd=0),
    'canvas':  lambda p, n: tk.Canvas(p, highlightthickness=0, bd=0),
    'optnmenu': lambda p, n: tk.OptionMenu(p, tk.StringVar(value=""), "Option 1", "Option 2"),
    'entry':   lambda p, n: tk.Entry(p),
    'txt':     lambda p, n: tk.Text(p),
    'separator':lambda p, n: ttk.Separator(p, orient='vertical')
}

# Call functions to create and hide screen-specific widgets (ignore this, just for context)
make_widgets(profile_screen_widgets)
make_widgets(record_screen_widgets)
make_widgets(export_screen_widgets)
make_widgets(delitems_screen_widgets)
make_widgets(settings_screen_widgets)
hide_widgets(widget_options)

root.mainloop()
deanhystad write Jul-19-2025, 01:10 PM:
Please post all code, output and errors (it it's entirety) between their respective tags. Refer to BBCode help topic on how to post. Use the "Preview Post" button to make sure the code is presented as you expect before hitting the "Post Reply/Thread" button.
Reply
#2
I think you introduced some errors preparing your code for posting. For example, there are no profile_screen_widgets in your code, and there is a call to show_widgets right after your imports. Please check your posted code

Write a short example that focuses on the problem you are having. Try to avoid using an external references like image files to make it easier for others to run your code. I wrote this using your code as a base:
import tkinter as tk


def make_widgets(widgets):
    for name, pos in widgets.items():
        suffix = name.split('_')[-1]
        factory = _factories.get(suffix, lambda p, n: tk.Label(p, text=n))
        w = factory(root, name)
        w.place(**pos)
        widgets[name]["widget"] = w


def set_options(widgets, widget_options):
    for name, options in widget_options.items():
        if name not in widgets:
            continue
        w = widgets[name]["widget"]
        for key, value in options.items():
            if key == "image_obj":
                w.create_image(0, 0, image=value, anchor='nw')
                w.image_obj = value  # hold reference
                w.config(width=value.width(), height=value.height())
            else:
                w[key] = value


widgets = {
    "item_category_optnmenu": {"x": 100, "y": 50, "anchor": "ne"},
    "delitem_quantity_lbl": {"x": 100, "y": 100, "anchor": "ne"},
}

options = {
    "item_category_optnmenu": {
        "background": "#BBBBBB",
    },
    "delitem_quantity_lbl": {
        "text": "Quantity",
        "bg": "#ff0000",
        "fg": "#ffffff"
    },
}

_factories = {
    'lbl':     lambda p, n: tk.Label(p),
    'optnmenu': lambda p, n: tk.OptionMenu(p, tk.StringVar(value=""), "Option 1", "Option 2"),
}


root = tk.Tk()
make_widgets(widgets)
set_options(widgets, options)
root.mainloop()
A small example makes it easier to focus on issues like option menus don't keep a reference to the stringvar, or option menus have no way to specify the values. Changing the factory can fix these problems. This version eliminates a separate options dictionary.
import tkinter as tk


def make_widgets(parent, widgets):
    for name, args in widgets.items():
        wtype = name.split('_')[-1]
        if wtype == "optnmenu":
            values = args.pop("values")
            var = tk.StringVar(parent, values[0])
            widget = tk.OptionMenu(parent, var, *values)
            widget.var = var
        elif wtype == "button":
            widget = tk.Button(parent)
        else:
            widget = tk.Label(parent)  # Default to Label
        widgets[name] = widget  # Save ref to widget.  Don't need config anymore.

        place_keys = ("x", "y", "anchor")
        place_args = {k: v for k, v in args.items() if k in place_keys}
        widget.place(**place_args)

        options = {k: v for k, v in args.items() if k not in place_keys}
        if len(options) > 0:
            widget.config(**options)


def increment():
    widget = widgets["quantity_lbl"]
    parts = widget["text"].split()
    parts[-1] = str(int(parts[-1]) + 1)
    widget["text"] = " ".join(parts)


widgets = {
    "category_optnmenu": {
        "x": 100, "y": 10, "anchor": "ne", "background": "#BBBBBB", "values": ("Option 1", "Option 2")
    },
    "quantity_lbl": {
        "x": 100, "y": 50, "anchor": "ne", "text": "Quantity = 1", "bg": "#ff0000", "fg": "#ffffff"
    },
    "command_button": {
        "x": 100, "y": 90, "anchor": "ne", "text": "Press Me", "command": increment
    }
}


root = tk.Tk()
make_widgets(root, widgets)
root.mainloop()
Why are you writing code this way? I see no benefit to making dictionaries over calling tkinter directly. If you want to use dictionaries to make GUI code take a look at PySimpleGUI,
Reply
#3
Thanks for the reply; I just tried making my code display a bit cleaner and removing images in a duplicate .py file, and the issue fixed itself after some stuff was deleted. I isolated and found the issue (was how my _factories dict was handling optionmenus, which by changing also fixed the label widget for whatever reason) so this post can be closed (if possible, I'm new to python forums) 👍
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Tkinter] The Text in the Label widget Tkinter cuts off the Long text in the view malmustafa 4 14,083 Jun-26-2022, 06:26 PM
Last Post: menator01
  Tkinter reduce OptionMenu height euras 2 7,451 May-25-2021, 09:14 PM
Last Post: euras
  Tkinter - How can I extend a label widget? TurboC 2 4,315 Oct-13-2020, 12:15 PM
Last Post: zazas321
  [Tkinter] optionmenu value write a list mdalireza 0 3,262 Nov-11-2019, 01:00 PM
Last Post: mdalireza
  [Tkinter] Retrieve OptionMenu selection? JP_ROMANO 5 30,066 Mar-13-2019, 10:56 AM
Last Post: vsathya
  [Tkinter] [split] need help in optionMenu using tkinter Rakeshkrtiwari_07 0 3,117 Aug-02-2018, 04:16 PM
Last Post: Rakeshkrtiwari_07
  [Tkinter] Tkinter optionmenu child menu position showing 0,0 thatguy14 2 6,752 Jun-15-2018, 10:42 AM
Last Post: thatguy14
  [Tkinter] need help in optionMenu using tkinter Lizard 4 9,258 Nov-07-2017, 03:08 PM
Last Post: Lizard

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020