Retrieving data from nested fields in Tkinter UI

29 Views Asked by At

I'm pretty amateur in Tkinter, but I've been coding in Python for a while. [Here is the screenshot of our UI]

We can display the data on the right field by clicking on treeview id, we are able to add regular fields (+ button), nested fields ({+} button) and delete fields (-).

When we modify the data in the Entry boxes or we add new fields and click on Update, we are able to retrieve only the data from regular fields and we miss all the values of sub fields of the nested fields. After Update the saved nested fields look like this: nested_key:{}

Here is how we display pre existing regular and nested fields:


def create_regular_field_ui(key, value, category_name, item_id):
    # Create a frame for each field
    field_frame = ttk.Frame(fields_container)
    field_frame.pack(fill='x', expand=True)

    
    # Entry box for the key
    key_entry = ttk.Entry(field_frame, width=30)
    key_entry.insert(0, key)  # Insert the key into the entry
    key_entry.pack(side='left', padx=10)

    # Determine if Entry or Text widget should be used for the value
    if '\n' in value:
        value_entry = tk.Text(field_frame, height=8, width=60)  # Using Text widget for multiline
    else:
        value_entry = ttk.Entry(field_frame, width=60)
    
    value_entry.insert('1.0' if '\n' in value else '0', value)
    value_entry.pack(side='left')

    # Update add_button command
    add_button = ttk.Button(field_frame, text='+', width=2, 
                        command=lambda: add_field(field_frame, fields_container, category_name, item_id))
    add_button.pack(side='left')

    # Add 'nst' button for creating nested fields
    nst_button = ttk.Button(field_frame, text='{+}', width=3, 
                            command=lambda: create_nested_field(field_frame, fields_container, category_name, item_id))
    nst_button.pack(side='left')

    # Delete button for a regular field
    delete_button = ttk.Button(field_frame, text='-',  width=2, command=lambda: delete_field(field_frame, key, category_name, item_id))
    delete_button.pack(side='left')

    


    

def create_nested_field_ui(nested_key, nested_value, category_name, item_id):
    # Create a frame for the nested field
    nested_frame = ttk.Frame(fields_container)
    nested_frame.pack(fill='x', expand=True, padx=10)

    # Create and pack the nested field's key entry 
    nested_key_entry = ttk.Entry(nested_frame, width=30)
    nested_key_entry.insert(0, nested_key)
    nested_key_entry.pack(side='left', padx=5)
    print(f"Created Nested Key Entry: {nested_key_entry}, in Frame: {nested_frame}")

    # Iterate over nested fields
    for sub_key, sub_value in nested_value.items():
        # Call a function to create each subfield
        create_sub_field(nested_frame, sub_key, sub_value, category_name, item_id, nested_key)
        

def create_sub_field(nested_frame, sub_key, sub_value, category_name, item_id, parent_key):
    sub_field_frame = ttk.Frame(nested_frame)
    sub_field_frame.pack(fill='x', expand=True, padx=40)

    # Sub field key entry
    sub_key_entry = ttk.Entry(sub_field_frame, width=30)
    sub_key_entry.insert(0, sub_key)
    sub_key_entry.pack(side='left', padx=5)

    # Decide whether to use Entry or Text widget for the sub value
    if sub_value is not None and '\n' in sub_value:
        sub_entry = tk.Text(sub_field_frame, height=8, width=60)
    else:
        sub_entry = ttk.Entry(sub_field_frame, width=60)
    
    if sub_value is not None:
        sub_entry.insert('1.0' if isinstance(sub_entry, tk.Text) else '0', sub_value)
    sub_entry.pack(side='left', padx=5, expand=True)

    # Operation buttons
    add_button = ttk.Button(sub_field_frame, text='+', width=2, 
                    command=lambda: add_field(sub_field_frame, nested_frame, category_name, item_id, True, parent_key))
    add_button.pack(side='left', padx=2)

    delete_button = ttk.Button(sub_field_frame, text='-', width=2, 
                        command=lambda: delete_field(sub_field_frame, sub_key, category_name, item_id, True, parent_key))
    delete_button.pack(side='left', padx=2)
    print(f"Created Subfield Frame: {sub_field_frame}, for Key: {sub_key}, Value: {sub_value}")

Here is how we create new regular and nested fields:


