From b8717f8f9f595fd621dd7c489ed98ad973c97544 Mon Sep 17 00:00:00 2001 From: mjeplin <163423890+mjeplin@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:19:41 +0200 Subject: [PATCH] Subclass the PySimpleGUI table --- main.py | 142 ++++++++++++++++++++++++++------------------------------ 1 file changed, 67 insertions(+), 75 deletions(-) diff --git a/main.py b/main.py index d7021f4..dcb0a2d 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,65 @@ import PySimpleGUI as sg +from PySimpleGUI.elements.table import Table as SgTable + +class EditTable(SgTable): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._editing = False + + def _edit_callback(self, event, row, col, text): + widget = event.widget + text = widget.get() + widget.destroy() + widget.master.destroy() + + values = list(self.Widget.item(row, "values")) + values[col] = text + self.Widget.item(row, values=values) + self._editing = False + + def edit_cell(self, window, row, col): + if self._editing or row <0: + return + + self._editing = True + + row += 1 + root = window.TKroot + table = self.Widget + text = table.item(row, "values")[col] + + x, y, width, height = table.bbox(row, col) + + # Depending on where in the GUI your table is, you'll have a different offset + # This seems to be just before the toplevel. + # This loops to find the pad_x and y to pad the frame placement + pad_x = 0 + pad_y = 0 + parent = table.master + while parent.widgetName != "toplevel": + pad_x = parent.winfo_x() + pad_y = parent.winfo_y() + parent = parent.master + + x += pad_x + y += pad_y + + frame = sg.tk.Frame(root) + frame.place(x=x, y=y, anchor="nw", width=width, height=height) + + textvariable = sg.tk.StringVar() + textvariable.set(text) + + entry = sg.tk.Entry(frame, textvariable=textvariable, justify="right") + entry.pack() + entry.select_range(0, sg.tk.END) + entry.icursor(sg.tk.END) + entry.focus_force() + + # Bind to FocusOut and Return (enter) + # No need to send a key, as this is the only place that calls the callback + entry.bind("", lambda e, r=row, c=col, t=text:self._edit_callback(e,r,c,t)) + entry.bind("", lambda e, r=row, c=col, t=text:self._edit_callback(e,r,c,t)) def generate_table_data(): headings = ['Name', 'ID', 'Grade1', 'Grade2', 'Grade3'] @@ -11,80 +72,11 @@ def generate_table_data(): return headings, data -# TKinter function to display and edit value in cell -def edit_cell(window, key, row, col, justify='left'): - - global textvariable, edit - - def callback(event, row, col, text, key): - global edit - # event.widget gives you the same entry widget we created earlier - widget = event.widget - if key == 'Focus_Out': - # Get new text that has been typed into widget - text = widget.get() - # Print to terminal - print(text) - # Destroy the entry widget - widget.destroy() - # Destroy all widgets - widget.master.destroy() - # Get the row from the table that was edited - # table variable exists here because it was called before the callback - values = list(table.item(row, 'values')) - # Store new value in the appropriate row and column - values[col] = text - table.item(row, values=values) - edit = False - - if edit or row <= 0: - return - - edit = True - # Get the Tkinter functionality for our window - root = window.TKroot - # Gets the Widget object from the PySimpleGUI table - a PySimpleGUI table is really - # what's called a TreeView widget in TKinter - table = window[key].Widget - # Get the row as a dict using .item function and get individual value using [col] - # Get currently selected value - text = table.item(row, "values")[col] - # Return x and y position of cell as well as width and height (in TreeView widget) - x, y, width, height = table.bbox(row, col) - - # Create a new container that acts as container for the editable text input widget - frame = sg.tk.Frame(root) - # put frame in same location as selected cell - frame.place(x=x, y=y, anchor="nw", width=width, height=height) - - # textvariable represents a text value - textvariable = sg.tk.StringVar() - textvariable.set(text) - # Used to acceot single line text input from user - editable text input - # frame is the parent window, textvariable is the initial value, justify is the position - entry = sg.tk.Entry(frame, textvariable=textvariable, justify=justify) - # Organizes widgets into blocks before putting them into the parent - entry.pack() - # selects all text in the entry input widget - entry.select_range(0, sg.tk.END) - # Puts cursor at end of input text - entry.icursor(sg.tk.END) - # Forces focus on the entry widget (actually when the user clicks because this initiates all this Tkinter stuff, e - # ending with a focus on what has been created) - entry.focus_force() - # When you click outside of the selected widget, everything is returned back to normal - # lambda e generates an empty function, which is turned into an event function - # which corresponds to the "FocusOut" (clicking outside of the cell) event - entry.bind("", lambda e, r=row, c=col, t=text, k='Focus_Out':callback(e, r, c, t, k)) - def generate_table(): - global edit - - edit = False headings, data = generate_table_data() sg.set_options(dpi_awareness=True) - layout = [[sg.Table(values=data, headings=headings, max_col_width=25, + layout = [[EditTable(values=data, headings=headings, max_col_width=25, font=("Arial", 15), auto_size_columns=True, # display_row_numbers=True, @@ -115,11 +107,11 @@ def generate_table(): if isinstance(event[2][0], int) and event[2][0] > -1: cell = row, col = event[2] print(row) - - # Displays that coordinates of the cell that was clicked on - window['-CLICKED_CELL-'].update(cell) - edit_cell(window, '-TABLE-', row+1, col, justify='right') + + # Displays that coordinates of the cell that was clicked on + window['-CLICKED_CELL-'].update(cell) + window['-TABLE-'].edit_cell(window, row, col) window.close() -generate_table() \ No newline at end of file +generate_table()