pythonpandastkinter

Unable to update values in CSV file imported into GUI using Tkinter


I'm not able to update any values in the imported CSV file into GUI. I'm able to open the CSV and add a new column, but when I try to select a value and update it to my desired value I'm not able to do that.

Also, I have made a button for updating the values, but when I select the existing value and click on the update row button nothing happens.

My code is below:

import pandas as pd
import tkinter as tk
from tkinter import filedialog
from tkinter import *
from tkinter import ttk

csv_data = pd.DataFrame()
csv_text = None
entry_values = {}

def create_gui():
    global csv_data, csv_text, entry_values

    def open_csv():
        global csv_data
        csv_path = tk.filedialog.askopenfilename(title='Select CSV File', filetypes=[('CSV Files', '*.csv')])
        if csv_path:
            csv_data = pd.read_csv(csv_path)
            update_csv_display()
            create_entry_widgets()

   def display_csv():
       if csv_data is not None:
           for col in range(len(csv_data.columns)):
               label = tk.Label(csv_frame, text=csv_data.columns[col])
               label.pack()

               for row in range(len(csv_data)):
                    entry = tk.Entry(csv_frame)
                    entry.pack()
                    entry.insert(tk.END, str(csv_data.iloc[row, col]))

   def save_csv():
       global csv_data
       if csv_data is not None:
           csv_path = tk.filedialog.asksaveasfilename(title='Save CSV File', defaultextension='.csv', filetypes=[('CSV Files', '*.csv')])
           csv_data.to_csv(csv_path, index=False)
           print("CSV file saved successfully.")

   def add_column():
        global csv_data
        if csv_data is not None:
            column_name = entry_column.get()
            csv_data[column_name] = ''
            update_csv_display()
            create_entry_widgets()

   def update_csv_display():
        csv_text.delete(1.0, tk.END)
        csv_text.insert(tk.END, csv_data.to_string())

   def create_entry_widgets():
        global entry_values
        for col in csv_data.columns:
            entry_values[col] = StringVar()
            entry = Entry(csv_frame, textvariable=entry_values[col])
            entry.pack(padx=5, pady=5)


   def update_selected_row(event=None):
        print("selected row")
        global csv_data
        selected_items = my_tree.selection()
        print(selected_items)
        if selected_items:
            selected_row = selected_items[0]
            print(selected_row)
            for col, value in entry_values.items():
                print(f"{col}: {value.get()}")
                entry_values[col].set(csv_data.at[selected_row, col])

    def update_csv_data():
        global csv_data
        selected_items = my_tree.selection()
        if selected_items:
            selected_row = selected_items[0]
            for col in csv_data.columns:
                new_value = entry_values[col].get()
                csv_data.at[selected_row, col] = new_value
            update_csv_display()

   global csv_text
   root = Tk()
   root.geometry("1000x800")

   # Create a frame for the CSV data
   csv_frame = Frame(root)
   csv_frame.pack(side=tk.LEFT, padx=10, pady=10)

   csv_text = Text(csv_frame, width=120, height=50)
   csv_text.pack()

    # Create a button to open the CSV file
   csv_button = Button(csv_frame, text='Open CSV', command=open_csv)
   csv_button.pack(pady=10)

    # Create a button to save the CSV file
   save_csv_button = Button(csv_frame, text='Save CSV', command=save_csv)
   save_csv_button.pack(pady=10)

   # Add a column entry field and button for manual column addition
   add_column_frame = Frame(csv_frame)
   add_column_frame.pack(pady=10)

   entry_column = Entry(add_column_frame, width=20)
   entry_column.pack(side=LEFT, padx=5)

   add_column_button = Button(add_column_frame, text='Add Column', command=add_column)
   add_column_button.pack(side=LEFT)

  # Create a Treeview for displaying CSV data
   my_tree = ttk.Treeview(root)
   my_tree.pack(pady=20)

   my_tree.bind("<<TreeviewSelect>>", update_selected_row)

   update_row_button = Button(root, text='Update Row', command=update_selected_row)
   update_row_button.pack(padx=5, pady=5)

   save_changes_button = Button(root, text='Save Changes', command=update_csv_data)
   save_changes_button.pack(padx=5, pady=5)


   root.mainloop()

