Question
Ubuntu 18.04.5 LTS x86_64
Python 3.6.9
tkinter. 4.30.0 Released 15-Oct-2020
4y Python programming experience
7y Programming experience overall
no Have used another Python GUI Framework (tkinter, Qt, etc) previously (yes/no is fine)?
What is the correct way to read events when using multiple (visible aka finalize=True) windows and using threads?
Is read_all_windows compatible with write_event_value? If I try to read_all_windows when I send write_event_values from multiple threads, some events aren't read (but are in the thread_queue).
#!/usr/bin/env python
import PySimpleGUI as sg
from threading import Thread, Event
LOADING = b'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'
class LoadingThread(Thread):
def __init__(self, event, window, startgif=False):
Thread.__init__(self)
self.stopped = event
self.window = window
self.startgif = startgif
self.daemon = True
def run(self):
while not self.stopped.is_set():
self.window.write_event_value('GIF', self.startgif)
print("GIFThread write_event_value(GIF)")
print("GIFThread write_event_value(GIFSTOP)")
self.window.write_event_value('GIFSTOP', 'test')
print("GIFThread dying")
mwind = sg.Window('Gif',
[[sg.Button('Start Gif', key='-START-'), sg.Button('Stop Gif', key='-STOP-')]],
finalize=True)
auxwind = sg.Window('Aux',
[[sg.OK(key='-OK-'), sg.Cancel(key='-CANCEL-')]],
finalize=True)
stopFlag = Event()
gif_thread = LoadingThread(stopFlag, mwind, startgif=True)
while True:
window, event, values = sg.read_all_windows()
print(f"read: {window.Title} {event}")
if window == sg.WIN_CLOSED or event == sg.WIN_CLOSED:
break
if window == mwind:
if event == '-START-':
gif_thread.start()
if event == 'GIF':
sg.popup_animated(LOADING, no_titlebar=False,
background_color='white',
time_between_frames=45)
if event == 'GIFSTOP':
sg.popup_animated(None)
if event == '-STOP-':
stopFlag.set()
if window == auxwind:
if event == '-OK-':
mwind.write_event_value('testevent', True)
if event == '-CANCEL-':
mwind.write_event_value('testevent', False)
print("ending main loop iteration")
read: Gif -START-
write_event_value called GIF:True, queue size before: 0
ending main loop iteration
write_event_value called GIF:True, queue size after: 1
read: Gif GIF
...
GIFThread write_event_value(GIF)
write_event_value called GIF:True, queue size before: 0
write_event_value called GIF:True, queue size after: 1
ending main loop iteration
read: Aux -OK-
write_event_value called testevent:True, queue size before: 1
write_event_value called testevent:True, queue size after: 2
ending main loop iteration
read: Gif GIF
...
GIFThread write_event_value(GIF)
write_event_value called GIF:True, queue size before: 1
write_event_value called GIF:True, queue size after: 2
ending main loop iteration
read: Gif -STOP-
ending main loop iteration
read: Gif GIF
GIFThread write_event_value(GIF)
GIFThread write_event_value(GIFSTOP)
write_event_value called GIFSTOP:test, queue size before: 1
write_event_value called GIFSTOP:test, queue size after: 2
ending main loop iteration
read: Gif GIF
GIFThread dying
ending main loop iteration
^CTraceback (most recent call last):
File "multithreadgif.py", line 39, in
window, event, values = sg.read_all_windows()
File "/home/schica/auditor/sergio/autoauditor/temp/test/lib/python3.6/site-packages/PySimpleGUI/PySimpleGUI.py", line 9060, in read_all_windows
Window._root_running_mainloop.mainloop()
File "/usr/lib/python3.6/tkinter/__init__.py", line 1283, in mainloop
self.tk.mainloop(n)
KeyboardInterrupt
I added a print before and after self.thread_queue.put(item=(key, value)) in the write_event_value function.
The execution fails when I press OK button in Aux window, the queue now has two value and 1 is never read.


When using write_event_values and read_all_windows, tkinter is doing all of the event handling in terms of tracking the events and if a user's code is posted.
I could see a potential of a missed event depending on how tkinter handles race conditions. There's a tiny bit of cheating happening with write_enent_value. That function makes a tkinter call strvar.set. It was my understanding that this call is the only thread-safe call available. It's why I'm able to utilize tkinter to manage the inter-thread communication in terms of signaling that something is there to process.
Ther previous way thread communications happend was that the user runs an event loop with a timer. This allows for polling of a thread queue. That same queue exists when calling write_event_value, except that PySimpleGUI is managing the queue for you.
The basic operation is simiple:
PySimpleGUI is an odd duck when it comes to how it uses these packages. I'm in a constant state of entering and hard-exiting event loops, with the expectation that tkinter and the others will properly manage their event queues regardless of whether or not they have control of the CPU at the time.
Normally, a user jumps into the GUI's event loop and never exits that loop until the program ends. Not so with PySimpleGUI. After every event, the entire event loop is stopped, the user gets control back, and then when window.read is called again, the event loop goes on its mary way.
Something appears to be losing a count which I believe would point in the direction of tkinter perhaps losing that strvar.set event. One way to check this out would be to add a timeout to the read call. Normally this timeout will never get hit if the events come along at a fast enough pace to keep ahead of the timer. This is a bit of a keep-alive timer. If you hit it, then you know an event got missed.
Still, it's surprising that there would be a missed event, even with the starting and stopping of the queue. I don't see where any PySimpleGUI/tkinter calls are being made from your thread other than write_event_value. It may be that I've got it wrong that strvar.set is a safe call to make from a thread.
Thanks,
I was looking the code trying to debug the race condition and noticed a function write_multi_window_event_value and _queued_multi_read_event_avilable (typo here), but they are not used in the code. Did you try to do the same as write_event_value and _queued_thread_event_available using callbacks when a new item is in the queue? What's the purpose of that functions?
I don't see specific info about the multi-window support of using write_event_value. I thought that it is supported and that the only part of PySimpleGUI that was not yet supported was the system tray interface. But, I'm starting to wonder if that's true or not. It does seem suspect that there is a function to do a multi-window write_event_value, but I think it may be that I discovered it wasn't needed. I don't believe I would have abandoned the development until the multi-window stuff was done.
If it was to be supported, then I would have tested it. Normally these kinds of tests end up as demo programs. I did find a test program that contains both a write_event_value and read_all_windows which means I thought it should work. I'm pretty sure I would have posted something about being cautious if it was not something that was going to work, like I was with the System Tray.
At the bottom is the code I found that has both a read all windows and a write event value. I just located it and ran it one time to make sure it did something useful and not crash. I thought it would be good to go ahead and post this right away since you're actively looking at the problem.
One thing I have not looked at and should have is the context that the callbacks are running within. My assumption has always been that callbacks will always occur from the mainthread. The reason I think this is that the architecture, as I understand it, is that callbacks occur from tkinter's event loop. I assume that setting a strvar will tell the event loop that a callback should occur, and that the code that sets the strvar only queues the event. If this was not the case, then this mechanism of signaling would not be threadsafe. As I understand it, this strvar call is the only threadsafe way of making a tkinter call from a thread.
The write_event_value function is trivial. The primary things it does are:
self.thread_queue.put(item=(key, value))
self.thread_strvar.set('new item')
These 2 calls add to the Queue and set the strvar that should eventually lead to a callback.
I'll add some test code to check what the context is for the callback.
If the callback context is ever the thread, then it's game over for this design and I'm going to have to rethink how to go about this. The callback code is most certainly not threadsafe as it makes a ton of tkinter calls and would likely result in all sorts of bad behavior.
Here is my code that tested the write_event_value along with a multi-window read.
import threading
import time
import PySimpleGUI as sg
"""
Threaded Demo - Uses Window.write_event_value communications
Requires PySimpleGUI.py version 4.25.0 and later
This is a really important demo to understand if you're going to be using multithreading in PySimpleGUI.
Older mechanisms for multi-threading in PySimpleGUI relied on polling of a queue. The management of a communications
queue is now performed internally to PySimpleGUI.
The importance of using the new window.write_event_value call cannot be emphasized enough. It will hav a HUGE impact, in
a positive way, on your code to move to this mechanism as your code will simply "pend" waiting for an event rather than polling.
Copyright 2020 PySimpleGUI.org
"""
THREAD_EVENT = '-THREAD-'
cp = sg.cprint
def the_thread(window):
"""
The thread that communicates with the application through the window's events.
Once a second wakes and sends a new event and associated value to the window
"""
i = 0
while True:
time.sleep(1)
window.write_event_value('-THREAD-', (threading.current_thread().name, i)) # Data sent is a tuple of thread name and counter
window.write_event_value('-PROGRESS-', i) # Data sent is a tuple of thread name and counter
# cp('This is cheating from the thread', c='white on green')
i += 1
def make_progbar_window():
layout = [[sg.Text('Progress Bar')],
[sg.ProgressBar(10, orientation='h', size=(15,20), k='-PROG-')]]
return sg.Window('Progress Bar', layout, finalize=True, location=(800,800))
def main():
"""
The demo will display in the multiline info about the event and values dictionary as it is being
returned from window.read()
Every time "Start" is clicked a new thread is started
Try clicking "Dummy" to see that the window is active while the thread stuff is happening in the background
"""
layout = [ [sg.Text('Output Area - cprint\'s route to here', font='Any 15')],
[sg.Multiline(size=(65,20), key='-ML-', autoscroll=True, reroute_stdout=False, write_only=True, reroute_cprint=True)],
[sg.T('Input so you can see data in your dictionary')],
[sg.Input(key='-IN-', size=(30,1))],
[sg.B('Start A Thread'), sg.B('Dummy'), sg.Button('Exit')] ]
window1 = sg.Window('Window Main', layout, finalize=True, return_keyboard_events=True)
window_prog = make_progbar_window()
while True: # Event Loop
# window = window1
# event, values = window.read()
window, event, values = sg.read_all_windows()
print(window.Title, event, values)
if event == sg.WIN_CLOSED or event == 'Exit':
break
if event.startswith('Start'):
threading.Thread(target=the_thread, args=(window,), daemon=True).start()
if event == THREAD_EVENT:
cp(f'Data from the thread ', colors='white on purple', end='')
cp(f'{values[THREAD_EVENT]}', colors='white on red')
if event == '-PROGRESS-':
window_prog['-PROG-'].update(values[event]%10 + 1) # type: sg.ProgressBar.update()
window.close()
if __name__ == '__main__':
main()
It does appear that the callback for a write_event_value strvar event is coming from the mainthread. I added a stack trace to the function _window_tkvar_changed_callback. This is the function that is called when the strvar is set. This value is the one set from the write_event_value function as I previously posted - self.thread_strvar.set('new item').
Here is the stack trace for the call to the callback:
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_1099.py", line 87, in <module>
the_gui()
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_1099.py", line 59, in the_gui
event, values = window.read(timeout=None)
File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 7700, in Read
results = self._read(timeout=timeout, timeout_key=timeout_key)
File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 7810, in _read
Window._root_running_mainloop.mainloop()
File "C:\Python\Anaconda3\lib\tkinter\__init__.py", line 1282, in mainloop
self.tk.mainloop(n)
File "C:\Python\Anaconda3\lib\tkinter\__init__.py", line 1704, in __call__
return self.func(*args)
File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 8867, in _window_tkvar_changed_callback
trace_details = traceback.format_stack()
What this shows is that the user's call to window.read is ultimately leading to the callback routine which is exactly what we want to see happen. It would have been very bad to see the thread's stack trace instead.
Here's the changed code for the callback function.
def _window_tkvar_changed_callback(self, event, *args):
"""
Internal callback function for when the thread
:param event: Information from tkinter about the callback
"""
print('Thread callback info')
print(event)
trace_details = traceback.format_stack()
print(''.join(trace_details))
if self._queued_thread_event_available():
self.FormRemainedOpen = True
# if self.CurrentlyRunningMainloop:
# self.TKroot.quit() # kick the users out of the mainloop
_exit_mainloop(self)
The callback is quite simple just as the write_event_value function is. The queue is checked to see if it has any items. If it does, then tkinter is told to exit the mainloop, which is what causes your call to window.read() to return.
A temporary workaround.
If you're looking for a way to get past this problem, you can do so by adding a timeout to your read call. This timeout will be your missed event trigger, assuming your events are coming in on a frequent basis.
When you get a timeout, you can call the function:
window._queued_thread_event_read()
This function will return None if the queue is empty. If there is an event waiting, then the event & value pair is returned as a tuple (event, value).
It's not an ideal kind of solution, but it will get you out of the problem of there being a missing tkinter event.
Caution is advised however, as there is a race condition that you could cause by doing this. If there is an event posted to the queue along with the tkinter and then your timeout event happens, then it's possible for you to steal the queued item. When the callback happens due to the strvar being set, there will be nothing in the queue to read.
I don't believe this race condition will be a problem because as you can see in the callback code posted above, if the queue is empty when one of these strvar event callbacks happen, then it simply ignores the callback and will not exit the mainloop.
Thank you very much.
I have checked again my code trying to mimir your demo, to discard some cases tried sending GIFSTOP event two consecutive times, and the main loop read it, so I started looking at the read_all_windows, using breakpoints to know when it stops, and found that Window._root_running_mainloop.mainloop() is blocking the execution, fine, we're not missing events, it just block and doesn't continue with the remaining events.
I have used your demo program to test it (however, changed to loop to only 10 executions). I added a new write_event_value in the dummy button, and you can see how the multiline stops printing 1 line for every button press.
while True: # Event Loop
# window = window1
# event, values = window.read()
window, event, values = sg.read_all_windows()
print(window.Title, event, values)
if event == sg.WIN_CLOSED or event == 'Exit':
break
if event.startswith('Start'):
threading.Thread(target=the_thread, args=(window,), daemon=True).start()
if event == THREAD_EVENT:
cp(f'Data from the thread ', colors='white on purple', end='')
cp(f'{values[THREAD_EVENT]}', colors='white on red')
if event == '-PROGRESS-':
window_prog['-PROG-'].update(values[event]%10 + 1) # type: sg.ProgressBar.update()
if event == 'Dummy':
window.write_event_value('-DUMMY-', 'pressed')
if event == '-DUMMY-':
cp("Dummy pressed")
window.close()

