diff --git a/DemoPrograms/Demo_Multithreaded_Delegate_Appear_To_Make_PSG_Calls_From_Thread.py b/DemoPrograms/Demo_Multithreaded_Delegate_Appear_To_Make_PSG_Calls_From_Thread.py new file mode 100644 index 00000000..8b49cb39 --- /dev/null +++ b/DemoPrograms/Demo_Multithreaded_Delegate_Appear_To_Make_PSG_Calls_From_Thread.py @@ -0,0 +1,145 @@ +import PySimpleGUI as sg +import time +import threading + +""" + Demo - Multi-threaded - Show Windows and perform other PySimpleGUI calls in what appread to be from a thread + + Just so that it's clear, you CANNOT make PySimpleGUI calls directly from a thread. There is ONE exception to this + rule. A thread may call window.write_event_values which enables it to communicate to a window through the window.read calls. + + The main GUI will not be visible on your screen nor on your taskbar despite running in the background. The calls you + make, such as popup, or even Window.read will create windows that your user will see. + + The basic function that you'll use in your thread has this format: + make_delegate_call(lambda: sg.popup('This is a popup', i, auto_close=True, auto_close_duration=2, keep_on_top=True, non_blocking=True)) + + Everything after the "lambda" looks exactly like a PySimpleGUI call. + If you want to display an entire window, then the suggestion is to put it into a function and pass the function to make_delegate_call + + Note - the behavior of variables may be a bit of a surprise as they are not evaluated until the mainthread processes the event. This means + in the example below that the counter variable being passed to the popup will not appear to be counting correctly. This is because the + value shown will be the value at the time the popup is DISPLAYED, not the value when the make_delegate_call was made. + + Copyright 2022 PySimpleGUI +""" + + +# Design decision was to make the window a global. You can just as easily pass it to your function after initizing your window +# But there becomes a problem then of wheere do you place the thread startup code. Using this global decouples them so that +# the thread is not started in the function that makes and executes the GUI + +window:sg.Window = None + +# M""MMMM""M +# M. `MM' .M +# MM. .MM .d8888b. dP dP 88d888b. +# MMMb dMMM 88' `88 88 88 88' `88 +# MMMM MMMM 88. .88 88. .88 88 +# MMMM MMMM `88888P' `88888P' dP +# MMMMMMMMMM +# +# M""""""""M dP dP +# Mmmm mmmM 88 88 +# MMMM MMMM 88d888b. 88d888b. .d8888b. .d8888b. .d888b88 +# MMMM MMMM 88' `88 88' `88 88ooood8 88' `88 88' `88 +# MMMM MMMM 88 88 88 88. ... 88. .88 88. .88 +# MMMM MMMM dP dP dP `88888P' `88888P8 `88888P8 +# MMMMMMMMMM + +def the_thread(): + """ + This is code that is unique to your application. It wants to "make calls to PySimpleGUI", but it cannot directly do so. + Instead it will send the request to make the call to the mainthread that is running the GUI. + + :return: + """ + + # loop 10 times, each time making 2 different popup calls that indicate they should autoclose and not block the main GUI + for i in range(10): + time.sleep(.2) + make_delegate_call(lambda: sg.popup('This is a popup', i, auto_close=True, auto_close_duration=2, keep_on_top=True, non_blocking=True)) + make_delegate_call(lambda: sg.popup_scrolled(__file__, sg.get_versions(), auto_close=True, auto_close_duration=1.5, non_blocking=True)) + + # when finished and ready to stop, tell the main GUI to exit + window.write_event_value('-THREAD EXIT-', None) + + +# -------------------------------------------------------------------------------------------------------- # + +# The remainder of the code is part of the overall design pattern. You should copy this code +# and use it as the basis for creating this time of delegated PySimpleGUI calls + + +# M""""""'YMM oo +# M mmmm. `M +# M MMMMM M .d8888b. .d8888b. dP .d8888b. 88d888b. +# M MMMMM M 88ooood8 Y8ooooo. 88 88' `88 88' `88 +# M MMMM' .M 88. ... 88 88 88. .88 88 88 +# M .MM `88888P' `88888P' dP `8888P88 dP dP +# MMMMMMMMMMM .88 +# d8888P +# MM"""""""`YM dP dP +# MM mmmmm M 88 88 +# M' .M .d8888b. d8888P d8888P .d8888b. 88d888b. 88d888b. +# MM MMMMMMMM 88' `88 88 88 88ooood8 88' `88 88' `88 +# MM MMMMMMMM 88. .88 88 88 88. ... 88 88 88 +# MM MMMMMMMM `88888P8 dP dP `88888P' dP dP dP +# MMMMMMMMMMMM + +def make_delegate_call(func): + """ + Make a deletegate call to PySimpleGUI. + + :param func: A lambda expression most likely. It's a function that will be called by the mainthread that's executing the GUI + :return: + """ + if window is not None: + window.write_event_value('-THREAD DELEGATE-', func) + + +# oo +# +# 88d8b.d8b. .d8888b. dP 88d888b. +# 88'`88'`88 88' `88 88 88' `88 +# 88 88 88 88. .88 88 88 88 +# dP dP dP `88888P8 dP dP dP + +def main(): + global window + + # create a window. A key is needed so that the values dictionary will return the thread's value as a key + layout = [[sg.Text('Window will not be visible', k='-T-')]] + + # set the window to be both invisible and have no taskbar icon + window = sg.Window('Invisible window', layout, no_titlebar=True, alpha_channel=0) + + while True: + event, values = window.read() + if event in ('Exit', sg.WIN_CLOSED): + break + # if the event is from the thread, then the value is the function that should be called + if event == '-THREAD DELEGATE-': + try: + values[event]() + except Exception as e: + sg.popup_error_with_traceback('Error calling your function passed to GUI', event, values, e) + elif event == '-THREAD EXIT-': + break + window.close() + + +# MP""""""`MM dP dP +# M mmmmm..M 88 88 +# M. `YM d8888P .d8888b. 88d888b. d8888P dP dP 88d888b. +# MMMMMMM. M 88 88' `88 88' `88 88 88 88 88' `88 +# M. .MMM' M 88 88. .88 88 88 88. .88 88. .88 +# Mb. .dM dP `88888P8 dP dP `88888P' 88Y888P' +# MMMMMMMMMMM 88 +# dP + +if __name__ == '__main__': + # first your thread will be started + threading.Thread(target=the_thread, daemon=True).start() + # then startup the main GUI + main()