Web Analytics Made Easy - Statcounter
Skip to content

Reading Windows

There are 2 basic design patterns for PySimpleGUI windows:

  • One-shot window - no event loop
  • Persistent window - has event loop
  • Multiple simultaneous window - has event loop & uses sg.read_all_windows()

Reading a single window uses the call window.read(). Reading multiple simultaneously running windows uses the call sg.read_all_windows().

Give It a Try

The best way for you to see these design patterns is to both see the code and run it. Here are all 3 design patterns in a single Trinket.

When Windows Die - Event sg.WIN_CLOSED / sg.WINDOW_CLOSED

If the user closes the window using the "X" on the titlebar, or uses ALT+F4 on Windows then you will receive an event with the value: sg.WIN_CLOSED. An alias for this is sg.WINDOW_CLOSED. In reality, currently, this values is None. There are no guarantees that it will remain that value.... although for backward compatibility it highly likely it will always be that value. If you see older code that tests for event is None, then it's checking to see if the window has been closed. Be the programmer kinder to your future self / other programmers and use the constant sg.WIN_CLOSED rather than hard-coding None.

It's important that you always check for this event immediately after window.read calls so that you do not attempt to perform operations that have a now invalid Window object. Doing so may crash your code. You should also assume that the Values Dictionary does not contain information. It may have a value of None as well meaning attempting to use it will also result in a crash.

window.read - Read a Single Window

When you only have 1 window at a time that will be interacted with, then you can safely use the read call. If you have 2 windows visible, be sure you're reading the correct one and that your user is able to interact with the one you're reading. Events from one window will not be returned through another so if you your user is clicking buttons in a window you're not reading, nothing will appear to be happening.

Several approaches can be used to help stop confusion. For example you can use modal windows or hide the other windows that the user shouldn't be interacting with.

read_all_windows - Read All Currently Open Windows

If your have more than 1 window visible and the user need to be able to use either of them, simultaneously, the the best approach is to use the call read_all_windows.

This call returns three items instead of the normal two that window.read returns. Two of the items returns are the event and values dictionary and you normally get on a window.read call. The 3rd items is the window that the event occurred in.

A typical design pattern for multi-window reads is:

import PySimpleGUI as sg

layout1 = [  [sg.Text('Window 1')],
             [sg.Input(key='-IN-')],
             [sg.Button('Go'), sg.Exit()] ]

layout2 = [  [sg.Text('Window 2')],
             [sg.Input(key='-IN-')],
             [sg.Button('Go'), sg.Exit()] ]

window1 = sg.Window('Window 1', layout1, finalize=True, relative_location=(0,-150))
window2 = sg.Window('Window 2', layout2, finalize=True)

while True:
    window, event, values = sg.read_all_windows()
    if window is None:
        break
    if event == sg.WIN_CLOSED or event == 'Exit':
        window.close()

If you receive the value None for the window, then that means all windows have been closed.

IMPORTANT: Every window that you want to be returned from the read_all_windows call must be finalized.

Alternative Approaches

It is possible to "poll" multiple open windows, in a round-robin fashion, rather than using read_all_windows. This was the technique used prior to the read_all_windows feature addition. The downside is that it's inefficient because you are running frequently, moving from one window to another looking for events.

To use this approach, add a timeout parameter on every window.read for the windows being read. While not recommended as it can cause events to be lost in some situations, it may be that you need this pattern, so here it is:

import PySimpleGUI as sg

layout1 = [  [sg.Text('Window 1')],
             [sg.Input(key='-IN-')],
             [sg.Button('Go'), sg.Exit()] ]

layout2 = [  [sg.Text('Window 2')],
             [sg.Input(key='-IN-')],
             [sg.Button('Go'), sg.Exit()] ]

window1 = sg.Window('Window 1', layout1, relative_location=(0,-150))
window2 = sg.Window('Window 2', layout2)

while True:
    event, values = window1.read(timeout=100)
    if event == sg.WIN_CLOSED or event == 'Exit':
        window1.close()
    event, values = window2.read(timeout=100)
    if event == sg.WIN_CLOSED or event == 'Exit':
        window2.close()
    if window1.is_closed(quick_check=True) and window2.is_closed(quick_check=True):
        break

Intercepting Window Closures

If you want to do something prior to a window actually closing when the user clicks the "X", such as confirm they really want to exit the program, then you can set the parameter enable_close_attempted_event to True when creating your window.

When these events are enabled, rather than closing the window when the user clicks the "X" in the titlebar, you will receive an event with the value sg.WINDOW_CLOSE_ATTEMPTED_EVENT. The window will not be closed when this happens, even if ALT+F4 is attempted. Task Manager will close the window because it kills the entire process, not just the window.

"Async" Windows - Reading with Timeout

Asynchronous Definition in Computing