def add_field(current_frame, container, category_name, item_id, is_subfield=False, parent_key=None, is_nested_within_nested=False):
    # Determine the appropriate container for the new field
    new_field_container = current_frame.master if is_subfield else container

    # Create a frame for the new field
    new_field_frame = ttk.Frame(new_field_container)
    new_field_frame.pack(after=current_frame)

     

    # Handle nested field within nested field
    if is_nested_within_nested:
        create_nested_field(new_field_frame, new_field_container, category_name, item_id, True, parent_key)
    
    else:
               
        # Create entry boxes for key and value
        key_entry = ttk.Entry(new_field_frame, width=30)
        key_entry.pack(side='left', padx=2)

        value_entry = ttk.Entry(new_field_frame, width=60)
        value_entry.pack(side='left', padx=2)

        # In the add_field function
        toggle_button = ttk.Button(new_field_frame, text='L', width=3, 
                                command=lambda: toggle_multiline(value_entry, new_field_frame, toggle_button))
        toggle_button.pack(side='left')

        # Add '+' and '-' buttons for the new field
        add_button_command = lambda: add_field(new_field_frame, container, category_name, item_id, is_subfield, parent_key if is_subfield else key_entry.get())
        add_button = ttk.Button(new_field_frame, text='+', width=2, command=add_button_command)
        add_button.pack(side='left')

        add_nested_command = lambda: create_nested_field(new_field_frame, new_field_container, category_name, item_id, parent_key)
        add_nested = ttk.Button(new_field_frame, text='{+}', width=3, command=add_nested_command)
        add_nested.pack(side='left')

        delete_button_command = lambda: delete_field(new_field_frame, key_entry.get(), category_name, item_id, is_subfield, parent_key if is_subfield else key_entry.get())
        delete_button = ttk.Button(new_field_frame, text='-', width=2, command=delete_button_command)
        delete_button.pack(side='left')



def create_nested_field(current_frame, container, category_name, item_id, parent_key=None):
    # Create a frame for the nested field
    nested_field_frame = ttk.Frame(container)
    nested_field_frame.pack(after=current_frame)

    # Entry for the nested field key
    nested_key_entry = ttk.Entry(nested_field_frame, width=30)
    nested_key_entry.pack(side='left', padx=2)

        # '+' button for adding more nested or subfields to this nested field
    add_subfield_button = ttk.Button(nested_field_frame, text='+', width=2, 
                                     command=lambda: add_field(nested_key_entry, subfields_container, category_name, item_id, True, nested_key_entry.get()))
    add_subfield_button.pack(side='left', padx=2)

    

    # '-' button for deleting the nested field and all its subfields
    delete_button = ttk.Button(nested_field_frame, text='-', width=2, 
                               command=lambda: delete_nested_field(nested_field_frame, nested_key_entry.get(), category_name, item_id, parent_key))
    delete_button.pack(side='left', padx=2)
    


    # Frame to contain subfields of this nested field
    subfields_container = ttk.Frame(nested_field_frame)
    subfields_container.pack(fill='x', expand=True)
    print_widget_hierarchy(nested_field_frame)

And here is how we collect the data:


def extract_nested_data(nested_frame):
    nested_data = {}
    for sub_field_frame in nested_frame.winfo_children():
        if isinstance(sub_field_frame, ttk.Frame) and len(sub_field_frame.winfo_children()) >= 2:
            widgets = sub_field_frame.winfo_children()
            key_widget = widgets[0]
            value_widget = widgets[1]

            key = key_widget.get() if isinstance(key_widget, ttk.Entry) else None
            value = value_widget.get() if isinstance(value_widget, ttk.Entry) else value_widget.get("1.0", tk.END).strip()

            if key is not None:
                nested_data[key] = value

    return nested_data


def collect_and_update_data():
    selected_items = tree.selection()
    if not selected_items:
        messagebox.showinfo("Info", "No item selected.")
        return

    selected_item = selected_items[0]
    selected_id = tree.item(selected_item, 'text')
    parent_id = tree.parent(selected_item)

    if not parent_id:
        messagebox.showinfo("Info", "Please select an item ID, not a category.")
        return

    category = tree.item(parent_id, 'text')
    existing_data = json_data[category].get(selected_id, {}).copy()

    for field_frame in fields_container.winfo_children():
        key_widget = None
        value_widget = None
        nested_data = None

        for widget in field_frame.winfo_children():
            if isinstance(widget, ttk.Entry):
                if key_widget is None:  # First Entry widget is the key
                    key_widget = widget
                else:  # Second Entry widget might be the value
                    value_widget = widget
            elif isinstance(widget, tk.Text):
                value_widget = widget
            elif isinstance(widget, ttk.Frame) and len(widget.winfo_children()) >= 2:
                    nested_data = extract_nested_data(widget)

        if key_widget:
            key = key_widget.get()
            if nested_data is not None:
                existing_data[key] = nested_data
            elif value_widget:
                value = value_widget.get("1.0", tk.END).strip() if isinstance(value_widget, tk.Text) else value_widget.get()
                existing_data[key] = value

    json_data[category][selected_id] = existing_data
    with open("florence_data.json", "w") as file:
        json.dump(json_data, file, indent=4)

    refresh_tree_view(tree, json_data)
    update_json_view(json.dumps(json_data, indent=4))
    messagebox.showinfo("Info", "Data updated successfully.")

Please revise and let me know where I'm getting this wrong because

I've tried printing all widgets to understand if I'm getting the wrong ones, since there are some buttons as well in each field.

I'm still prone to think that the problem should be in way we extract data from the widget, however I've tried both getting the widgets by order and by type with no results.

0

There are 0 best solutions below