0

My problem is running a thread so that my GUI does not freeze which works fine with a window.perform_long_operation. But the problem arises when I want a popup to show up while running the thread and then wait for user input from the popup to continue the function. I am pasting a very small part of my full GUI code.

from threading import Thread
import time
import PySimpleGUI as sg
import queue


# A function to send text to an element one character at a time with a delay of 0.08s

def slow_type(el, text, delay=0.08):
    for character in text:
        el.send_keys(character)
        time.sleep(delay)


def cont_auto(que):
    # cont_auto_value = sg.PopupYesNo("Continue?")
    # que.put(item=cont_auto_value)
    # new_wo_window.write_event_value(key="-MASUM-", value=que.put(item=cont_auto_value))
    
**
TRIED USING THE POPUP AS A SEPARATE FUNCTION AND PUTTING THE VALUE IN A QUEUE. STILL 
DOES N0T WORK.**


def google_search(que):
    # sg.cprint_set_output_destination(window=main_window, multiline_key="-MAIN-ML-")

    from selenium.webdriver.common.by import By
    from selenium import webdriver
    from selenium.webdriver.common.action_chains import ActionChains
    from page_locators.PageLocators import dir_list

    opt = webdriver.ChromeOptions()

    opt.add_experimental_option("prefs", {
        'download.default_directory': dir_list.new_folder,
        'download.prompt_for_download': False,
        'download.directory_upgrade': True,
        'plugins.always_open_pdf_externally': True
    })

    opt.add_argument("--kiosk-printing")

    driver = webdriver.Chrome(r"C:\Users\me_a1\Desktop\chromedriver.exe", 
                              chrome_options=opt)

    action = ActionChains(driver)

    driver.get("https:www.google.com")

    driver.maximize_window()

    # Wait 2 seconds for elements to load

    time.sleep(2)

    item = que.get()

    # new_wo_window.write_event_value(key="-GOOGLE-", value=item)

    print(item)

    if item == "Yes":

        search_textbox = driver.find_element(By.XPATH, "//input[@class='gLFyf gsfi']")

        action.click(search_textbox).send_keys("real madrid").perform()

        time.sleep(2)

        driver.quit()

    else:

        search_textbox = driver.find_element(By.XPATH, "//input[@class='gLFyf gsfi']")

        action.click(search_textbox).send_keys("uefa").perform()

        time.sleep(2)

        driver.quit()

    que.task_done()


