New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Enhancement] Demo showing "Edit a cell" #4972
Comments
It's much difficult for me to build it by pure PySimpleGUI code. All tkinter code in function import PySimpleGUI as sg
import random, string
# ------ Some functions to help generate data for the table ------
def word():
return ''.join(random.choice(string.ascii_lowercase) for i in range(10))
def number(max_val=1000):
return random.randint(0, max_val)
def make_table(num_rows, num_cols):
data = [[j for j in range(num_cols)] for i in range(num_rows)]
data[0] = [word() for _ in range(num_cols)]
for i in range(0, num_rows):
data[i] = [i, word(), *[number() for i in range(num_cols - 1)]]
return data
def edit_cell(window, key, row, col, justify='left'):
global textvariable, edit
def callback(event, row, col, text, key):
global edit
widget = event.widget
if key == 'Return':
text = widget.get()
print(text)
widget.destroy()
widget.master.destroy()
values = list(table.item(row, 'values'))
values[col] = text
table.item(row, values=values)
edit = False
if edit or row <= 0:
return
edit = True
# root = window.TKroot # Update 07/23/2022
table = window[key].Widget
root = table.master # Update 07/23/2022
text = table.item(row, "values")[col]
x, y, width, height = table.bbox(row, col)
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=justify)
entry.pack()
entry.select_range(0, sg.tk.END)
entry.icursor(sg.tk.END)
entry.focus_force()
entry.bind("<Return>", lambda e, r=row, c=col, t=text, k='Return':callback(e, r, c, t, k))
entry.bind("<Escape>", lambda e, r=row, c=col, t=text, k='Escape':callback(e, r, c, t, k))
def main_example1():
global edit
edit = False
# ------ Make the Table Data ------
# sg.Print('Creating table...')
data = make_table(num_rows=1_000, num_cols=6)
# headings = [str(data[0][x])+' ..' for x in range(len(data[0]))]
headings = [f'Col {col}' for col in range(len(data[0]))]
# sg.Print('Done creating table. Creating GUI...')
sg.set_options(dpi_awareness=True)
layout = [[sg.Table(values=data, headings=headings, max_col_width=25,
auto_size_columns=True,
# display_row_numbers=True,
justification='right',
num_rows=20,
alternating_row_color=sg.theme_button_color()[1],
key='-TABLE-',
# selected_row_colors='red on yellow',
# enable_events=True,
# select_mode=sg.TABLE_SELECT_MODE_BROWSE,
expand_x=True,
expand_y=True,
enable_click_events=True, # Comment out to not enable header and other clicks
)],
[sg.Button('Read'), sg.Button('Double'), sg.Button('Change Colors')],
[sg.Text('Cell clicked:'), sg.T(k='-CLICKED-')]]
window = sg.Window('Table Element - Example 1', layout, resizable=True, finalize=True)
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
elif isinstance(event, tuple):
cell = row, col = event[2]
window['-CLICKED-'].update(cell)
edit_cell(window, '-TABLE-', row+1, col, justify='right')
window.close()
main_example1() Note: Here I found something special, I can use both methods |
By using Demo example here from io import BytesIO
from PIL import Image
import PySimpleGUI as sg
def image_to_data(im):
with BytesIO() as output:
im.save(output, format="PNG")
data = output.getvalue()
return data
def make_background(window, file, main_frame):
global images
def find_frames(widget):
widgets = list(widget.children.values())
if isinstance(widget, (sg.tk.Frame, sg.tk.LabelFrame)):
widget.update()
x, y = widget.winfo_rootx() - x0, widget.winfo_rooty() - y0
width, height = widget.winfo_width(), widget.winfo_height()
new_im = im_.crop((x, y, x+width, y+height))
image = sg.tk.PhotoImage(data=image_to_data(new_im))
images.append(image)
label = sg.tk.Label(widget, image=image, padx=0, pady=0, bd=0, bg=bg)
label.place(x=0, y=0)
label.lower()
for widget in widgets:
find_frames(widget)
size = window.size
im_ = Image.open(file).resize(size)
root = window.TKroot
widgets = list(root.children.values())
x0, y0 = root.winfo_rootx(), root.winfo_rooty()
frame = sg.tk.Frame(root, padx=0, pady=0, bd=0, bg=bg)
frame.place(x=0, y=0)
images = []
image = sg.tk.PhotoImage(data=image_to_data(im_))
images.append(image)
label = sg.tk.Label(frame, image=image, padx=0, pady=0, bd=0, bg=bg)
label.pack()
main_frame.Widget.master.place(in_=frame, anchor='center', relx=.5, rely=.5)
frame.lower()
frame.update()
for widget in widgets:
find_frames(widget)
bg = sg.theme_background_color()
background_image_file = 'd:/background.png'
w, h = size = 640, 480 # size of background image
sg.set_options(dpi_awareness=True)
frame = [
[sg.Input(size=(30, 1), expand_x=True),
sg.Button('Browse')],
[sg.Multiline('', expand_x=True, expand_y=True)],
[sg.Button('Exit')],
]
layout = [[sg.Frame('', frame, size=(350, 200), border_width=0, key='FRAME', background_color=bg)]]
location = sg.Window.get_screen_size()
window = sg.Window('Background Demo', layout, margins=(0, 0), grab_anywhere=True,
size=size, keep_on_top=True, finalize=True,
no_titlebar=True,
transparent_color=bg,
)
images = []
make_background(window, background_image_file, window['FRAME'])
while True:
event, values = window.read()
if event in (sg.WINDOW_CLOSED, 'Cancel', 'Exit'):
break
print(event)
window.close() |
Thank you!!! I will study this today! I'm more than happy to also pull some of this code into PySimpleGUI itself to help create this feature. I could see helping users with additional parameters, additional methods, etc. I just didn't have the code but certainly, have code to look at now! You're a true programming 🧙♂️ Jason! The screenshot is AMAZING. This will no doubt be a popular feature once it's discovered. VERY clever use of place/pack. One serious limitation of PySimpleGUI is that in the tkinter callbacks, I cannot call other PySimpleGUI calls that create windows. For example, I can't call popup_get_date. It'll be interesting to merge your code with my table callback code. This is the code that is in PySimpleGUI that does the header and cell detection. And, once again, I have you to thank for this code existing! def _table_clicked(self, event):
"""
Not user callable. Callback function that is called a click happens on a table.
Stores the selected rows in Element as they are being selected. If events enabled, then returns from Read
:param event: event information from tkinter
:type event: (unknown)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
try:
region = self.Widget.identify('region', event.x, event.y)
if region == 'heading':
row = -1
elif region == 'cell':
row = int(self.Widget.identify_row(event.y))-1
elif region == 'separator':
row = None
else:
row = None
col_identified = self.Widget.identify_column(event.x)
if col_identified: # Sometimes tkinter returns a value of '' which would cause an error if cast to an int
column = int(self.Widget.identify_column(event.x)[1:])-1-int(self.DisplayRowNumbers is True)
else:
column = None
except Exception as e:
warnings.warn('Error getting table click data for table with key= {}\nError: {}'.format(self.Key, e), UserWarning)
if not SUPPRESS_ERROR_POPUPS:
_error_popup_with_traceback('Unable to complete operation getting the clicked event for table with key {}'.format(self.Key), _create_error_message(), e, 'Event data:', obj_to_string_single_obj(event))
row = column = None
self.last_clicked_position = (row, column)
# update the rows being selected if appropriate
self.ParentForm.TKroot.update()
# self.TKTreeview.()
selections = self.TKTreeview.selection()
# print(selections)
self.SelectedRows = [int(x) - 1 for x in selections]
# print('The new selected rows = ', self.SelectedRows)
if self.enable_click_events is True:
if self.Key is not None:
self.ParentForm.LastButtonClicked = (self.Key, '+CICKED+', (row, column))
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
_exit_mainloop(self.ParentForm) |
Here, I found another issue, location = sg.Window.get_screen_size()
window = sg.Window('Demo background', layout, margins=(0, 0), border_depth=0,
location=location, finalize=True)
background(window, "D:/desktop.png")
window.refresh() # required if window changed, and GUI not updated
window.move_to_center() |
Ah, I maybe should add a refresh inside of |
Ideally, I want to merge the code you provided for the Edit Cell into the code inside PySimpleGUI, specifically the callback used to determine if a header or cell was clicked. That seems like the best place to do the edit type of operation. I was thinking of adding a few parms such as Gosh Jason, thank you so much for this code! I'm stunned every single time. |
IMO, there should be different when select a row/item or edit, maybe double clicks to edit ? |
Yes yes! Maybe a "bind string" can be passed in as an optional parm? The default being a double click of the left mouse. By making a parameter with a bind-string, it can be overridden. Thank you for continuing to make this better and better. This is great stuff! |
I really think this is worth the investment of getting this feature into the code prior to recording the Udemy lesson on Tables. I've got the basic examples written and this one feature would REALLY take it something special! |
Now that I've said this, I have to make sure to balance out the need, to get the course completed ASAP... so, I may end up delaying this implementation for a bit instead of trying to code it up quicky, jam it into a release, and then write a lesson that incorporates it. I think I would prefer, now that I've taken a moment, to get the course completed and then go back and make this enhancement. One of the examples in the lesson does cover getting click events on cells and headers, so the viewers will be educated enough on the Table element to be able to easily understand the addition of a few extra parms. It's really hard to resist adding these features that stand out as clearly being incredibly useful with getting this course completed. Thank you for doing all this work. I'm going to wait until I get the course done and then go back to this particular feature FIRST. I can also always go back and make another recording of the Table lesson once the feature is completed. |
@jason990420 I used the same Do you have a clue on how to modify the position of the Entry?
|
Maybe you can try to change the table = window[key].Widget
root = table.master |
It solved my problem. Thank you for the prompt reply! |
@jason990420 Thank you for the edit_cell function. Very cool. I am trying to use it for a GUI with tabs. I have one table in each tab. When I select a cell in table 2 it edits the corresponding cell in Table 1. I am not too familiar with Tkinter. I have included the code. If you select a cell in Table 2, the cell in Table 1 is highlighted instead. Is there a way to edit a table in different tabs? Thanks!
|
There're variables for settings of table editor, so it will not work for multiple tables and if table not shown. The programming logic may work wrong if you click on one table and finished the cell edit only on one table, then next click on table to edit will not work in the right way. Anyway, IMO, It looks like wrong logic to edit the same cell of both tables at the same time. To work for each table, I revise it to new class with new method. import PySimpleGUI as sg
class Table(sg.Table):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.edit = False
def callback(self, event, row, col, text, key):
widget = event.widget
if key == 'Return':
text = widget.get()
widget.destroy()
widget.master.destroy()
values = list(self.table.item(row, 'values'))
values[col] = text
self.table.item(row, values=values)
self.edit = False
def edit_cell(self, row, col, justify='left'):
if self.edit or row <= 0:
return
self.edit = True
self.table = self.Widget
root = self.table.master
text = self.table.item(row, "values")[col]
rec = self.table.bbox(row, col)
print(repr(rec))
x, y, width, height = rec
frame = sg.tk.Frame(root)
frame.place(x=x, y=y, anchor="nw", width=width, height=height)
self.textvariable = sg.tk.StringVar()
self.textvariable.set(text)
entry = sg.tk.Entry(frame, textvariable=self.textvariable, justify=justify)
entry.pack()
entry.select_range(0, sg.tk.END)
entry.icursor(sg.tk.END)
entry.focus_force()
entry.bind("<Return>", lambda e, r=row, c=col, t=text, k='Return':self.callback(e, r, c, t, k))
entry.bind("<Escape>", lambda e, r=row, c=col, t=text, k='Escape':self.callback(e, r, c, t, k))
## Make GUI with tabs ##
col_labels = ['Col 1', 'Col 2', 'Col 3']
data1 = [[1, 1, 1],
[1, 1, 1],
[1, 1, 1]]
data2 = [[2, 2, 2],
[2, 2, 2],
[2, 2, 2]]
# Some options
sg.set_options(dpi_awareness=True)
sg.theme('LightBlue')
edit = False # Set edit to be false
## GUI Layout
# Table 1
t1_layout = [[Table(values=data1, headings=col_labels, max_col_width=25,
auto_size_columns=True,
justification='center',
row_height=23,
num_rows=8,
alternating_row_color=sg.theme_button_color()[0],
key='-TABLE-1-',
selected_row_colors='red on yellow',
enable_events=True,
select_mode=sg.TABLE_SELECT_MODE_BROWSE,
expand_x=True,
expand_y=True,
enable_click_events=True, # Comment out to not enable header and other clicks
)]]
# Table 2
t2_layout = [[Table(data2, headings=col_labels, max_col_width=25,
auto_size_columns=True,
justification='center',
row_height=23,
num_rows=8,
alternating_row_color=sg.theme_button_color()[0],
key='-TABLE-2-',
selected_row_colors='red on yellow',
enable_events=True,
select_mode=sg.TABLE_SELECT_MODE_BROWSE,
expand_x=True,
expand_y=True,
enable_click_events=True, # Comment out to not enable header and other clicks
)] ]
# Group the tabs
tab_group = [
[sg.TabGroup(
[[
sg.Tab('Table 1', t1_layout, background_color=None, key='-TAB 1-'),
sg.Tab('Table 2', t2_layout, background_color=None, key='-TAB 2-') ]],
tab_location='centertop',
title_color='White',
tab_background_color='Purple',
selected_title_color='Green',
selected_background_color='Yellow',
border_width=5,
expand_x=True,
expand_y=True,
key='-TABGROUP-',
enable_events=True),
],
[sg.Text('Cell clicked:'), sg.T(k='-CLICKED-')]
]
## GUI Window
window = sg.Window('Edit Table Test', tab_group, resizable=True, finalize=True)
tab1 , tab2 = window['-TAB 1-'], window['-TAB 2-']
table1, table2 = window['-TABLE-1-'], window['-TABLE-2-']
## GUI Event
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
elif isinstance(event, tuple):
cell = row, col = event[2]
window['-CLICKED-'].update(cell)
if row is not None:
tab = values['-TABGROUP-']
tab1.select()
window.refresh()
table1.edit_cell(row+1, col, justify='center')
tab2.select()
window.refresh()
table2.edit_cell(row+1, col, justify='center')
window[tab].select()
window.close() Note: No testing on new code. |
Awesome, thanks Jason! The only thing I included is an 'if' statement in the last block in the isinstance() call. Now the code will only edit the selected cell in the current tab. Thanks again Jason! import PySimpleGUI as sg
class Table(sg.Table):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.edit = False
def callback(self, event, row, col, text, key):
widget = event.widget
if key == 'Return':
text = widget.get()
widget.destroy()
widget.master.destroy()
values = list(self.table.item(row, 'values'))
values[col] = text
self.table.item(row, values=values)
self.edit = False
def edit_cell(self, row, col, justify='left'):
if self.edit or row <= 0:
return
self.edit = True
self.table = self.Widget
root = self.table.master
text = self.table.item(row, "values")[col]
rec = self.table.bbox(row, col)
print(repr(rec))
x, y, width, height = rec
frame = sg.tk.Frame(root)
frame.place(x=x, y=y, anchor="nw", width=width, height=height)
self.textvariable = sg.tk.StringVar()
self.textvariable.set(text)
entry = sg.tk.Entry(frame, textvariable=self.textvariable, justify=justify)
entry.pack()
entry.select_range(0, sg.tk.END)
entry.icursor(sg.tk.END)
entry.focus_force()
entry.bind("<Return>", lambda e, r=row, c=col, t=text, k='Return':self.callback(e, r, c, t, k))
entry.bind("<Escape>", lambda e, r=row, c=col, t=text, k='Escape':self.callback(e, r, c, t, k))
## Make GUI with tabs ##
col_labels = ['Col 1', 'Col 2', 'Col 3']
data1 = [[1, 1, 1],
[1, 1, 1],
[1, 1, 1]]
data2 = [[2, 2, 2],
[2, 2, 2],
[2, 2, 2]]
# Some options
sg.set_options(dpi_awareness=True)
sg.theme('LightBlue')
edit = False # Set edit to be false
## GUI Layout
# Table 1
t1_layout = [[Table(values=data1, headings=col_labels, max_col_width=25,
auto_size_columns=True,
justification='center',
row_height=23,
num_rows=8,
alternating_row_color=sg.theme_button_color()[0],
key='-TABLE-1-',
selected_row_colors='red on yellow',
enable_events=True,
select_mode=sg.TABLE_SELECT_MODE_BROWSE,
expand_x=True,
expand_y=True,
enable_click_events=True, # Comment out to not enable header and other clicks
)]]
# Table 2
t2_layout = [[Table(data2, headings=col_labels, max_col_width=25,
auto_size_columns=True,
justification='center',
row_height=23,
num_rows=8,
alternating_row_color=sg.theme_button_color()[0],
key='-TABLE-2-',
selected_row_colors='red on yellow',
enable_events=True,
select_mode=sg.TABLE_SELECT_MODE_BROWSE,
expand_x=True,
expand_y=True,
enable_click_events=True, # Comment out to not enable header and other clicks
)] ]
# Group the tabs
tab_group = [
[sg.TabGroup(
[[
sg.Tab('Table 1', t1_layout, background_color=None, key='-TAB 1-'),
sg.Tab('Table 2', t2_layout, background_color=None, key='-TAB 2-') ]],
tab_location='centertop',
title_color='White',
tab_background_color='Purple',
selected_title_color='Green',
selected_background_color='Yellow',
border_width=5,
expand_x=True,
expand_y=True,
key='-TABGROUP-',
enable_events=True),
],
[sg.Text('Cell clicked:'), sg.T(k='-CLICKED-')]
]
## GUI Window
window = sg.Window('Edit Table Test', tab_group, resizable=True, finalize=True)
tab1 , tab2 = window['-TAB 1-'], window['-TAB 2-']
table1, table2 = window['-TABLE-1-'], window['-TABLE-2-']
## GUI Event
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
elif isinstance(event, tuple):
cell = row, col = event[2]
window['-CLICKED-'].update(cell)
if row is not None:
tab = values['-TABGROUP-']
if tab == '-TAB 1-': # Edits the cell only in Table 1
tab1.select()
window.refresh()
table1.edit_cell(row+1, col, justify='center')
if tab == '-TAB 2-': # Edits the cell only in Table 2
tab2.select()
window.refresh()
table2.edit_cell(row+1, col, justify='center')
window[tab].select()
window.close() |
First of all, thanks for the feature.
|
There're still lot of issues for the edit function., like
You can find the index in the values of your table by (row, col), and update the value by if key == 'Return':
text = widget.get() # Get the new value of cell, then convert to the data type and update the values of your table.
print(text) |
Thanks a lot for the reply and the useful hints.
|
Type of Issue (Enhancement, Error, Bug, Question)
Enhancement
Environment
Operating System
Windows version 10
PySimpleGUI Port (tkinter, Qt, Wx, Web)
tkinter
Versions
Python version (
sg.sys.version
)3.11.0a2 (tags/v3.11.0a2:e2b4e4b, Nov 5 2021, 20:00:05) [MSC v.1929 64 bit (AMD64)]
PySimpleGUI Version (
sg.__version__
)4.55.1.4
GUI Version (tkinter (
sg.tclversion_detailed
), PySide2, WxPython, Remi)8.6.11
Your Experience In Months or Years (optional)
43 Years Python programming experience
4 Years Programming experience overall
No Have used another Python GUI Framework? (tkinter, Qt, etc) (yes/no is fine)
This project and the users!
Troubleshooting
These items may solve your problem. Please check those you've done by changing - [ ] to - [X]
Detailed Description
I'm working on the Table lesson for the new course.
It would be nice to be able to point to a demo that does an "edit" operation.
In Qt it's possible to do that.
Jason wrote the code that does the detect of click events for cells and headers. I'm hoping that maybe Jason can work his magic again and help make this example better. My focus really needs to be on the course more than working on this enhancement. I tend to add to elements as I record the lessons. This would be a fantastic one to have.
So far I have a little input that pops up when you click a cell. The problem is that it pops up where the mouse clicked. It would be ideal if it popped up exactly where that cell is located so that it looks like the cell is being typed in.
To use the example, click on a cell. When the input opens, type the new value. Press return to save it or Escape to cancel.
It's pretty close, kind of, but could be a lot better.
Code To Duplicate
Screenshot, Sketch, or Drawing
The text was updated successfully, but these errors were encountered: