Pysimplegui: [Question] Is it possible to have a thread trigger an event in the GUI?

Created on 18 Nov 2020  ยท  6Comments  ยท  Source: PySimpleGUI/PySimpleGUI

Type of Issues (Enhancement, Error, Bug, Question)

Question

Operating System

Windows 10 (20H2)

Python version

3.6

PySimpleGUI Port and Version

Ports = tkinter

PySimpleGUI Version: 4.32.1

tkinter version: 8.6.6

Your Experience Levels In Months or Years

3 years - Python programming experience
3 years - Programming experience overall
No - Have used another Python GUI Framework (tkinter, Qt, etc) previously (yes/no is fine)?

You have completed these steps:

  • [X] Read instructions on how to file an Issue
  • [X] Searched through main docs http://www.PySimpleGUI.org for your problem
  • [X] Searched through the readme for your specific port if not PySimpleGUI (Qt, WX, Remi)
  • [X] Looked for Demo Programs that are similar to your goal http://www.PySimpleGUI.com
  • [X] Note that there are also Demo Programs under each port on GitHub
  • [X] Run your program outside of your debugger (from a command line)
  • [X] Searched through Issues (open and closed) to see if already reported
  • [X] Try again by upgrading your PySimpleGUI.py file to use the current one on GitHub. Your problem may have already been fixed but is not yet on PyPI.

Description of Problem / Question / Details

Can a thread (not main) act to return an event trigger to the GUI? According to the documentation, this might not be possible.

My workflow for threading is this:

  1. Create a thread separate from main using this tutorial/demo to offshoot an external call to an API to retrieve and clean up some files (function called get_eads()).
  2. Before the thread is called, I disable the "_EXPORT_EAD_" button in the GUI to prevent users from spamming "_EXPORT_EAD_" button and spinning off too many threads to result in a crash.
  3. After the thread starts, the GUI continues to run, but is waiting for an event to trigger going through the while loop.
  4. (Wanted behavior) When the thread completes the export, I want to re-enable the "_EXPORT_EAD_" button, but without the user having to press another button to trigger an event in the loop.

I have an event in the loop that checks if the thread is completed (using is_alive()), but this is only triggered when a user selects an event (in this case, clicks a button).

I've consulted the documentation, which states the following: "no threads can directly call PySimpleGUI calls,โ€ as well as this documentation on multi-threading. I'm taking this to mean my desired behavior actually isn't possible if this is the case, but I could be misunderstanding.

I consulted the Window.write_event_value() demo, but I don't think it's quite what I'm looking for. Here's a gif of my program now:

disable_enable_export

Code To Duplicate

import PySimpleGUI as sg

        if event_simple == "_EXPORT_EAD_":
            window_simple[f'{"_EXPORT_EAD_"}'].update(disabled=True)
            ead_thread = threading.Thread(target=get_eads, args=(input_ids, defaults, cleanup_options, repositories, client, values_simple,))
            ead_thread.start()
        if not ead_thread.is_alive():
            window_simple[f'{"_EXPORT_EAD_"}'].update(disabled=False)  # This is only triggered when a user clicks a button - I want it to update when ead_thread is completed.

All 6 comments

Not read full of your issue exactly, but it seems not correct about threading as you mentioned.

Demo_Multithreaded_Write_Event_Value.py

write_event_value

Adds a key & value tuple to the queue that is used by threads to communicate with the window

Modified code per above example, here write_event_value called in thread.

from time import sleep
import threading
import PySimpleGUI as sg

THREAD_EVENT = '-THREAD-'

def the_thread(window):
    sleep(2)
    window.write_event_value('-THREAD-', (threading.current_thread().name,))

def main():

    layout = [
        [sg.Button('Start A Thread')],
        [sg.Text("", size=(50, 1), key='Status')]]
    window = sg.Window('Window Title', layout)

    while True:
        event, values = window.read()
        if event == sg.WIN_CLOSED:
            break
        if event == 'Start A Thread':
            threading.Thread(target=the_thread, args=(window,), daemon=True).start()
            window['Start A Thread'].update(disabled=True)
            window['Status'].update("Button disabled now, will be enabled after 2 seconds")
        if event == THREAD_EVENT:
            window['Start A Thread'].update(disabled=False)
            window['Status'].update("")

    window.close()

if __name__ == '__main__':
    main()

Jason's posted the right way for a thread to communicate with the event loop.

The primary documentation needs to be updated... thanks for the reminder.

The Cookbook has a section devoted to Multi-threading.

https://pysimplegui.readthedocs.io/en/latest/cookbook/#recipe-long-operations-multi-threading

In it you'll find a description of the single call that's available to threads, window.write_event_value(). You may "get away" (i.e. your program won't immediately crash) with making some calls, some of the time, but you'll most likely eventually get caught.

There are also numerous demo programs that use write_event_value. I count 6 of them at the moment. They all start with "Demo_Multithreaded_".

I'll update the primary documentation so that the section on multi-threading discusses the write_event_value call.

I think the question has been answered so closing this Issue. Feel free to reply if you still don't understand.

Thank you @jason990420 and @PySimpleGUI ! I didn't fully understand how write_event_value works, but now it makes more sense. It's essentially doing exactly what I want in writing an event and value back to the GUI - back when I wrote the GUI before July 2020, this wasn't a feature yet.

I'm still confused as to how it works its magic (this bit here: '-THREAD-', (threading.current_thread().name,)), but I tested it in my code and it works as the cookbook and examples show.

The write function takes 2 parameters. The first is the event that will be returned to your window.read() function. The second parameter is the value that will be in your values dictionary for that event. Think of it like an Input element with events enabled. The read will return the "key", which is the event parameter. The values dictionary will have the value values[event] set to whatever you specify as the second parameter.

One way to learn all this stuff is to put a print statement after your window.read() call. You'll find in almost every program I write there's a print(event, values) statement after my window.read() call. This is because those 2 variables are THE most important variables in your event loop. They hold everything that you'll be working with.

That makes sense, thank you! I'll start putting a print statement for event, values after window.read() to make better sense of it all.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

flowerbug picture flowerbug  ยท  4Comments

lucasea777 picture lucasea777  ยท  3Comments

OPMUSER picture OPMUSER  ยท  5Comments

DKatarakis picture DKatarakis  ยท  6Comments

thelou1s picture thelou1s  ยท  4Comments