はじめに
customtkinter(tkinterのモダンUI版、実装方法はtkinterとほぼ同じ)で発生した以下エラーの解決方法を備忘録として残します。
_tkinter.TclError: image "pyimage1" doesn't exist
こちらのエラーは1つのアプリケーションの中で2つのウィンドウを作成した際に発生したものです。
現象の再現と処理の流れ
以下実際のプログラムを簡単化して同じエラーとなるようにしたプログラムです。
処理の流れ
- Contrelloeから、1つ目のウィンドウをとなるView1を実行、mainloop()します。
- View1のウィンドウであるボタンを押すともう1つのウィンドウとなるView2が立ち上がり、こちらも同様にmainloop()します。
- View2のウィンドウでまた別のボタンを押すと画像が表示される想定でしたが、画像が表示されずに該当エラーが発生しました。
import customtkinter
import tkinter as tk
from PIL import Image, ImageTk
class Controller:
def __init__(self):
self.view1 = View1(self)
self.view1.mainloop()
def add_window(self):
self.view2 = View2(self)
self.view2.mainloop()
def display_img(self):
img = Image.open("dog1.jpg")
img = ImageTk.PhotoImage(img)
self.view2.add_img(img)
class View1(customtkinter.CTk):
def __init__(self, controller):
super().__init__()
self.geometry("300x300")
self.controller = controller
customtkinter.CTkButton(self, text="view2ウィンドウに画像を表示",
command=self.controller.add_window).pack()
class View2(customtkinter.CTk):
def __init__(self, controller):
super().__init__()
self.geometry("500x500")
self.controller = controller
customtkinter.CTkButton(self, text="画像を表示します", command=self.controller.display_img).pack()
def add_img(self, img):
self.image_label = customtkinter.CTkLabel(self, image=img)
self.image_label.image = img
self.image_label.pack()
if __name__ == "__main__":
controller = Controller()
原因
今回の原因は2つView
をそれぞれmainloop()
していたことでした。
それによって連携させたいウィンドウが独立してしまい、画像オブジェクトの参照が無効になっていたと推察されます。
対処法
- 各ウィンドウで
mainloop()
を呼び出さないようにして、View1
のmainloop()
で全体を制御 -
View2
はToplevel
を使用したサブウィンドウとして機能させる
具体的にはサブウィンドウとしたいクラスにToplevelクラスを継承させてあげます
対処コード
# 2つめのmianloop()を削除(コメントアウト化)
# self.view2.mainloop()
# class View2(customtkinter.CTk): ⇚これを削除、代わりにToplevelクラスを継承
class View2(customtkinter.CTkToplevel):
最終的なコード
import customtkinter
import tkinter as tk
from PIL import Image, ImageTk
class Controller:
def __init__(self):
self.view1 = View1(self)
self.view1.mainloop()
def add_window(self):
self.view2 = View2(self)
# self.view2.mainloop()
def display_img(self):
img = Image.open("dog1.jpg")
img = ImageTk.PhotoImage(img)
self.view2.add_img(img)
class View1(customtkinter.CTk):
def __init__(self, controller):
super().__init__()
self.geometry("300x300")
self.controller = controller
customtkinter.CTkButton(self, text="view2ウィンドウに画像を表示",
command=self.controller.add_window).pack()
class View2(customtkinter.CTkToplevel):
def __init__(self, controller):
super().__init__()
self.geometry("500x500")
self.controller = controller
customtkinter.CTkButton(self, text="画像を表示します", command=self.controller.display_img).pack()
def add_img(self, img):
self.image_label = customtkinter.CTkLabel(self, image=img)
self.image_label.image = img # Keep a reference to the image
self.image_label.pack()
if __name__ == "__main__":
controller = Controller()
修正後実行結果
まとめ
- mainloop()はアプリケーションで1回だけでそれ以上はNG
- 複数ウィンドウはToplevelを使用してサブウィンドウ化して対応