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.
- These programs run for days rather than moments
- 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:
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
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.
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.
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.
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.