Почему я получаю ошибку PhotoImage, когда я помещаю свой графический интерфейс в поток?

#python #python-3.x #multithreading #tkinter #python-imaging-library

#python #python-3.x #многопоточность #tkinter #python-imaging-library

Вопрос:

Я пытаюсь создать систему для отображения заставки, когда загружается первая страница моего основного графического интерфейса (обе сделаны с помощью tkinter). Я прочитал в Интернете, что tkinter должен иметь все графические команды для определенного окна tkinter в одном потоке. Итак, моя идея отобразить заставку в окне верхнего уровня во втором потоке, в то время как главная страница загружается в первом потоке (главная страница скрыта, пока отображается заставка withdraw() ), не сработает. Поэтому я решил создать отдельные графические интерфейсы для заставки и основного приложения. Главная страница будет инициализироваться во время отображения заставки, и после этого заставка будет уничтожена, а главная страница снова появится с использованием deiconify() . Я попробовал эту идею, используя 2 простых графических интерфейса, и она сработала, но когда я реализовал ее в своем коде для заставки, по какой-то причине я получил следующую ошибку.

 Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:UsersjovanAppDataLocalProgramsPythonPython38-32libthreading.py", line 932, in _bootstrap_inner
    self.run()
  File "C:UsersjovanAppDataLocalProgramsPythonPython38-32libthreading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "c:/Jovan/Captain!!Project/Captain_repo/rough.py", line 7, in import_
    import rough1
  File "c:JovanCaptain!!ProjectCaptain_reporough1.py", line 71, in <module>
    splash.display()
  File "c:JovanCaptain!!ProjectCaptain_reporough1.py", line 56, in display
    self.tkimage = ImageTk.PhotoImage(image1)
  File "C:UsersjovanAppDataLocalProgramsPythonPython38-32libsite-packagesPILImageTk.py", line 112, in __init__
    self.__photo = tkinter.PhotoImage(**kw)
  File "C:UsersjovanAppDataLocalProgramsPythonPython38-32libtkinter__init__.py", line 4061, in __init__
    Image.__init__(self, 'photo', name, cnf, master, **kw)
  File "C:UsersjovanAppDataLocalProgramsPythonPython38-32libtkinter__init__.py", line 4006, in __init__
    self.tk.call(('image', 'create', imgtype, name,)   options)
RuntimeError: main thread is not in main loop
Exception ignored in: <function PhotoImage.__del__ at 0x04EF02B0>
Traceback (most recent call last):
  File "C:UsersjovanAppDataLocalProgramsPythonPython38-32libsite-packagesPILImageTk.py", line 118, in __del__
    name = self.__photo.name
AttributeError: 'PhotoImage' object has no attribute '_PhotoImage__photo'
 

Я нашел в Интернете вопросы с похожими ошибками, но все они предполагали, что изображение, переданное в PhotoImage, не было PIL-изображением, что мне не очень помогло. Заставка работает нормально, если я просто выполняю ее или импортирую из другого файла, но когда я помещаю ее в поток, я получаю указанную выше ошибку. Вот воспроизводимый код для моей проблемы.

 '''The code is executed here.'''
import tkinter as tk
import time
import threading


def import_() :
    import rough1

# creating and starting thread to display splash
splash_thread = threading.Thread(target=import_)
splash_thread.daemon = True
splash_thread.start()

# Main GUI
root = tk.Tk()
root.withdraw()
root.geometry("300x300")
root.protocol("WM_DELETE_WINDOW",root.quit())
root.title("rough")
root.update()
tk.Button(root,text="Hey").pack()
tk.Label(root,text="Hello").pack()
root.update_idletasks()

# Re-displays the main GUI if the splash has been destroyed.
print(threading.active_count())
while threading.active_count() == 2 :
    print("loading...")
    time.sleep(0.5)
else :
    root.deiconify()
    print(threading.active_count())

root.mainloop()
 

Код для заставки:

 import tkinter as tk
from PIL import Image, ImageTk
import tkinter.ttk as ttk
import time
from ttkthemes import ThemedTk

class SplashScreen(ThemedTk) :
    def __init__(self,**kwargs) :
        ThemedTk.__init__(self,theme="black",**kwargs)
        self.overrideredirect(True)
        # self.title("[GAME INITIALIZING...]")
        self.splash()
        self.update()
        self.window()

    def splash(self) :
        self.image1 = Image.open('project_pics\splash.png')

    def window(self) :
        width, height = self.image1.size
        setwinwidth = (self.winfo_screenwidth()-width)//2
        setwinheight = (self.winfo_screenheight()-height)//2
        self.geometry(f"{width}x{height} {setwinwidth} {setwinheight}")
        self.update()

    def display(self) :
        print("splash_Displayed!!")
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        splash_canvas = tk.Canvas(self,width=screen_width,height=screen_height)
        splash_canvas.pack()
        self.update_idletasks()
        self.tkimage = ImageTk.PhotoImage(self.image1)
        self.update_idletasks()
        splash_canvas.create_image(0,0,image=self.tkimage,anchor="nw")
        splash_canvas.image = self.tkimage
        self.update_idletasks()
        splash_canvas.create_text(200,34,text="[LOADING]...PLEASE WAIT",font=("small fonts",20),fill="white")
        progressbar = ttk.Progressbar(orient=tk.HORIZONTAL, length=900, mode='determinate',style="Horizontal.TProgressbar")
        progressbar.place(x=450,y=25)
        progressbar.start()
        self.after(5050,self.destroy)
        self.mainloop()


splash = SplashScreen()
splash.display()
 

Итак, это мои запросы. Почему я получаю указанную выше ошибку и как мне ее исправить??

Комментарии:

1. Toplevel не является отдельной частью, и ее следует использовать в основном потоке.

2. ошибка сначала покажет main thread is not in main loop вам, что Tkinter должен запускать только один mainloop , и он должен запускать его в основном потоке. Итак, ваша настоящая проблема не PhotoImage в том, но mainloop и в использовании thread . Вам действительно нужно использовать thread?

3. @furas Главная страница моего графического интерфейса загружается не очень быстро. Таким образом, вы можете увидеть что-то вроде сбоя на экране в течение секунды, пока он настраивается. Я хотел скрыть это, загрузив главную страницу во время отображения заставки. Вот почему я использовал поток для параллельного выполнения обоих.

4. что такого большого вы загружаете на этой странице? если вы загружаете какой-либо файл, вы можете загрузить его в разделенный файл, но позже вы должны добавить его в окно tkinter в основном потоке. И в то же время заставка должна отображаться в основном потоке

5. time.sleep() заставка, чтобы у вашего главного окна было некоторое время для загрузки