Sep-23-2021, 12:57 PM
(This post was last modified: Sep-23-2021, 09:04 PM by deanhystad.)
I played around with your code. It is an, um, er, interesting approach. I would never have come up with the idea of destroying and re-creating windows just to update the widgets. The top row of buttons is also an unusual way to present different views.
Here are some ideas you may find interesting. I modified the code to remove recursion.
Recursion is when a function calls itself, either directly or indirectly through other functions. Your code has a lot of recursion. Functions call themselves to update the window or call functions to draw other windows that may in turn call the original function. This is a bad thing because it eats up memory. It is so bad that Python places an upper limit on how many levels of function calls are allowed. If you program reaches that limit, an exception is thrown.
To remove the recursion I rewrote the code to return control to a window switcher which calls the function to draw the next window. Instead of login() calling main(), login() now sets a variable and returns. The window switcher.
I changed the way window location was saved and restored. I have the window send an event if you click on the close window decoration. In the event handler I capture that event, save the window location, and close the window. I save the window location in a PySimpleGUI user setting named '-WINDOW_LOCATION-', and when the new window is drawn it uses the user setting to set the location of the new window.
I rewrote the login window to allow multiple login attempts without having to redraw the window. If the user enters in invalid username or password it clears the inputs, displays a message in a popup, and sets focus back to the user input.
Clearing the inputs is easy. You can set a flag to automatically clear an input when it is read, and I did that in the layout
Here are some ideas you may find interesting. I modified the code to remove recursion.
Recursion is when a function calls itself, either directly or indirectly through other functions. Your code has a lot of recursion. Functions call themselves to update the window or call functions to draw other windows that may in turn call the original function. This is a bad thing because it eats up memory. It is so bad that Python places an upper limit on how many levels of function calls are allowed. If you program reaches that limit, an exception is thrown.
To remove the recursion I rewrote the code to return control to a window switcher which calls the function to draw the next window. Instead of login() calling main(), login() now sets a variable and returns. The window switcher.
switch_window(login) # Start with login window while next_window: next_window() # execute function to draw next windowTo switch from the current window to a new window, the window event loop assigns "next_window" to be the function that draws the next window, then the current window exits its event loop and lets control return to the window switcher loop. It uses a couple of convenience functions to help with this.
def switch_window(window_func): '''Close current window and set function to open new window''' global next_window, window_open next_window = window_func window_open = False def show_window(title, layout): '''Create new window using layout. Position window in same location as current window''' global window, window_open if window: window.close() window = sg.Window(title, layout, size=(600, 500), enable_close_attempted_event=True, location=sg.user_settings_get_entry('-WINDOW_LOCATION-', (None, None))) window_open = Trueswitch_window() sets the next_window function variable and sets window_open to False, causing the event loop to exit. show_window() draws the new window.
I changed the way window location was saved and restored. I have the window send an event if you click on the close window decoration. In the event handler I capture that event, save the window location, and close the window. I save the window location in a PySimpleGUI user setting named '-WINDOW_LOCATION-', and when the new window is drawn it uses the user setting to set the location of the new window.
I rewrote the login window to allow multiple login attempts without having to redraw the window. If the user enters in invalid username or password it clears the inputs, displays a message in a popup, and sets focus back to the user input.
Clearing the inputs is easy. You can set a flag to automatically clear an input when it is read, and I did that in the layout
layout = [ [sg.T("Username")], [sg.InputText(key = "-USER-", do_not_clear=False)], [sg.T("Password")], [sg.InputText(key = "-PASSWORD-", do_not_clear=False)], [sg.Button("OK", bind_return_key=True, key="-OK-")], ]Setting the focus back to the user input was a bit trickier. I had to call the set_focus() method for the user input.
window['-USER-'].set_focus() Give keyboard focus to the user input widgetThe entire program:
import PySimpleGUI as sg accounts = {"Admin":"Administrator", "User":"OneTwoThree"} # use database or dictionary for passwords. Easier to use than lists of lists. window = None # Variables used by the window switcher window_open = False next_window = None def switch_window(window_func): '''Close current window and set function to open new window''' global next_window, window_open next_window = window_func window_open = False def show_window(title, layout): '''Create new window using layout. Position window in same location as current window''' global window, window_open if window: window.close() window = sg.Window(title, layout, size=(600, 500), enable_close_attempted_event=True, location=sg.user_settings_get_entry('-WINDOW_LOCATION-', (None, None))) window_open = True def main_window_menu(event, values): '''Process events common to all windows''' if event == sg.WINDOW_CLOSE_ATTEMPTED_EVENT: sg.user_settings_set_entry('-WINDOW_LOCATION-', window.current_location()) # Save window location before closing window.close() if event == sg.WIN_CLOSED: switch_window(login) if event == "User List": switch_window(user_list) if event == "View Logged Equipment": switch_window(user_equip) def login(): '''User login window''' layout = [ [sg.T("Username")], [sg.InputText(key = "-USER-", do_not_clear=False)], [sg.T("Password")], [sg.InputText(key = "-PASSWORD-", do_not_clear=False)], [sg.Button("OK", bind_return_key=True, key="-OK-")], ] show_window("Login", layout) while window and window_open: event, values = window.read() # window.read() belongs inside while loop if event == sg.WINDOW_CLOSE_ATTEMPTED_EVENT: window.close() if event == sg.WIN_CLOSED: switch_window(None) if event == "-OK-": if accounts.get(values['-USER-']) == values['-PASSWORD-']: switch_window(user_list) else: sg.popup('Invalid Login.','Please try again.') window['-USER-'].set_focus() def user_list(): '''Some window that does something''' layout = [ [sg.B("View Logged Equipment"), sg.B("User List")], [sg.B("User List Push Me", bind_return_key=True, key='-PUSH_ME-')] ] show_window("User List", layout) while window and window_open: event, values = window.read() if event == "-PUSH_ME-": # Do event handling specific to this window first print('In user list window') else: main_window_menu(event, values) # Handle events common to many windows (the top buttons) def user_equip(): '''Some window that does different things''' layout = [ [sg.B("View Logged Equipment"), sg.B("User List")], [sg.B("User Equipment Push Me", bind_return_key=True, key='-PUSH_ME-')] ] show_window("User Equipment", layout) while window and window_open: event, values = window.read() if event == "-PUSH_ME-": print('In user equipment window') else: main_window_menu(event, values) switch_window(login) # Start with login window while next_window: next_window() # execute function to draw next windowI do not think this is the right way to write this program. I would make a tabbed window and create tabs for Logged Equipment, User List, Log Equipment Out, Open Tickets and Open Requests. Instead of destroying and re-creating the windows, the event loop would update the existing widgets to display new information. The resulting program would maybe be 200 lines long to do everything your program currently does.