I just tried using PySimpleGUI to prototype a simple Windows installer type program. It looks like you cannot create a window and then change the contents of it later so I create 3 separate windows up front, one for each page in the installer, and hide all but the first page. As the user clicks on the Next or Prev buttons the current window is hidden and the next window is unhidden. This seems to work okay but I'm open to ideas if there is a better way of doing this ?
The initial window appears centered on the screen. If the user moves the window and then presses the Next button then the next page will be shown in the screen center instead of where the previous page was moved to. I am reading the previous window's Location attribute and using that to move the next window but the problem is that the win.Location attribute does not appear to be updated after the user moves the window so it always returns (None, None).
Are you supposed to be able to use the Location attribute to get the current location of a window ?
My code is as follows:
import PySimpleGUI as sg
title = "Test Installer"
page_size = (400, 100)
page1 = [
[ sg.Text("Welcome to page 1 of the installer.") ],
[ sg.Text(" ") ],
[ sg.Button("Prev", disabled = True), sg.Button("Next") ]
]
page2 = [
[ sg.Text("Welcome to page 2 of the installer.") ],
[ sg.Text(" ") ],
[ sg.Button("Prev"), sg.Button("Next") ]
]
page3 = [
[ sg.Text("Welcome to page 3 of the installer.") ],
[ sg.Text("Some extra text.") ],
[ sg.Button("Prev"), sg.Button("Install") ]
]
win1 = sg.Window(title, size = page_size, keep_on_top = True).Layout(page1)
win2 = sg.Window(title, size = page_size).Layout(page2)
win3 = sg.Window(title, size = page_size).Layout(page3)
windows = (win1, win2, win3)
# create and show all windows
# the first window is created with keep_on_top=True so that we won't see the other windows before we hide them
win1.Read(timeout = 0)
for win in windows[1:]:
win.Read(timeout = 0)
win.Hide()
page = 0
win_last = None
win_location = win1.Location
while True:
print(f"Showing page: {page}")
win = windows[page]
win.keep_on_top = True
print(f"previous page win.Location: {win_location}")
print(f"current page win.Location before move: {win.Location}")
win.Move(*win_location)
print(f"current page win.Location after move: {win.Location}")
win.UnHide()
if win_last:
win_last.Hide()
event, values = win.Read()
print(f"win.Location after Read: {win.Location}")
if event is None or event == "Exit":
print("Quitting installer")
break
elif event == "Prev":
page -= 1
print(f"Moving to previous page: {page}")
elif event == "Next":
page += 1
print(f"Moving to next page: {page}")
elif event == "Install":
print("Installing software")
break
win.keep_on_top = False
win_last = win
win_location = win.Location
win.Close()
You want to use the window.CurrentLocation() method.
It doesn't appear to be in the docs. I'm really sorry about that.... adding it now.
Just checked in the change that included that one. I had updated the docs last night and didn't check them in. The Window class should be up to date now. Still have more to go on the Update methods.
The ReadMe is where you want to look for info on it.
You can change the contents of a window later, BTW.
You call the element's Update method. I'll model your behavior in an example.
Thanks for the quick update. I'll try using the window.CurrentLocation() method. Even better to hear that the contents of a window can be changed using the Update() method. Does that mean I can dispense with having separate windows per page and just have the one window ?
Oh, I forgot to post this!
import sys
if sys.version_info[0] >= 3:
import PySimpleGUI as sg
else:
import PySimpleGUI27 as sg
layout = [
[sg.Text('Welcome to page 1 of the installer', key='_LINE1_')],
[sg.Text('', size=(15,1), key='_LINE2_')],
[sg.Text('', key='_LINE3_')],
[sg.Button('Prev', key='_PREV_', disabled=True),sg.Button('Next', key='_NEXT_')],
[],
]
window = sg.Window('Window Title').Layout(layout)
screen_text = ('Welcome to page 1 of the installer',
'Welcome to page 2 of the installer',
'Welcome to page 3 of the installer',
'** Installing Your Software **')
cur_screen = 1
while True: # Event Loop
event, values = window.Read()
if event is None or event == 'Exit':
break
if event == '_NEXT_':
if cur_screen == 1:
window.Element('_PREV_').Update(disabled=False)
elif cur_screen == 2:
window.Element('_NEXT_').Update('Install')
elif cur_screen == 3:
window.Element('_LINE2_').Update('One moment....')
window.Element('_PREV_').Update('Cancel')
else:
continue
cur_screen += 1
elif event == '_PREV_':
window.Element('_LINE2_').Update('') # clear in case backing up from install
if cur_screen == 2:
window.Element('_PREV_').Update(disabled=True)
elif cur_screen == 3:
window.Element('_NEXT_').Update('Next')
elif cur_screen == 4:
window.Element('_LINE2_').Update('Cancelling....')
window.Element('_PREV_').Update('Prev')
cur_screen -= 1
window.Element('_LINE1_').Update(screen_text[cur_screen-1])
window.Close()
Thanks for the quick update. I'll try using the window.CurrentLocation() method. Even better to hear that the contents of a window can be changed using the Update() method. Does that mean I can dispense with having separate windows per page and just have the one window ?
I'm sorry about the delay. I could swear I posted that this morning but I clearly didn't.
The answer to your question about multiple windows depends on whether or not you can pre-define the elements that need to appear on the screen. If so and it's simply a matter of changing the values, then you don't need multiple windows.
Just seen your version using the Update() method. I wanted to have different elements on each screen. So screen 1 would be just an image, screen 2 would replace the image with some text and screen 3 would replace the text with a scrollable list of check boxes to select which items to install. All 3 screens would have the Prev/Next buttons at the bottom but the main content in the middle would be different for each screen.
I think this cannot be handled using Update() and i still need to have multiple windows ?
OK, then yes, multiple windows. Make sure you make a new layout for each window. You cannot 'reuse' layouts. They're individual objects so they retain state.
Okay, that's what I thought.
I have one more question. Is there a mechanism to automatically space elements horizontally ? If I wanted to have a button on the left and another button on the right the only way I can see to do it currently is to have a layout like this:
layout = [ [ sg.Button('Prev', key='_PREV_'), sg.Text(" " * 20), sg.Button('Next', key='_NEXT_') ] ]
where the text element uses a hard coded number of spaces to force the following button to the right of the window. Obviously that is quite a fragile way of doing that - if the window width is changed then it will break.
Is there a horizontal spacer element that can be used to automatically select the correct spacing ?
If you're using Qt, then you can add a "stretch" element in between and it will push them to opposite sides of the window.
For tkinter, there's no such mechanism.
You can add padding to the button so you don' t need the Text element.
This will place 600 pixels to the left side of the button
sg.Button('Next', key='_NEXT_', pad=((600,0),5))
You can also make use of columns if you want several objects to line up along an edge.
I'm using tkinter so it looks like manual spacing will be required. Not a huge problem for a simple installer where the window size will be fixed. The column idea is nice - that might be more forgiving of changing window sizes. Thanks for your help (and sorry for re-opening the issue just after you closed it :-) I'll let it stay closed this time).
You're the one that closed it, not me. :-)
Oops, I must have hit the wrong button then. Anyway, you've answered all my questions so I'll close this issue now.
Most helpful comment
You want to use the
window.CurrentLocation()method.It doesn't appear to be in the docs. I'm really sorry about that.... adding it now.