You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PySimpleGUI线程内弹出交互窗口触发RuntimeError问题求助

问题:PySimpleGUI线程中弹窗引发RuntimeError

用PySimpleGUI开发GUI程序时,通过window.perform_long_operation或Threading模块能正常避免界面冻结,但在线程运行过程中弹出PopupYesNo等待用户输入以继续执行函数时,会触发RuntimeError: main thread is not in main loop错误。尝试将弹窗逻辑放在独立函数并通过queue传递结果,仍无法解决问题。目前将函数放在主GUI循环中可规避错误,但希望在点击“New WO”单选按钮打开的子GUI中实现该功能。


简化后代码

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


# 逐字符输出文本到元素,延迟0.08秒
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))
    
    """
    试过把弹窗放到独立函数,用队列传值,还是不行
    """


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()

    # 等待2秒加载元素
    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()

                    # 等待所有任务完成
                    # 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]


# 主窗口布局
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()

    # 关闭窗口或点击Cancel时退出程序
    if main_event == sg.WIN_CLOSED or main_event == "Cancel":
        break
    if main_event == "New WO":
        new_wo()  # 这里是要运行线程并弹出弹窗的子GUI
        continue

报错信息

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

解决方法

错误根源是所有PySimpleGUI(基于tkinter)的GUI操作必须在主线程执行,子线程不能直接创建弹窗或操作GUI元素。正确的做法是让子线程通知主线程弹出窗口,再把用户选择的结果传回子线程。

具体实现步骤

  1. 子线程执行到需要用户交互的节点时,用window.write_event_value给主线程发送自定义事件,传递弹窗需求。
  2. 主线程在事件循环中捕获该自定义事件,弹出PopupYesNo获取用户输入。
  3. 通过队列将用户选择的结果传回子线程,让子线程继续执行后续逻辑。

修改后的关键代码

1. 调整google_search函数,通知主线程弹窗

def google_search(que, new_wo_window):
    # ... 省略初始化浏览器的代码 ...
    
    driver.get("https://www.google.com")
    driver.maximize_window()
    time.sleep(2)

    # 通知主线程弹出确认窗口
    new_wo_window.write_event_value("-ASK_CONTINUE-", "")

    # 等待主线程传回用户选择
    item = que.get()

    # ... 省略后续搜索逻辑 ...

2. 修改new_wo函数的事件循环,处理自定义事件

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)
    que = queue.Queue()  # 创建队列用于线程间传值

    while not new_wo_window.is_closed():
        new_wo_event, new_wo_values = new_wo_window.read()

        # 处理子线程发来的弹窗请求
        if new_wo_event == "-ASK_CONTINUE-":
            cont_auto_value = sg.PopupYesNo("Continue?")
            que.put(cont_auto_value)

        elif new_wo_event == "Create WO":
            search = [i.strip().upper()
                           for i in str(new_wo_values["search"]).split("_.)(") if i != ""]
            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":
                    # 启动线程,传入队列和子窗口对象
                    function_thread = Thread(target=google_search, args=(que, new_wo_window), daemon=True)
                    function_thread.start()
            # ... 省略其他事件处理代码 ...

核心原理

  • 子线程仅负责耗时的非GUI操作(如Selenium自动化),遇到用户交互需求时,通过事件通知主线程。
  • 主线程统一处理所有GUI操作,弹窗获取输入后用队列传递结果给子线程。
  • 既避免了界面冻结,又符合tkinter的主线程GUI操作规则,彻底解决RuntimeError问题。

内容的提问来源于stack exchange,提问作者Md. Masum Omar Jashim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 20:55:23