create_gui()

Please let me know where I am wrong and what I am missing.


Solution

  • Your doesn't work as you describe.


    First: it doesn't create items in Treeview when it loads CSV so buttons Update Row and Save Changes can't work.

    I add row numbers in Treeview when I load file:
    (but first I remove old items - so when I load second file then it removes rows from first file):

    # remove old items in Treeview
    
    for item in my_tree.get_children():
        my_tree.delete(item)
    
    # add new items in Treeview            
    
    for index in range(len(csv_data)):
        my_tree.insert("", "end", text=index)
    

    Second: You have code to get selected items from Treeview but it gives you row ID in Treeview but you need row number in DataFrame.

    I use this:

    selected_row_id = selected_items[0]
    print('Treeview row id:', selected_row_id)
    
    selected_row = my_tree.item(selected_row_id)['text']
    print('DataFrame row number:', selected_row)
    

    Other problem: button Add column creates new Entry for all existing columns - so it adds many times Entry for the same columns. It should add only one Entry - only for new column - or it should remove all previous Entry before adding again Entry for all columns.

    I remove all Entry. To make it simpler I create new Frame (named rows_frame_ and put all Entry in this Frame. And later I remove all children.

    # remove all widgets from Frame
    
    for item in rows_frame.winfo_children():
        item.destroy()
    

    Because it is easy so I add also Label with column's names. I also add Label with column name.


    enter image description here

    My full working code:

    import tkinter as tk   # PEP8: `import *` is not preferred
    from tkinter import filedialog
    from tkinter import ttk
    import pandas as pd
    
    # --- constants ---  # PEP8: `UPPER_CASE_NAMES`
    
    DEBUG = True
    
    # --- classes ---
    
    # --- functions ---
    
    def create_gui():
        global csv_data 
        global csv_text
        global entry_values
        global entry_column
        global csv_frame
        global my_tree
    
        def open_csv():
            """Read CSV file."""
            
            global csv_data
            
            if DEBUG:
               print('[DEBUG] open_csv()')
                   
            csv_path = tk.filedialog.askopenfilename(title='Select CSV File', filetypes=[('CSV Files', '*.csv')])
            
            if csv_path:
                csv_data = pd.read_csv(csv_path)
                update_csv_display()
                create_entry_widgets()
    
                # remove old items in Treeview
                for item in my_tree.get_children():
                    my_tree.delete(item)
    
                # add new items in Treeview            
                for index in range(len(csv_data)):
                    my_tree.insert("", "end", text=index)
    
        def save_csv():
            """Write CSV file."""
    
            global csv_data
    
            if DEBUG:
               print('[DEBUG] save_csv()')
           
            if csv_data is not None:
                csv_path = tk.filedialog.asksaveasfilename(title='Save CSV File', defaultextension='.csv', filetypes=[('CSV Files', '*.csv')])
                csv_data.to_csv(csv_path, index=False)
                print("CSV file saved successfully.")
    
        def display_csv():
            """Display DataFrame in ???"""
    
            if DEBUG:
               print('[DEBUG] display_csv()')
    
            if csv_data is not None:
                for col in range(len(csv_data.columns)):
                   label = tk.Label(csv_frame, text=csv_data.columns[col])
                   label.pack()
    
                   for row in range(len(csv_data)):
                        entry = tk.Entry(csv_frame)
                        entry.pack()
                        entry.insert(tk.END, str(csv_data.iloc[row, col]))
    
        def add_column():
            """Add new column to DataFrame and (re)create all Entry."""
            
            global csv_data
    
            if DEBUG:
               print('[DEBUG] add_column()')
            
            if csv_data is not None:
                column_name = entry_column.get()
                column_name = column_name.strip()  # remove spaces at the ends.
                if column_name:  # check if name is not empty
                    csv_data[column_name] = ''
                    update_csv_display()
                    create_entry_widgets()
    
        def update_csv_display():
            """Display DataFrame information in Text."""
    
            if DEBUG:
               print('[DEBUG] update_csv_display()')
    
            csv_text.delete(1.0, tk.END)
            csv_text.insert(tk.END, csv_data.to_string())
    
        def create_entry_widgets():
            """(Re)Create Entry for all rows."""
            
            global entry_values
            global entry_widgets
    
            if DEBUG:
               print('[DEBUG] create_entry_widgets()')
            
            # remove all widgets from Frame
            for item in rows_frame.winfo_children():
                item.destroy()
                
            # create again all Entry widgets and all Treeview rows
            for index, col in enumerate(csv_data.columns):
                print('col:', col)
                entry_values[col] = tk.StringVar()
    
                entry = tk.Label(rows_frame, text=f"{col}:")
                entry.grid(row=index, column=0, sticky='e')
    
                entry = tk.Entry(rows_frame, textvariable=entry_values[col])
                entry.grid(row=index, column=1)
    
        def get_selected_row_from_csv(event=None):
            """Get data from `csv_data` put in all `Entry` for row selected in TreeView."""
        
            global csv_data
    
            if DEBUG:
               print('[DEBUG] get_selected_row_from_csv()')
            
            selected_items = my_tree.selection()
            print('selected_items:', selected_items)
            
            if selected_items:
                selected_row_id = selected_items[0]
                print('Treeview row id:', selected_row_id)
                
                selected_row = my_tree.item(selected_row_id)['text']
                print('DataFrame row number:', selected_row)
                
                for col, value in entry_values.items():
                    print(f"{col}: {value.get()}")
                    entry_values[col].set(csv_data.at[selected_row, col])
    
        def update_selected_row_in_csv():
            """Get data from all `Entry` and put in `csv_data` for row selected in TreeView."""
            
            global csv_data
    
            if DEBUG:
               print('[DEBUG] update_selected_row_in_csv()')
            
            selected_items = my_tree.selection()
            
            if selected_items:
                selected_row_id = selected_items[0]
                print('Treeview row id:', selected_row_id)
                
                selected_row = my_tree.item(selected_row_id)['text']
                print('DataFrame row number:', selected_row)
                
                for col in csv_data.columns:
                    csv_data.at[selected_row, col] = entry_values[col].get()
                
                update_csv_display()
    
        # --- main ---
        
        if DEBUG:
           print('[DEBUG] create_gui()')
        
        root = tk.Tk()
        #root.geometry("1200x600")
    
        # Create a frame for the CSV data
        csv_frame = tk.Frame(root)
        csv_frame.pack(side="left", padx=10, pady=10)
    
        csv_text = tk.Text(csv_frame, width=120, height=15)
        csv_text.pack()
    
        # Create a button to open the CSV file
        csv_button = tk.Button(csv_frame, text='Open CSV', command=open_csv)
        csv_button.pack(pady=10)
    
        # Create a button to save the CSV file
        save_csv_button = tk.Button(csv_frame, text='Save CSV', command=save_csv)
        save_csv_button.pack(pady=10)
    
        # Add a column entry field and button for manual column addition
        add_column_frame = tk.Frame(csv_frame)
        add_column_frame.pack(pady=10)
    
        entry_column = tk.Entry(add_column_frame, width=10)
        entry_column.pack(padx=5, side="left")
    
        add_column_button = tk.Button(add_column_frame, text='Add Column', command=add_column)
        add_column_button.pack(side="left")
    
        rows_frame = tk.Frame(csv_frame)
        rows_frame.pack()
        
        # Create a Treeview for displaying CSV data
        my_tree = ttk.Treeview(root)
        my_tree.heading('#0', text='Select Row')
        
        my_tree.pack(pady=20)
    
        my_tree.bind("<<TreeviewSelect>>", get_selected_row_from_csv)
    
        update_row_button = tk.Button(root, text='Get selected row from CSV', command=get_selected_row_from_csv)
        update_row_button.pack(padx=5, pady=5)
    
        save_changes_button = tk.Button(root, text='Update selected row in CSV', command=update_selected_row_in_csv)
        save_changes_button.pack(padx=5, pady=5)
    
        root.mainloop()
    
    # --- main ---
    
    csv_data = pd.DataFrame()
    csv_text = None
    entry_values = {}
    entry_widgets = []
    
    create_gui()
    

    PEP 8 -- Style Guide for Python Code