def new_wo():

    new_wo_layout = [[sg.Text("Search Term:".upper(), pad=((5, 0), (0, 0)))],
                     [sg.InputText("", key="search", pad=((5, 0), (0, 0)))]
                     ]

    layout_new_wo = [[sg.Column(new_wo_layout)],
                     [sg.Button("Create WO", pad=((10, 0), (15, 0))), sg.Button("Cancel", pad=((20, 0), (15, 0)))]]

    new_wo_window = sg.Window(title=window_title(), layout=layout_new_wo, finalize=True, modal=True)

    while not new_wo_window.is_closed():

        new_wo_event, new_wo_values = new_wo_window.read()

        search = [i.strip().upper()
                       for i in str(new_wo_values["search"]).split("_.)(") if i != ""]

        if new_wo_event in [sg.WIN_CLOSED, "Cancel"]:

            new_wo_window.close()

            break

        elif new_wo_event == "Create WO":

            if all(len(new_wo_info_list) != 0 for new_wo_info_list in [search]):

                create_wo = sg.PopupYesNo("Start search?".upper(),
                                          "(please check your data before clicking 
                                          yes)".upper(),
                                          f"Search terms: {''.join(search)}",
                                          modal=True, title="wo create data".upper())

                if create_wo == "Yes":

                    # que = queue.Queue()
                    
                    # function_thread = Thread(target=google_search, args=(que,), daemon=True)
                    # function_thread.start()

                    # continue_automation_popup_thread = Thread(target=cont_auto, args=(que,))
                    # continue_automation_popup_thread.start()

                    # continue_automation_popup_thread.join()

                    # wait for all work to be processed

                    # que.join()

                else:

                    sg.Popup("New WO creation cancelled!".upper(), modal=True, title="wo 
                              cancel".upper())

            else:

                sg.Popup("Please check that all data is provided".upper(), modal=True,
                         title="Missing Data".upper())


def window_title():
    title = []

    for key, value in main_values.items():

        if value:
            title.append(str(key).upper())

    return title[0]


# Define the layout of the program's first window

sg.theme_global("DarkBlack1")

text_list = ["New WO"]

layout = [[sg.Radio(str(text_list[i].upper()), enable_events=True, key=str(text_list[i]), group_id=0)]
          for i in range(0, len(text_list))]

layout += [[sg.Multiline(size=(65, 23), key="-MAIN-ML-", pad=((5, 0), (0, 0)))],
           [sg.Button("Cancel", pad=((5, 0), (10, 6)))]]

layout += [[sg.Menubar(menu_definition=[["SETTINGS", ("COLOR SCHEME",
                                                      ["Dark Mode", "Light Mode", "Rose Pink",
                                                       "Bright Red", "Midnight Blue"])]])]]

layout_frame = [[sg.Frame(title="Select Option".upper(), title_color="yellow", layout=layout)]]

main_window = sg.Window(title="Work Order Automation".upper(), layout=layout_frame, element_justification='c',
                        finalize=True)

while True:

    main_event, main_values = main_window.read()

    """End program if user closes window or
     presses the Cancel button"""

    if main_event == sg.WIN_CLOSED or main_event == "Cancel":
        break

    if main_event == "New WO":

        new_wo() ---> The Layout that should run the thread and show the POPUP

        continue

The exception trace is as follows:

Exception ignored in: <function Variable.__del__ at 0x000002A95AF86B90>
Traceback (most recent call last):
  File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 388, in __del__
    if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
RuntimeError: main thread is not in main loop


Exception ignored in: <function Variable.__del__ at 0x000002A95AF86B90>
Traceback (most recent call last):
  File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 388, in __del__
    if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
RuntimeError: main thread is not in main loop


Exception ignored in: <function Variable.__del__ at 0x000002A95AF86B90>
Traceback (most recent call last):
  File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 388, in __del__
    if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
RuntimeError: main thread is not in main loop

Exception in thread Thread-2 (cont_auto):
Traceback (most recent call last):
  File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\threading.py", line 1009, in _bootstrap_inner
    self.run()
  File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\threading.py", line 946, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\me_a1\Desktop\WO_Automation\GUI\testing.py", line 18, in cont_auto
    cont_auto_value = sg.PopupYesNo("Continue?")
  File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 20106, in popup_yes_no
    return popup(*args, title=title, button_type=POPUP_BUTTONS_YES_NO, background_color=background_color,
  File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 19390, in popup
    button, values = window.read()
  File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 10072, in read
    results = self._read(timeout=timeout, timeout_key=timeout_key)
  File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 10143, in _read
    self._Show()
  File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 9883, in _Show
    StartupTK(self)
  File "C:\Users\me_a1\AppData\Local\JetBrains\PyCharmCE2022.1\demo\PyCharmLearningProject\venv\lib\site-packages\PySimpleGUI\PySimpleGUI.py", line 16817, in StartupTK
    root = tk.Toplevel(class_=window.Title)
  File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 2650, in __init__
    BaseWidget.__init__(self, master, 'toplevel', cnf, {}, extra)
  File "C:\Users\me_a1\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 2601, in __init__
    self.tk.call(
RuntimeError: main thread is not in main loop

The solution can be overcome if I run the function in the main "while" loop of the top-level GUI, but I want it to run in the sub-GUI that opens after clicking on the radio button.

For simplicity's sake, I have only provided the minimum GUI code if the problem arises in. There are more radio buttons that open sub-GUIs that perform other functions. It is only the "New WO" radio button whose function needs a popup right before the end of the script. I have replaced the long function with a google_search() function again to simplify everything. Hopefully, this will allow others to read and understand the code much more quicker.

  • FYI, If you `start()` a thread and then immediately `join()` it, that's practically the same as just calling the thread's `target` function. (It will take at least as long, maybe a tiny bit longer.) Also note: A thread doesn't really have an "inside." A thread is going to do whatever you tell it to do regardless of which other thread created it and started it. – Solomon Slow Oct 14 '22 at 20:46
  • @SolomonSlow But I didn't join the main thread. I only joined the thread that called the sg.PopupYesNo function. And I did try without the ```join()``` too but it doesn't work. The fundamental problem is that the thread is being run from a sub-GUI within the main GUI loop. I just want to know if there is any way to implement it without having to run my function from the main loop as that would beat the purpose of my GUI app anyways. – Md. Masum Omar Jashim Oct 14 '22 at 22:18

1 Answers1

1

Maybe you can split the code in one thread to different threads.

Demo code

from time import sleep
import PySimpleGUI as sg

def func(win, index):
    for i in range(0, 51):
        sleep(0.1)
        win.write_event_value('Update', (f'P{index}', i))

sg.theme('DarkBlue3')
layout = [
    [sg.Text('', size=(50, 1), relief='sunken', font=('Courier', 11), text_color='yellow', background_color='black',key=f'P{i}')] for i in (1, 2)] + [
    [sg.Button('Start')],
]
window = sg.Window("Title", layout)
sg.theme('DarkBlue4')

while True:

    event, values = window.read()

    if event == sg.WIN_CLOSED:
        break
    elif event == 'Start':
        window['Start'].update(disabled=True)
        window['P1'].update('')
        window['P2'].update('')
        window.perform_long_operation(lambda win=window, index=1:func(win, index), "P1 Done")
    elif event == "P1 Done":
        if sg.popup_yes_no("Step 2 ?") == 'Yes':
            window.perform_long_operation(lambda win=window, index=2:func(win, index), "P2 Done")
        else:
            window['Start'].update(disabled=False)
    elif event == "P2 Done":
        window['Start'].update(disabled=False)
    elif event == 'Update':
        key, i = values[event]
        window[key].update('█'*i)

window.close()

enter image description here

Jason Yang
  • 11,284
  • 2
  • 9
  • 23
  • Can you run my code, please? There is a radio button that opens another window which then takes user input and then runs a threaded function. I had used ```window.perform_long_operation``` before but moved to queue and threading but to no avail. Both throw exceptions when the popup is called in that function. – Md. Masum Omar Jashim Oct 14 '22 at 21:59
  • my original script (function) calls the popup at exactly a certain point and then continues on with some other work after the popup is clicked. I will post an excerpt in the next comment – Md. Masum Omar Jashim Oct 14 '22 at 22:00
  • Original function: def origin_func(): # do something call popupyesno if user_clicked == "Yes": # continue with something else: # continue with something else – Md. Masum Omar Jashim Oct 14 '22 at 22:04
  • Your code is not executable and you cannot call GUI action, like `sg.popup`, when not in the main thread. You can call `sg.popup` before or after your threaded function. – Jason Yang Oct 14 '22 at 23:10
  • Ok. So there is no way to run the function in a thread in a sub-GUI without the main thread (the top level GUI WHILE loop). As such, is there any way to pause a thread execution that is not in the main loop? – Md. Masum Omar Jashim Oct 15 '22 at 07:01
  • Maybe you can refer https://stackoverflow.com/questions/3262346/how-to-pause-and-resume-a-thread-using-the-threading-module – Jason Yang Oct 15 '22 at 09:57
  • I have tried that. It also throws the same exception. I guess there is no workaround for this problem. Maybe I should just recode the whole GUI to somehow bring that one function to the main loop. I just don't get it though, why the function while in the thread cannot communicate with the queue I had created in the original code. Queues were meant for threads to communicate, and yet I got the same "main thread not in main loop" error. – Md. Masum Omar Jashim Oct 15 '22 at 17:24