Web Analytics Made Easy - Statcounter
Skip to content

Exception Handling

Some applications, such as those that run in the system tray or are "Desktop Widgets" are meant to run entirely without a console. On Windows, they're often named with a .pyw extension. This is challenging in a couple of ways.

  1. These programs run for days rather than moments
  2. They have no console to output to

Debugging an intermittent problem is difficult when using plain Python for an application like these. Normally you would need to use a debugger so that the crash is caught when it happens or use a logging module that writes to disk.

With PySimpleGUI there are a number of simple ways to handle these types of errors, assuming that PySimpleGUI itself has remained functional.

Numerous popup calls are available to output information to a popup window. If you want to output a lot of information that you can also copy and paste from, then a popup_scrolled call is a good choice. However, there's an ever better technique.

The Debug Print Window

The Debug Print Window is One of the easiest ways to get the information to help you debug the problem. To output to this window, call the function sg.Print in the same way you a normal print. It accepts similar parameters including sep and end as well as some PySimpleGUI specific ones that enable printing in color.

One advantage that the Debug Print has over popups is that you can output the information using multiple calls. Each time you call sg.Print, the information will be added to output that's already in the Debug Print Window. You will be able to copy and paste from this window to another application should there be a log or some other information that you can to save. Maybe you want to loop through a list and print certain items as part of the information to display. Using sg.Print will enable you to easily do this.

Use the wait/ blocking parameter!

Be sure and set the wait parameter to True in the last call you make to sg.Print. The reason for this is that if you do not, the call will output to the window and then immediately return and your program will likely continue on and exit. Adding wait=True will output to the Debug Window and then wait for you to click the Continue button in the window before the call will return.

Example Program With Exception

This example will crash when you click the "Go" button.

import PySimpleGUI as sg

def main():
    layout = [  [sg.Text('My Window')],
                [sg.Input(key='-IN-')],
                [sg.Button('Go'), sg.Button('Exit')]  ]

    window = sg.Window('Window Title', layout)

    while True:             # Event Loop
        event, values = window.read()
        if event == sg.WIN_CLOSED or event == 'Exit':
            break
        if event == 'Go':
            bad_call()
    window.close()

if __name__ == '__main__':
    main()

If this program was launched by double clicking a .pyw file or some other technique that doesn't involve a debugger or command line, then what you would see is this:

CrashNoHandler

Your window would disappear and you would have nothing to help you understand why. This is why PySimpleGUI uses a popup window for errors that are encountered internally instead of raising an exception.

Example With Exception Handling Added

Here is the same program, but we've added a try block around the entire event loop. A total of 3 line of code were added.
1. The try statement 2. The except statement 3. The sg.Print call to output to the Debug Window

import PySimpleGUI as sg

def main():
    layout = [  [sg.Text('My Window')],
                [sg.Input(key='-IN-')],
                [sg.Button('Go'), sg.Button('Exit')]  ]

    window = sg.Window('Window Title', layout)

    try:
        while True:             # Event Loop
            event, values = window.read()
            if event == sg.WIN_CLOSED or event == 'Exit':
                break
            if event == 'Go':
                bad_call()
    except Exception as e:
        sg.Print('Exception in my event loop for the program:', sg.__file__, e, keep_on_top=True, wait=True)

    window.close()

if __name__ == '__main__':
    main()

This time the experience enables you to understand why and where your program crashed. It's clearly there's a problem with the call to bad_call

CrashWithDebugOutput

Adding Traceback and Editing Capabilities

With the Debug Print, you're able to output information about the exception which certainly helps. Because PySimpleGUI was written for you, a Python developer, there's a popup call that you can add to make your life even easier!

We'll change the exception handling portion this time to be these 2 lines of code:

        sg.Print('Exception in my event loop for the program:', sg.__file__, e, keep_on_top=True)
        sg.popup_error_with_traceback('Problem in my event loop!', e)