Let's first define what Asynchronous means in the context of PySimpleGUI Window read calls. In computing, a Synchronous design means you will pend (wait) until something completes. You will be blocked from taking further action until the operation completes. A classic example is reading from a hard drive. In Synchronous reads, you:

  • Call "read from disk"
  • The OS will go fetch your data
  • The function call you made returns with the data

In an Asynchronous system, rather than pending (waiting), the call will return early, allow you to do other things, and then you will check again for the data or be informed that the read has completed.

Asynchronous Window read Calls

Normally your event loop calls window.read() and your code will not return until something happens, like your user clicks a button. If you want to do something else while waiting, you need a mechanism to make that happen. One option is to use threading, but we're going to not go that route as it's complicated.

The architecture of PySimpleGUI simplifies this async capability by giving you a timeout parameter in the window.read call. If you specify a timeout, then it means:

Wait for an event to happen. If none happens withing the amount of time indicated by the timeout, then return from the read call and tell me this has happened.

When To Use? (hint - polling)

One example of when this capability is useful is if you have hardware that need to be checked on a periodic basis. Maybe you have a serial port that needs to be read every 100 milliseconds. This type of design is called "polling" and it's quite common in embedded systems.

Get an Event At Least Every X ms

The best summary for using window.read(timeout=100) is that you will receive an event at least every 100 ms. It does not mean that you will get a timeout event exactly 100 ms between each. For that kind of accuracy, you'll need to use the Window Timer capability that's new in PySimpleGUI 5.0 for tkinter. You can, however, compute the time and make adjustments to your timeout values so that you get a consistent timer.

Accurate Timing

If you need accurate timing, then you should use the time.time() system call to get the current time and compute the difference between each return from window.read. You'll find Demo Programs that show you this technique. If you do not use an accurate time source and rely on the timeouts from PySimpleGUI, then your accuracy will drift. For example, if you use a timeout of 100 ms, and your event loop takes 5 ms to complete, then each time you go through your event loop, you are taking 105 ms, not 100.

Timer Services

NEVER CALL SLEEP IN YOUR EVENT LOOP!

You should not call sleep in your event loop. Instead, use a read with a timeout or use the window.timer_start call to get an accurate timer. The reason for this is that while your program is sleeping, you are starving the underlying GUI frame work from running. Ideally, you want to allow PySimpleGUI to run as much as possible. Use the async capabilities to implement the timing code rather than using sleeps. Your GUI will perform MUCH better.

Timer API Calls

Instead of sleeping, you can request that an event be generated after some period of time. If you need to "Sleep for 3 seconds" as part of some operation, instead schedule a timer for 3 seconds. Your window.read call will return a value of sg.TIMER_KEY or sg.EVENT_TIMER (they are aliases and thus have the same value).

These are the API calls that you'll use to manage timers:

window.timer_start starts a timer

window.timer_stop stops a single timer

window.timer_stop_all stops all timers

window.timer_get_active_timers returns a list of active timer IDs

Example - start a 3 second timer that does not repeat:

window.timer_start(3000, repeating=False)       # start a 3-second timer

When this timer expires, you'll get an event sg.EVENT_TIMER. If you want to specify your own key to be returned, then use the key parameter:

window.timer_start(3000, key='-MY TIMER KEY-', repeating=False)

See the call reference for the details of each call.

Read(timeout = t, timeout_key=TIMEOUT_KEY, close=False)

This timer program is a good example using a read with a timeout. What's important in this program is that the time shown is accurate. Exactly how long the time between when the window.read call returns is not important. If it's 10 ms, 8 ms, 5 ms, or 20 ms, it doesn't matter to this code because the time shown in the timer is computed using a stable time source, a call to time.time.

read(timeout=0)

It may be tempting, and in some rare cases it's necessary to use a timeout value of 0 or some other value under 10 milliseconds. The first thing to be aware of is that doing this will consume 100% of your CPU's time unless you've got other calls in your event loop that will pend.

An example of when it could be OK to use a low value... if you've got a write call to a video display that will take 30 ms, and that call allows other things to run on your processor's core, then a low timeout value is OK.

A true non-blocking (timeout=0) read is generally reserved as a "last resort". Too many times people use non-blocking reads when a blocking read will do just fine or a read with a timeout would work.

Be a good computing citizen. Run with a non-zero timeout so that other programs on your CPU will have time to run.

Refreshing Windows

Periodically Calling read or refresh

Let's say you do end up using async reads... then you've got some housekeeping to do. It's up to you to periodically "refresh" the visible GUI. The longer you wait between updates to your GUI the more sluggish your windows will feel. It is up to you to make these calls or your GUI will appear to freeze.

refresh

If you change an element or something about a window and you want that change to appear immediately, instead of waiting until the next call to window.read, then you need to call window.refresh(). This will cause any changes you've made to appear.