2

As far as I can see, this is a constant problem when using tkinter since it is not multi-threaded. I have read a lot about this issue but I don't know how I can solve it in my case.

My application consists of a plotly dash running in a local server with a button that calls a function that does a series of processes with the excel. At first I got main thread is not in main loop when pressing the button. Nevertheless, setting a daemon I managed to avoid it. However, if I press the button again the program stops working and the error mentioned above appears.

This is a summarized script of my plotly dash:

def interact_callbacks(self):
        @callback(
            Output('add-automatically-button', 'children'),
            Input('add-automatically-button', 'n_clicks')
        )
        def update_output(n_clicks):
            if n_clicks is not None:
                t = threading.Thread(target=startInteraction)
                t.setDaemon(True)
                t.start()
                return 'Clicked!'
            else:
                return 'Add automatically'

This is the function startInteraction() that is in a different .py

def startInteraction():
    gui = GUI()
    gui.get_excel_path()
    path=gui.path

    confirmation = gui.confirmationPopUp(path)
    sys.stderr.write("***OK: selected**\n")
    
    if(confirmation):
        sys.stderr.write("***OK***\n")
        defectLoader(path)
        
    else:
        sys.stderr.write("***CANCEL***\n")
        gui.errImport()
        exit()

This is my gui class (I have more pop-ups in this class)

class GUI():
    def __init__(self):
        self.path = None    

    def get_excel_path(self):
        size = {
            "size": (40, 1)
        }

        layout = [
            [sg.Text('Select Database (Excel):')],
            [sg.Input(**size), sg.FileBrowse(file_types=(("Excel File", "*.xlsx"),))],
            [sg.OK(), sg.Cancel()]
        ]

        window = sg.Window('File Browser', layout)
        event, values = window.read()
        window.close()

        try:
            file_path = values[0]
            if event == 'Cancel' or not file_path:
                sys.stderr.write("***CANCEL***\n")
                raise Exception('No file selected')

            self.path= file_path
        except Exception as e:
            sg.popup(f'Error: {e}')
            exit()

How can I write a thread queue in this code? Is there other option? Thanks in advance.

yeray
  • 51
  • 7
  • There are examples in the Demo Programs on threading with PySimpleGUI. Look for "multithreaded". The call you'll use is `window.write_event_values` – Mike from PSG Aug 01 '23 at 15:30
  • 1
    Don't create or update your GUI in sub-thread. – Jason Yang Aug 02 '23 at 02:48
  • @MikefromPSG I would like to thank you for your advice. I have never programmed with `plotly` dash and `pysimplegui` before, however I think what you say about window.write_event_values() is used when you make a call from `pysimplegui`. In my case the main thread would be the `plotly` dash app and when I press a button I call a method that uses the `pysimplegui` library. So I don't know if this will be useful for my specific case. Am I right? thank you very much in advance. – yeray Aug 02 '23 at 08:58
  • @JasonYang Is there another way to do it? – yeray Aug 02 '23 at 09:00
  • 1
    Better to build the GUI by PySimpleGUI in your main thread, then by event to start other sub-thread which will call `window.write_event_value` to generate an event to the event loop in the main thread and update the GUI in the main thread. – Jason Yang Aug 02 '23 at 13:15

1 Answers1

2

This is a typical problem when trying to combine the plotly dash library with tkinter or pysimplegui. I had a similar problem a few years ago and solved it by doing the following:

p = Process(target=yourFunctionName)
p.start()

Do not forget to do the following import:

from multiprocessing import Process

In your case:

def interact_callbacks(self):
    @callback(
        Output('add-automatically-button', 'children'),
        Input('add-automatically-button', 'n_clicks')
    )
    def update_output(n_clicks):
        if n_clicks is not None:
            p = Process(target=startInteraction)
            p.start()
            return 'Clicked!'
        else:
            return 'Add automatically'

I hope this solves your problems and you can finish your app.