FreeSimpleGUI
FreeSimpleGUI copied to clipboard
Column scroll distance not updated until window is manually resized
I'm having trouble with using tabs or the "visible" property of frames within a column that has scrolling enabled.
I'm trying to create a layout that fits vertically on the screen when the app is started, so I made some of the frames invisible, and allow them to be made visible when needed. I've tried tabs and the "visible" property, but the scrollbars of the containing column are not updating correctly.
If the frames are hidden to begin with, the main column scrollbars are set for the minimized content, and they don't update when the content is maximized. If the frames are visible to begin with, the scrollbars are set correctly - but when the frames are minimized they reset to the smaller size, and when the frames are maximized, they don't update again.
For some reason, the scrollbars reset when minimizing sections, but not when maximizing sections.
If you maximize sections and then manually resize the window by any amount, the scrollbars will reset to the current view. But this does not happen when programmatically resizing the window, so it's not a workaround.
import FreeSimpleGUI as sg
def collapse(title, layout, key):
"""
Helper function that creates a Column that can be later made hidden, thus appearing "collapsed"
:param layout: The layout for the section
:param key: Key used to make this seciton visible / invisible
:return: A pinned column that can be placed directly into your layout
:rtype: sg.pin
"""
# return sg.pin(sg.Column(layout, key=key))
return sg.pin(sg.Frame(title, layout, border_width=6,
relief="ridge", expand_x=True, visible=False, key=key))
test_1_multiline = [
[sg.Sizer(h_pixels=3),
sg.Multiline(size=(83, 13),
font=('Courier', 14, 'bold'), background_color='#fdfdfc',
expand_x=True, disabled=True,
no_scrollbar=True, key='TEST_1_MLINE'),
sg.Sizer(h_pixels=3)],
[sg.Sizer(h_pixels=0, v_pixels=5)]
]
test_1_col1 = [
[sg.Frame('Test Frame 1',
test_1_multiline, expand_x=True, border_width=1, relief='ridge',
font=('Helvetica', 13, 'bold'), title_location=sg.TITLE_LOCATION_TOP)]
]
test_1_col2 = [
[sg.Text('Test text 1')]
]
test_1_layout = [
[sg.Column(test_1_col1, element_justification='centered',
expand_x=True)],
[sg.Column(test_1_col2, expand_x=True)]
]
sec1 = sg.Column(
[
[sg.T(sg.SYMBOL_RIGHT, enable_events=True, k='OPEN_TEST1SEC'),
sg.T('Binary Operations', enable_events=True,
font=(None, 14,"bold"), k='OPEN_TEST1SEC_TEXT')],
[collapse('', test_1_layout, 'TEST1SEC'), sg.Sizer(h_pixels=5)]
],
key='SEC1_KEY'
)
# App Control Button Frame
sec5 = sg.Frame("",
[
[
sg.Column(
[
[
sg.Button('Example', font='Helvetica 11', key='-EXAMPLE-'),
sg.Button('Clear', font='Helvetica 11', key='-CLEAR-'),
sg.Text('', text_color='red', font='None 14 bold',
key='-BTN_MESSAGES-'),
sg.Push(),
sg.Button('About', font=('Helvetica', 12)),
sg.Button(' Exit ', font=('Helvetica', 12, 'bold'))
]
],
expand_x=True,
)
]
],
expand_x=True,
border_width=6,
relief="ridge",
)
page_layout = [
[sec1],
# [sec2],
# [sec3],
# [sec4],
[sg.Sizer(h_pixels=5), sec5, sg.Sizer(h_pixels=10)],
[sg.Sizer(v_pixels=8)]
]
layout = [
# [results_frame],
[sg.Column(page_layout,
expand_x=True,
expand_y=True,
scrollable=True,
# vertical_scroll_only=True,
key='LAYOUT_COLUMN'),
]
]
# window = sg.Window('IP MAP Calculator', layout, size=(796, 500),
# window = sg.Window('IP MAP Calculator', layout, size=(771, 200),
window = sg.Window('IP MAP Calculator', layout, size=(700, 200),
resizable=True, enable_close_attempted_event=True,
finalize=True,
location=sg.user_settings_get_entry('-location-', (None, None))) # resizable=True, # size=(777, 1000)
# Main Event Loop
# opened1, opened2, opened3 = False, False, False
opened1 = False
count = 0
while True:
event, values = window.read()
print(event, values)
if event in (' Exit ', sg.WINDOW_CLOSE_ATTEMPTED_EVENT):
# Save screen location when closing window
sg.user_settings_set_entry('-location-', window.current_location())
break
if event.startswith('OPEN_TEST1SEC'):
opened1 = not opened1
window['OPEN_TEST1SEC'].update(sg.SYMBOL_DOWN if opened1 else sg.SYMBOL_RIGHT)
window['TEST1SEC'].update(visible=opened1)
window['LAYOUT_COLUMN'].contents_changed() # per Controls > Column > scrollable - Description
# a, b = window.size
# window.size = (700, b + 1)
# print(window.size)
# window.refresh()
# print(window.size) # on any event
window.close()
https://github.com/user-attachments/assets/a249df42-f295-44c8-87d0-81f1b775a8a7
macOS Sonoma 14.7 Python 3.12.4 FSG Version: 5.1.1 tkinter version 8.6
Thank you for the very detailed report and the recording. I think it is clear to me what the unexpected/expected behaviors are, though the cause is not immediately apparent. Tentatively, I will call this a bug.
At this moment, I can't make any promises as to timelines for a fix, but I will investigate as my time allows and try to come up with a fix or workaround. I'll also be happy to collaborate with any other interested people to investigate and/or review PRs that address this.
Thanks again for the report!
Possibly helpful info:
I just searched through the PSG issues and found #5017 that suggested adding both of these after changing the scrollable column's contents (in my case, changing visibility of pinned frames):
window.refresh()
window['LAYOUT_COLUMN'].contents_changed()
With my first example script (above), this only worked if there was only one element that changed size, but not if there were multiple elements that changed size.
After more testing, adding these lines is giving mixed results. I was able to create one layout that seems to work when using them. The problem may have to do with the depth of nesting in container elements within the top-level, scrollable column.
Layout that is working better:
import FreeSimpleGUI as sg
def collapse(num):
return [
sg.Column(
[
[sg.T(sg.SYMBOL_RIGHT, enable_events=True,
k=f'OPEN_FRAME_{num}'),
sg.T(f'Frame {num}', enable_events=True,
font=(None, 14, "bold"), k=f'OPEN_FRAME_{num}_TEXT')
],
[sg.pin(sg.Frame(f'Frame {num}',
[[sg.T(f'Section {num} text')]],
border_width=6, relief="ridge",
expand_x=True, visible=False,
key=f"FRAME_{num}")
)
]
]
)
]
frames_layout = [ collapse(i) for i in range(1, 5) ]
scroll_layout = [
[sg.Column(frames_layout,
scrollable=True,
vertical_scroll_only=True,
expand_y=True,
key='SCROLL')]
]
main_column = [
[scroll_layout]
]
layout = [
[sg.Text('Window line 1')],
[sg.Text('Window line 2')],
[main_column],
[sg.Button(' + '), sg.Push(), sg.Button(' Exit ')]
]
# Open/close frames
opened1, opened2, opened3, opened4 = False, False, False, False
def opened(idx):
if idx == 1:
opened = opened1
elif idx == 2:
opened = opened2
elif idx == 3:
opened = opened3
elif idx == 4:
opened = opened4
window[f'OPEN_FRAME_{idx}'].update(sg.SYMBOL_DOWN if opened
else sg.SYMBOL_RIGHT)
window[f'FRAME_{idx}'].update(visible=opened)
window.refresh()
window['SCROLL'].contents_changed()
window = sg.Window('Test Window', layout,
enable_close_attempted_event=True,
location=sg.user_settings_get_entry('-location-', (None, None)),
resizable=True, finalize=True
)
cnt = 1
while True:
event, values = window.read()
print(event, values)
if event in (' Exit ', sg.WINDOW_CLOSE_ATTEMPTED_EVENT):
# Save screen location when closing window
sg.user_settings_set_entry('-location-', window.current_location())
break
if event == ' + ':
window.extend_layout(window['SCROLL'], [[sg.Text(f"Extra Text Line {cnt}")]])
cnt += 1
window.refresh()
window['SCROLL'].contents_changed()
if event.startswith('OPEN_FRAME_1'):
opened1 = not opened1
opened(1)
if event.startswith('OPEN_FRAME_2'):
opened2 = not opened2
opened(2)
if event.startswith('OPEN_FRAME_3'):
opened3 = not opened3
opened(3)
if event.startswith('OPEN_FRAME_4'):
opened4 = not opened4
opened(4)
print(sg.DEFAULT_FONT)
window.close()
I'm going to try and add all of contents of my app layout into this template, and see how it works. If it works, I may not need an open issue.
Thank you, Scott