Bug (I think)
Windows 10 64-bit
3.7.0
3.29.0
I have seen a couple bugs regarding the Table element. If you use the "alternating_row_color" parameter (I have it set as light blue) and you have a button that updates the Table element using the Update() call, it will make every row that color instead of keeping it as alternating.
The other bug that I found is when you use both the "row_colors" and "alternating_row_color" parameters together. The "alternating_row_color" parameter makes all of the even rows (0, 2, 4, 6, etc) the color that you choose. Now, let's say you use the "row_colors" parameter to make the first row the color red, you would put "row_colors=[(0, 'red')]". If you run the program with these two parameters and then press the button the uses the Update() call on the Table element, it will cause the color you chose for the "row_colors" parameter to be used as the color for the "alternating_row_color" parameter (so will make all of the even rows red instead of the original color) and the color used in the "alternating_row_color" parameter will then color all of the odd rows instead of the even rows (so row 1, 3, 5, 7 etc). If you use the "row_colors" parameter to make any other row a different color and then press a button that uses the Update() call, it just does the same thing as the first bug I mentioned (turns every row the same color as the color chosen for the parameter).
Here is the example code that I used:
import PySimpleGUI as sg
from random import randint as rand
def table_example():
data = [list(str(rand(0,50)) for i in range(5)) for i in range(10)]
header_list = ['C1', 'C2', 'C3', 'C4', 'C5']
layout = [[sg.Table(values=data,
headings=header_list,
auto_size_columns=True,
justification='center',
num_rows=min(len(data), 20),
alternating_row_color='lightblue',
row_colors=((0, 'red'), (2, 'yellow')),
display_row_numbers=True,
key='table')],
[sg.Button('Randomize'), sg.Button('Close')]]
window = sg.Window('table', layout)
while True:
event, values = window.Read()
if event in ('Close', None): break
if event is 'Randomize':
window.Element('table').Update(values=[list(str(rand(0,50)) for i in range(5)) for i in range(10)],
num_rows=min(len(data), 20))
table_example()
All the code does is randomly generate rows using the random package and uses that to create a table. When you press the "Randomize" button, it basically rerolls the numbers for each row (same code as the 'data' variable) to update the table with new numbers. If you comment out the "row_colors" parameter line, you should see the first bug I am talking about when you press the "Randomize" button. If you run the program as is and then press the "Randomize" button, you should see the second bug I am talking about. I hope I explained myself well enough and hope that you can see the same thing I am using my example.
Yea, the reason is pretty straightforward.
Here is the code UPDATE code for a table
if values is not None:
children = self.TKTreeview.get_children()
for i in children:
self.TKTreeview.detach(i)
self.TKTreeview.delete(i)
children = self.TKTreeview.get_children()
# self.TKTreeview.delete(*self.TKTreeview.get_children())
for i, value in enumerate(values):
if self.DisplayRowNumbers:
value = [i+self.StartingRowNumber] + value
id = self.TKTreeview.insert('', 'end', text=i, iid=i + 1, values=value, tag=i % 2)
if self.AlternatingRowColor is not None:
self.TKTreeview.tag_configure(1, background=self.AlternatingRowColor)
self.Values = values
self.SelectedRows = []
if visible is False:
self.TKTreeview.pack_forget()
elif visible is True:
self.TKTreeview.pack()
if num_rows is not None:
self.TKTreeview.config(height=num_rows)
And this is the code that is executed when a table element is created:
elif element_type == ELEM_TYPE_TABLE:
element = element # type: Table
frame = tk.Frame(tk_row_frame)
height = element.NumRows
if element.Justification == 'left':
anchor = tk.W
elif element.Justification == 'right':
anchor = tk.E
else:
anchor = tk.CENTER
column_widths = {}
for row in element.Values:
for i, col in enumerate(row):
col_width = min(len(str(col)), element.MaxColumnWidth)
try:
if col_width > column_widths[i]:
column_widths[i] = col_width
except:
column_widths[i] = col_width
if element.ColumnsToDisplay is None:
displaycolumns = element.ColumnHeadings if element.ColumnHeadings is not None else element.Values[0]
else:
displaycolumns = []
for i, should_display in enumerate(element.ColumnsToDisplay):
if should_display:
displaycolumns.append(element.ColumnHeadings[i])
column_headings = element.ColumnHeadings
if element.DisplayRowNumbers: # if display row number, tack on the numbers to front of columns
displaycolumns = [element.RowHeaderText, ] + displaycolumns
column_headings = [element.RowHeaderText, ] + element.ColumnHeadings
element.TKTreeview = element.Widget = ttk.Treeview(frame, columns=column_headings,
displaycolumns=displaycolumns, show='headings', height=height,
selectmode=element.SelectMode,)
treeview = element.TKTreeview
if element.DisplayRowNumbers:
treeview.heading(element.RowHeaderText, text=element.RowHeaderText) # make a dummy heading
treeview.column(element.RowHeaderText, width=50, anchor=anchor)
headings = element.ColumnHeadings if element.ColumnHeadings is not None else element.Values[0]
for i, heading in enumerate(headings):
treeview.heading(heading, text=heading)
if element.AutoSizeColumns:
width = max(column_widths[i], len(heading))
else:
try:
width = element.ColumnWidths[i]
except:
width = element.DefaultColumnWidth
treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor)
# Insert values into the tree
for i, value in enumerate(element.Values):
if element.DisplayRowNumbers:
value = [i+element.StartingRowNumber] + value
id = treeview.insert('', 'end', text=value, iid=i + 1, values=value, tag=i)
if element.AlternatingRowColor is not None: # alternating colors
for row in range(0, len(element.Values), 2):
treeview.tag_configure(row, background=element.AlternatingRowColor)
if element.RowColors is not None: # individual row colors
for row_def in element.RowColors:
if len(row_def) == 2: # only background is specified
treeview.tag_configure(row_def[0], background=row_def[1])
else:
treeview.tag_configure(row_def[0], background=row_def[2], foreground=row_def[1])
if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT:
ttk.Style().configure("Treeview", background=element.BackgroundColor,
fieldbackground=element.BackgroundColor)
if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT:
ttk.Style().configure("Treeview", foreground=element.TextColor)
if element.RowHeight is not None:
ttk.Style().configure("Treeview", rowheight=element.RowHeight)
ttk.Style().configure("Treeview", font=font)
# scrollable_frame.pack(side=tk.LEFT, padx=elementpad[0], pady=elementpad[1], expand=True, fill='both')
treeview.bind("<<TreeviewSelect>>", element.treeview_selected)
if element.BindReturnKey:
treeview.bind('<Return>', element.treeview_double_click)
treeview.bind('<Double-Button-1>', element.treeview_double_click)
if not element.HideVerticalScroll:
scrollbar = tk.Scrollbar(frame)
scrollbar.pack(side=tk.RIGHT, fill='y')
scrollbar.config(command=treeview.yview)
treeview.configure(yscrollcommand=scrollbar.set)
if not element.VerticalScrollOnly:
hscrollbar = tk.Scrollbar(frame, orient=tk.HORIZONTAL)
hscrollbar.pack(side=tk.BOTTOM, fill='x')
hscrollbar.config(command=treeview.xview)
treeview.configure(xscrollcommand=hscrollbar.set)
element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both')
if element.Visible is False:
element.TKTreeview.pack_forget()
frame.pack(side=tk.LEFT, expand=True, padx=0, pady=0)
if element.Tooltip is not None:
element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip,
timeout=DEFAULT_TOOLTIP_TIME)
if element.RightClickMenu or toplevel_form.RightClickMenu:
menu = element.RightClickMenu or toplevel_form.RightClickMenu
top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False)
AddMenuItem(top_menu, menu[1], element)
element.TKRightClickMenu = top_menu
element.TKTreeview.bind('<Button-3>', element.RightClickMenuCallback)
As you as easily see, a fair amount of code needs to be added to the Update code to deal with row colors, etc.
I tried to make the Demo_Table_Element.py working in a browser with just changing
line 5 import PySimpleGUIWeb as sg
but got the error:
Traceback (most recent call last):
File "/home/giodegas/Documenti/dev/TaLTaC4/t4/scouting/table_test.py", line 47, in <module> window.FindElement('_table_').Update(values=data)
File "/home/giodegas/.taltac/lib/python3.7/site-packages/PySimpleGUIWeb/PySimpleGUIWeb.py", line 2583, in Update
children = self.TKTreeview.get_children()
AttributeError: 'NoneType' object has no attribute 'get_children'
The Web port is the newest and may not always have matching parameters or work exactly the same way. The Table Element is one of the most complex of the elements. I believe that was the element I was working on when I took a break and started working on the documentation.
I'll have a look. I try to test Demo code against the other ports. If they work, I leave the other imports comments out at the top.
Yea, the Web port still has a way to go on Tables. The Update method is what is crashing. It's attempting to call old tkinter code as I haven't written the Table.Update method yet. The Web Readme file chronicles the Table Element development.
I'll add an action item to put asserts in the code for these user callable functions/methods that are not yet completed.
I can do this as part of the documentation work that's going on right now. I'm on the tkinter port still, but will be doing the same docstring stuff for all of the ports. Hmmmm... that may also be a good place to indicate methods that are not yet completed. Between those 2 changes it should add some clarity to what's implemented and what's not.
I eventually made it to work in a web browser, regenerating the window layout when data is changed.
Check it out at this gist: PySimpleGuiWeb_Table_test.py
BRILLIANT!
You even did the right thing in how you created the window.... you did not "re-use" the layout and instead created a brand new one every time. This is 100% the correct way of doing it.
I'm impressed. I would have never thought to do it that way!
It's this kind of stuff that I love about this community. I learn something new from you guys every day.
Well, thank you, but it is far from to be perfect. When closing the web page, the None event is not delivered any more.. So I have to stop the app manually.
Hmmmmmm...... oh.... I need to give you a way to close a window but not exit Remi. Like the Popups. I forget what I did to make Popups work.
Hi,
just a short question. I also would like to set the row colors in the Table.Update function.
Can you tell me roughly what priority this feature has for you at the moment? I am trying to decide whether I should wait or start looking for a solution for my specific use case.
I want to make sure that everyone on this thread is NOT running Python 3.7.4. That the form filled in with 3.7.0 is correct... you really are running 3.7.0.
@till314 - I'm not a fan of bugs. I'll get on this one soon since it's in the tkinter version which tends to get the highest priority as it's the '"done" one of the 4 ports.
You can now change the alternating row color and the individual row colors using the Table.Update function for PySimpleGUI (the tkinter version).
I don't have the bandwidth to document any further than the doc strings the I just completed. You may have to play with it to figure some sh*t out, but the code is there at least for you to try.
You'll want to check out the Demo Program named: Demo_Table_Element.py because it calls Update a couple of times to show how to update the data as well as the f*cking colors.
Row colors come in 2 formats. The text color is optional. Here is a single entry from a list of row colors:
(row#, [foreground/text color], background color)
This example shows 2 rows being specifically colored to have a red background. Row 12 also has white text.
rows_colors = ((12, 'white', 'red'), (13, 'red'))
If no background or text colors specified when the table was created, and the values are updated, the default is black text on a white background.
I believe this is the feature everyone is asking for. Please test it out and lemme know if this will fit your use cases. It should cover just about any combination you desire. It may take a little cleverness, I dunno.
Good luck.
To use, once again, download PySimpleGUI.py from GitHub and place in the folder with your application (in with whatever .py file that imports PySimpleGUI)
Thanks. Works fine here.
I found interesting update behavior with version "4.1.0.16 Unreleased - Anniversary Edition"
import PySimpleGUI as sg
data = [[str(x * y) for x in range(5)] for y in range(100)]
header = ["one", "two", "three", "four", "five"]
Table1 = sg.Table(
values=data,
headings=header,
text_color="blue",
row_height=20,
background_color="green",,
key="_table1_",)
Table2 = sg.Table(
values=data,
headings=header,
text_color="lightgrey",
row_height=20,
background_color="blue",
alternating_row_color="red",
key="_table2_",)
layout = [[sg.Button("Exit", key="Exit", visible=True)],[sg.Button("Update1", size=(18,1), visible=True),sg.Button("Update2" ,size=(18,1), visible=True)], [Table1,Table2]]
window = (
sg.Window("Table Color", auto_size_buttons=False, default_element_size=(20, 5), border_depth=1, resizable=True, ).Layout(layout).Finalize())
while True:
event, values = window.Read()
if event is None or event == "Exit":
break
elif event is None or event == "Update1":
window.FindElement("_table1_").Update(data)
elif event is None or event == "Update2":
window.FindElement("_table2_").Update(data)
That the different background-colors and text-colors are not respected before using Update is due to the style system in tk, could be changed to the same code used in Update, but not that interesting I think. But that text-color and background-color of the first row aren't updated is due to the fact that the id in tree_ids starts with one not with zero.
if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT:
for row in range(0, len(element.Values)):
treeview.tag_configure(row, background=element.BackgroundColor)
if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT:
for row in range(0, len(element.Values)):
treeview.tag_configure(row, foreground=element.TextColor)
Most helpful comment
BRILLIANT!
You even did the right thing in how you created the window.... you did not "re-use" the layout and instead created a brand new one every time. This is 100% the correct way of doing it.
I'm impressed. I would have never thought to do it that way!
It's this kind of stuff that I love about this community. I learn something new from you guys every day.