Sep-30-2021, 07:57 PM
(This post was last modified: Oct-01-2021, 09:44 PM by deanhystad.)
It is hard to be certain since I don't have your database, but the program flow seems awkward to me.
First off I think it odd to have a login screen appear when the program starts. Normally if you require a login, the login is a control at the top of the screen (along with a logout), and operations are enabled/disabled by the login status. This would eliminate the Guest Login as that would be the reduced capability when not logged in. I would change the program so it opens to the guest window. The guest window has a login button near the top right corner. Clicking that opens a login popup. If the login is successful you can erase the guest window and replace with the admin main window. This window has a logout button near the top right corner. Clicking that closes the admin main window and draws the guest window.
User equipment is odd. To do a return you go to the Equipment tab and press the select button. This draws another screen that has lists of equipment checked out by the user. For some reason the list is split inti equipment type and serial number. On this page you can select a serial number and press the return button to return the item.
I think it would be much more natural to have a screen that displays he checked out equipment tickets. The entire ticket (is it a ticket or is it called something else? checkout?), not just the user id. At least the user id, equipment type and serial number so you can select a ticket/checkout without having to click on the user id to display additional info. There should be a way to filter tickets by user ID and equipment type. These should not require an additional window, only updating the tickets displayed in the equipment tab list. There should be a button on this page for returning the equipment. Press this button and it might pop up a dialog to complete the return. When the dialog is filled out, the ticket is removed from the database and all tabs should automatically update their displays. There should not be any refresh buttons anywhere.
Essentially the interface should be arranged such that everything you need to complete an activity is visible in one place and you don't have to move between screens. Confirmation screens are the one exception, but those should be modal dialogs that appear over (but don't completely occlude) the main window.
This may make more sense:
First off I think it odd to have a login screen appear when the program starts. Normally if you require a login, the login is a control at the top of the screen (along with a logout), and operations are enabled/disabled by the login status. This would eliminate the Guest Login as that would be the reduced capability when not logged in. I would change the program so it opens to the guest window. The guest window has a login button near the top right corner. Clicking that opens a login popup. If the login is successful you can erase the guest window and replace with the admin main window. This window has a logout button near the top right corner. Clicking that closes the admin main window and draws the guest window.
User equipment is odd. To do a return you go to the Equipment tab and press the select button. This draws another screen that has lists of equipment checked out by the user. For some reason the list is split inti equipment type and serial number. On this page you can select a serial number and press the return button to return the item.
I think it would be much more natural to have a screen that displays he checked out equipment tickets. The entire ticket (is it a ticket or is it called something else? checkout?), not just the user id. At least the user id, equipment type and serial number so you can select a ticket/checkout without having to click on the user id to display additional info. There should be a way to filter tickets by user ID and equipment type. These should not require an additional window, only updating the tickets displayed in the equipment tab list. There should be a button on this page for returning the equipment. Press this button and it might pop up a dialog to complete the return. When the dialog is filled out, the ticket is removed from the database and all tabs should automatically update their displays. There should not be any refresh buttons anywhere.
Essentially the interface should be arranged such that everything you need to complete an activity is visible in one place and you don't have to move between screens. Confirmation screens are the one exception, but those should be modal dialogs that appear over (but don't completely occlude) the main window.
This may make more sense:
import PySimpleGUI as sg saved_location = (None, None) # Remember where window was when closed class Shutdown(Exception): '''Custom exception for exiting this program''' def show_window(title, layout, parent=None, **kwargs): '''Window create convenience function. Position window on top of parent if provided, else restore saved location ''' location = (x+70 for x in parent.current_location()) if parent else saved_location return sg.Window(title, layout, finalize=True, enable_close_attempted_event=True, location=location, **kwargs) def close_window(window): '''Window close convenience function. Saves window location''' global saved_location saved_location = window.current_location() window.close() def verify_quit(parent): '''Verify that you want to quit the application. Returns True if user wamts to quit''' window = show_window("Verify Quit", [ [sg.T("Are you sure you want to quit. Press 'Yes' to quit")], [sg.Button("Yes", size=10, key="-QUIT_YES-"), sg.Button("No", size=10, key="-QUIT_NO-")]], modal=True, parent=parent) while True: event, values = window.read() if event == sg.WIN_CLOSED: break elif event in (sg.WINDOW_CLOSE_ATTEMPTED_EVENT, "-QUIT_NO-", "-QUIT_YES-"): window.close() # Do not save location of this window return event == "-QUIT_YES-" return False def login(parent): '''Dialog window for logging into admin level''' accounts = {"Admin":"Administrator", "User":"ABC"} window = show_window("Login", [ [sg.T("Username", size=8), sg.InputText(key = "-LOGIN_USER-", size=20)], [sg.T("Password", size=8), sg.InputText(key = "-LOGIN_PASSWORD-", size=20, password_char='*')], [sg.Button("Enter", size=10, key="-LOGIN_ENTER-"), sg.Button("Cancel", size=10, key="-LOGIN_CANCEL-")]], modal=True, parent=parent) while True: event, values = window.read() if event == sg.WIN_CLOSED: break elif event in (sg.WINDOW_CLOSE_ATTEMPTED_EVENT, "-LOGIN_CANCEL-"): window.close() # Do not save location of this window elif event == "-LOGIN_ENTER-": if accounts.get(values['-LOGIN_USER-']) == values['-LOGIN_PASSWORD-']: window.close() # Do not save location of this window return True sg.popup('Invalid user id or password') return False def guest_main(): '''Reduced access window''' window = show_window('Guest Portal', [ [sg.T("Topic:", size=15), sg.Combo(values = ("Office Products", "Email", "Hardware", "Other"), key='-GUEST_TOPIC-'), \ sg.T("", expand_x=True), sg.B('Login', border_width=0, key='-GUEST_LOGIN-')], [sg.T("Issue Description:", size=15), sg.Multiline(size = (45,5), expand_x=True, key="-GUEST_DESCRIPTION-")], [sg.T("Email Address:", size=15), sg.InputText(expand_x=True, key="-GUEST_EMAIL-")], [sg.T("")], [sg.B("Submit Ticket", key='-GUEST_SUBMIT'), sg.T("Please include as much information as you can.")]]) window['-GUEST_LOGIN-'].update(button_color=window.BackgroundColor) # Blend login button while True: event, values = window.read() if event == sg.WIN_CLOSED: break elif event == sg.WINDOW_CLOSE_ATTEMPTED_EVENT: if verify_quit(window): close_window(window) raise Shutdown() elif event == '-GUEST_LOGIN-': if login(window): # Return True to indicate successful login close_window(window) return True return False def admin_main(): '''Full access window. Closing window logs out of admin access''' window = show_window('Admin', [ [sg.T("Topic:", size=15), sg.Combo(values = ("Office Products", "Email", "Hardware", "Other"), key='-ADMIN_TOPIC-'), \ sg.T("", expand_x=True), sg.B('Logout', border_width=0, key='-ADMIN_LOGOUT-')], [sg.T("Issue Description:", size=15), sg.Multiline(size = (45,5), expand_x=True, key="-ADMIN_DESCRIPTION-")], [sg.T("Email Address:", size=15), sg.InputText(expand_x=True, key="-ADMIN_EMAIL-")], [sg.T("")], [sg.B("Submit Ticket", key='-ADIMIN-SUBMIT'), sg.T("Please include as much information as you can.")]]) window['-ADMIN_LOGOUT-'].update(button_color=window.BackgroundColor) # Blend logout button while True: event, values = window.read() if event == sg.WIN_CLOSED: break elif event in (sg.WINDOW_CLOSE_ATTEMPTED_EVENT, '-ADMIN_LOGOUT-'): close_window(window) return False # Handle Guest/Admin. Start as guest try: logged_in = False while True: logged_in = admin_main() if logged_in else guest_main() except Shutdown: print('Shutting down') # Replace with cleanup codeNotice the show_window(), close_window() convenience functions? When you see the same lines of code repeated over and over and over and over and over you should make it a function. Even if the code is only repeated 2-3 times it is worth writing a function. It reduces the overall amount of code. It documents that this code is related and performs a well defined task. It puts all the code in one place so you only have to fix bugs once. I cannot emphasize enough how important it is for you to stop using copy/paste when writing code.