We're still print to the Debug Window, but we've removed the wait parameter because the popup call immediately after it will stop the program from exiting. The popup we've added is popup_error_with_traceback. This call will show you more detailed information about where the problem happened, and more conveniently, enable you to open your editor to the line of code that caused the problem. You will need to add your editor's information in the PySimpleGUI globals settings for this feature to work, so be sure and fill in this information.

Now our experience changes considerably. We get the Debug Window as before, but we also get a popup that has a "Take me to error" button that will open your editor to the line of code where you called the popup. The exception information is included in the popup because we added it when calling the popup.

This popup saves you the time of locating where on your computer the .py or .pyw file is located. Just click the button and your editor will be launched with the correct filename and you'll be taken to the line of code. You also get, for free, an attempt at humor with an unhappy looking emoji.

CrashPopupTraceback


Asynchronous Window With Periodic Update

Sync Versus Async Mode

It's possible, and even easy, to run your PySimpleGUI program in an "asynchronous" way.

What does that even mean?

There are 2 modes sync and async. When running normally (synchronous), calls are made into the GUI stay in the GUI until something happens. You call window.read() and wait for a button or some event that causes the read to return.

With async calls, you wait for an event for a certain amount of time and then you return after that amount of time if there's no event. You don't wait forever for a new event.

When running asynchronously, you are giving the illusion that multiple things are happening at the same time when in fact they are interwoven.

It means your program doesn't "block" or stop running while the user is interacting with the window. Your program continues to run and does things while the user is fiddling around.

The critical part of these async windows is to ensure that you are calling either read or refresh often enough. Just because your code is running doesn't mean you can ignore the GUI. We've all experienced what happens when a GUI program "locks up". We're shown this lovely window.

image

This happens when the GUI subsystem isn't given an opportunity to run for a long time. Adding a sleep to your event loop will cause one of these to pop up pretty quickly.

We can "cheat" a little though. Rather than being stuck inside the GUI code, we get control back, do a little bit of work, and then jump back into the GUI code. If this is done quickly enough, you don't get the ugly little "not responding" window.

Async Uses - Polling

Use this design pattern for projects that need to poll or output something on a regular basis. In this case, we're indicating we want a timeout=10 on our window.read call. This will cause the Read call to return a "timeout key" as the event every 10 milliseconds has passed without some GUI thing happening first (like the user clicking a button). The timeout key is PySimpleGUI.TIMEOUT_KEY usually written as sg.TIMEOUT_KEY in normal PySimpleGUI code.

Use caution when using windows with a timeout. You should rarely need to use a timeout=0. A zero value is a truly non-blocking call, so try not to abuse this design pattern. You shouldn't use a timeout of zero unless you're a realtime application and you know what you're doing. A zero value will consume 100% of the CPU core your code is running on. Abuse it an bad things will happen.

A note about timers... this is not a good design for a stopwatch as it can very easily drift. This would never pass for a good solution in a bit of commercial code. For better accuracy always get the actual time from a reputable source, like the operating system. Use that as what you use to measure and display the time.

image

import PySimpleGUI as sg

sg.theme('DarkBrown1')

layout = [  [sg.Text('Stopwatch', size=(20, 2), justification='center')],
            [sg.Text(size=(10, 2), font=('Helvetica', 20), justification='center', key='-OUTPUT-')],
            [sg.T(' ' * 5), sg.Button('Start/Stop', focus=True), sg.Quit()]]

window = sg.Window('Stopwatch Timer', layout)

timer_running, counter = True, 0

while True:                                 # Event Loop
    event, values = window.read(timeout=10) # Please try and use as high of a timeout value as you can
    if event in (sg.WIN_CLOSED, 'Quit'):             # if user closed the window using X or clicked Quit button
        break
    elif event == 'Start/Stop':
        timer_running = not timer_running
    if timer_running:
        window['-OUTPUT-'].update('{:02d}:{:02d}.{:02d}'.format((counter // 100) // 60, (counter // 100) % 60, counter % 100))
        counter += 1
window.close()

The focus parameter for the Button causes the window to start with that button having focus. This will allow you to press the return key or spacebar to control the button.