Edit: We were typing at the same time, haven't read your update.
What conclusion did you come to from your test?
I do see a potential problem, at least in the test.
The write_event_value, when called from the mainthread, does not wait until the window.read() happens. tkinter is calling the callback immediately. This will cause a missed event because tkinter will not make the callback due to the read_all_windows call because it's already happened when the event was posted.
Here is the stack trace that I get from the callback routine if the maintrhead makes the write_event_value.
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_999.py", line 88, in <module>
main()
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_999.py", line 80, in main
window.write_event_value('-DUMMY-', 'pressed')
File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 8905, in write_event_value
self.thread_strvar.set('new item')
File "C:\Python\Anaconda3\lib\tkinter\__init__.py", line 346, in set
return self._tk.globalsetvar(self._name, value)
File "C:\Python\Anaconda3\lib\tkinter\__init__.py", line 1704, in __call__
return self.func(*args)
File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 8867, in _window_tkvar_changed_callback
trace_details = traceback.format_stack()
Yes, sorry. You will be X event behind (depending the write_event_value you have done) the read_all_windows because Tk.mainloop() is blocking the execution. Using your workaround fixes the problem to certain degree, as you said, you can have a race condition stealing the wrong event.
Edit: I don't understand why starting another thread continues the execution, in the new thread you're using write_event_value to add events in the thread_queue meaning that multiple callbacks are done (but if you are blocked in the mainloop, how is it possible?), but if you do a write_event_value from another button in another if event ==..., it doesn't have effect in the execution, because you're blocked in the read_all_windows.
Edit2:
The
write_event_value, when called from the mainthread, does not wait until thewindow.read()happens. tkinter is calling the callback immediately. This will cause a missed event because tkinter will not make the callback due to theread_all_windowscall because it's already happened when the event was posted.
I have seen that behavior in the test, the callback is executed right after calling _BuildResults in read_all_windows:
\\\\ calling _BuildResults ////
\\\\ window.ReturnValues: Dummy|{'-IN-': ''} ////
Window Main Dummy {'-IN-': ''}
#### enter _window_tkvar_changed_callback
event: PY_VAR0 | args: ('', 'w')
queue_thread_event_available(): True
thread_strvar.get(): new item -DUMMY-:pressed
* enter _queued_thread_event_available
One way I can get around the problem of the missing event in the case of the mainthread calling write_event_value is to add a check for items from the queue prior to callling the tkinter event loop. I should have been doing this anyway as there is clearly a race condition. It makes sense the process the queue first anyway as I know those are events that are queued up and ready to be delivered. They should be sent through first. I'll make that change.
GitHub version 4.30.0.22 will read the Queue first in read_all_windows. I'll make a similar change to the single window read if it works out OK just in case a similar situation happens there. I didn't know that tkinter had this execution path where the set will make the callback instead of the mainloop. I was lucky to have the trace in place in the callback routine when you added the code to make the call on a button push. "I would rather be lucky than good"
One more thing... you may want to remove the stdout routing. It would only be a problem if the print were to originate from the thread. I'm not 100% sure it would cause a big problem, but it's better to be safe when it comes to making any calls into tkinter from a thread.
[sg.MLine(size=(80, 12), k='-ML-', reroute_stdout=False,write_only=True, autoscroll=True, auto_refresh=True)],
Thank you very much. Version 4.30.0.22 fixes the problem.
[sg.MLine(size=(80, 12), k='-ML-', reroute_stdout=False,write_only=True, autoscroll=True, auto_refresh=True)],I added the reroute_cprint=True because I was the following warning
** Warning ** Attempting to perform a cprint without a valid window & key Will instead print on Console You can specify window and key in this cprint call, or set ahead of t
ime using cprint_set_output_destination
Awesome! Thanks for your help and patience throughout the debugging.
I added the reroute_cprint=True
Makes sense. In the "real" write_event_value demo program, the Multiline is set to reroute cprint as well.
[sg.Multiline(size=(65,20), key='-ML-', autoscroll=True, reroute_stdout=True, write_only=True, reroute_cprint=True)],
I need to document the possible problem with stdout when using threads. I don't think I've documented this possible problem before. One issue with many of these threading warnings is that threading problems, as you _very well_ understand, can be intermittent and are often due to race conditions. It's been great working with you as you have a solid understanding of the overall problems encountered with threads and race conditions. Users are able to "get away with it".... at least for a while.
Whew.... glad this one is working correctly now. I'll work on adding a similar check to the single window read as well.
I created a new demo program that demonstrates that stuff we've been working on, the user of write_event_value with read_all_windows.
It's shockingly short at under 50 lines of code. I made it a bit more deterministic by adding a counter to the thread, something I recall you doing as well. Hopefully, the next person that's looking to run a multi-window project using threads will have an easier go of it than you did. Glad you were the person reporting this problem as your help definitely played a part in finding where the problem was located. It's so much easier solving these kinds of problems with another person that understands threading.
Here's the source for the new demo.
import threading
import time
import PySimpleGUI as sg
"""
Threaded Demo - Multiwindow Version
This demo uses the write_event_value method in a multi-window environment. Instead of `window.read()` returning the event to
the user, the call to read_all_windows is used which will return both the window that had the event along with the event.
Copyright 2020 PySimpleGUI.org
"""
THREAD_EVENT = '-THREAD-'
PROGRESS_EVENT = '-PROGRESS-'
cp = sg.cprint
def the_thread(window, window_prog):
"""
The thread that communicates with the application through the window's events.
Once a second wakes and sends a new event and associated value to a window for 10 seconds.
Note that WHICH window the message is sent to doesn't really matter because the code
in the event loop is calling read_all_windows. This means that any window with an event
will cause the call to return.
"""
for i in range(10):
time.sleep(1)
window.write_event_value(THREAD_EVENT, (threading.current_thread().name, i)) # Data sent is a tuple of thread name and counter
window_prog.write_event_value(PROGRESS_EVENT, i) # Send a message that the progress bar should be updated
def make_progbar_window():
layout = [[sg.Text('Progress Bar')],
[sg.ProgressBar(10, orientation='h', size=(15, 20), k='-PROG-')]]
return sg.Window('Progress Bar', layout, finalize=True, location=(800, 800))
def make_main_window():
layout = [[sg.Text('Output Area - cprint\'s route to here', font='Any 15')],
[sg.Multiline(size=(65, 20), key='-ML-', autoscroll=True, reroute_stdout=True, write_only=True, reroute_cprint=True)],
[sg.T('Input so you can see data in your dictionary')],
[sg.Input(key='-IN-', size=(30, 1))],
[sg.B('Start A Thread'), sg.B('Dummy'), sg.Button('Exit')]]
return sg.Window('Window Main', layout, finalize=True)
def main():
"""
The demo will display in the multiline info about the event and values dictionary as it is being
returned from window.read()
Every time "Start" is clicked a new thread is started
Try clicking "Dummy" to see that the window is active while the thread stuff is happening in the background
"""
main_window = make_main_window()
window_prog = make_progbar_window()
while True: # Event Loop
window, event, values = sg.read_all_windows()
print(window.Title, event, values)
if event == sg.WIN_CLOSED or event == 'Exit':
break
if event.startswith('Start'):
threading.Thread(target=the_thread, args=(main_window, window_prog), daemon=True).start()
if event == THREAD_EVENT:
cp(f'Thread Event ', colors='white on blue', end='')
cp(f'{values[THREAD_EVENT]}', colors='white on red')
if event == PROGRESS_EVENT:
cp(f'Progress Event from thread ', colors='white on purple', end='')
cp(f'{values[PROGRESS_EVENT]}', colors='white on red')
window_prog['-PROG-'].update(values[event] % 10 + 1) # type: sg.ProgressBar.update()
if event == 'Dummy':
window.write_event_value('-DUMMY-', 'pressed')
if event == '-DUMMY-':
cp("Dummy pressed")
window.close()
if __name__ == '__main__':
main()
Thanks to you for your hard work, and I really appreciate the help you provided fixing the problem!
The Window.read() function has also been updated to check the queue prior to going into the tkinter event loop. It's unclear if there is a problem however. This test code will detect if there's a queued event sitting around. It appears things clear up OK with and without the code the processes the queue first in window.read(). I don't believe there will be a problem with including the check so I'm going to release it with the other fix.
I want to get this fix posted to PyPI quickly as it's a pretty bad bug for someone to encounter that's running a threaded application. I don't want to put anyone through the experience you've just been through.
import threading
import time
import PySimpleGUI as sg
"""
Threaded Demo - Uses Window.write_event_value communications
Requires PySimpleGUI.py version 4.25.0 and later
This is a really important demo to understand if you're going to be using multithreading in PySimpleGUI.
Older mechanisms for multi-threading in PySimpleGUI relied on polling of a queue. The management of a communications
queue is now performed internally to PySimpleGUI.
The importance of using the new window.write_event_value call cannot be emphasized enough. It will hav a HUGE impact, in
a positive way, on your code to move to this mechanism as your code will simply "pend" waiting for an event rather than polling.
Copyright 2020 PySimpleGUI.org
"""
THREAD_EVENT = '-THREAD-'
cp = sg.cprint
def the_thread(window):
"""
The thread that communicates with the application through the window's events.
Once a second wakes and sends a new event and associated value to the window
"""
for i in range(10):
time.sleep(.2)
window.write_event_value('-THREAD-', (threading.current_thread().name, i)) # Data sent is a tuple of thread name and counter
# cp('This is cheating from the thread', c='white on green')
def main():
"""
The demo will display in the multiline info about the event and values dictionary as it is being
returned from window.read()
Every time "Start" is clicked a new thread is started
Try clicking "Dummy" to see that the window is active while the thread stuff is happening in the background
"""
layout = [[sg.Text('Output Area - cprint\'s route to here', font='Any 15')],
[sg.Multiline(size=(65, 20), key='-ML-', autoscroll=True, reroute_stdout=True, write_only=True, reroute_cprint=True)],
[sg.T('Input so you can see data in your dictionary')],
[sg.Input(key='-IN-', size=(30, 1))],
[sg.B('Start A Thread'), sg.B('Dummy'), sg.B('Nothing'), sg.Button('Exit')]]
window = sg.Window('Window Title', layout, finalize=True)
while True: # Event Loop
event, values = window.read()
if window._queued_thread_event_available():
print('** queued events are available **')
cp(event, values)
if event == sg.WIN_CLOSED or event == 'Exit':
break
if event.startswith('Start'):
threading.Thread(target=the_thread, args=(window,), daemon=True).start()
if event == THREAD_EVENT:
cp(f'Data from the thread ', colors='white on purple', end='')
cp(f'{values[THREAD_EVENT]}', colors='white on red')
if event == 'Dummy':
window.write_event_value('-DUMMY-', 'This was generated by the button')
window.close()
if __name__ == '__main__':
main()
Released to PyPI in 4.31.0
Most helpful comment
Thanks to you for your hard work, and I really appreciate the help you provided fixing the problem!