I'm pretty amateur in Tkinter, but I've been coding in Python for a while.
[]
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.