PySimpleGUI和tkPDFViewer共用时报main thread不在主循环错误
问题根源
- 所有基于tkinter开发的GUI组件(包括PySimpleGUI、tkPDFViewer)都严格要求UI控件创建、界面渲染逻辑必须在主线程的主循环中执行,禁止在子线程操作任何tkinter相关对象。
- PySimpleGUI窗口的
read()方法内部会启动独立的事件循环,抢占主线程执行权,关闭PySimpleGUI窗口后主线程回到原主窗口的mainloop时,tkinter的运行上下文状态会发生变更。 - tkPDFViewer本身存在线程安全设计缺陷:其内部会启动子线程执行PDF转图片逻辑,并直接在子线程中创建tkinter的
PhotoImage对象,触发了tkinter的线程规则校验,导致报错。
解决方案
优先选择改动量最小的补丁方案:
方案1:覆盖tkPDFViewer的多线程加载逻辑
通过猴子补丁替换tkPDFViewer的子线程加载逻辑,改为在主线程同步执行PDF渲染,同时将PDF窗口的创建调度到主循环上下文中,修改后完整可运行代码如下:
import tkinter as tk import PySimpleGUI as sg import fitz from tkinter import ttk from tkPDFViewer import tkPDFViewer as pdf # 猴子补丁:替换tkPDFViewer的多线程加载逻辑,避免子线程操作tk对象 original_add_img = pdf.ShowPdf.add_img def patched_add_img(self, page, master, width, height, pbar): img = page.get_pixmap(matrix=fitz.Matrix(2, 2)) pix = img.tobytes("ppm") timg = tk.PhotoImage(data=pix) timg = timg.subsample(2, 2) self.img_object_li.append(timg) if pbar: pbar["value"] += 1 pdf.ShowPdf.add_img = patched_add_img # 主窗口 class Main(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) SIMPLEGUI_BUTTON = ttk.Button(self, text="SIMPLE GUI", command=lambda: the_gui()) # 用after调度PDF窗口创建,确保在主循环上下文执行 PDF_Button = ttk.Button(self, text="PDF", command=lambda: self.after(0, PDFWindow)) SIMPLEGUI_BUTTON.grid(padx=20, pady=10) PDF_Button.grid(padx=20, pady=10) # PySimpleGUI窗口 def the_gui(): layout = [[]] window = sg.Window('Window', layout) while True: event, values = window.read() if event in (sg.WIN_CLOSED, 'Exit'): break window.close() # PDF窗口 class PDFWindow(): def __init__(self): window = tk.Toplevel() v1 = pdf.ShowPdf() # 替换为你本地的PDF路径 v2 = v1.pdf_view(master=window, pdf_location="C:\\Users\\Documents\\2021-07-30.pdf", width=200, height=100) v2.pack(side="bottom", fill="both") if __name__ == '__main__': app = Main() app.mainloop()
方案2:替换PDF渲染组件
如果需要加载大体积PDF、不想阻塞主线程,可以放弃tkPDFViewer,自行实现PDF渲染逻辑:子线程只负责将PDF页转为图片二进制数据,再通过after方法将数据抛回主线程创建PhotoImage渲染,完全符合tkinter的线程安全规则。
内容的提问来源于stack exchange,提问作者PythonBeginner
相关产品推荐
相关产品推荐

