tkinter [image "pyimage1" doesn't exist]エラー解決備忘録

## はじめに
customtkinter（tkinterのモダンUI版、実装方法はtkinterとほぼ同じ）で発生した以下エラーの解決方法を備忘録として残します。
```_tkinter.TclError: image "pyimage1" doesn't exist```
こちらのエラーは1つのアプリケーションの中で2つのウィンドウを作成した際に発生したものです。

## 現象の再現と処理の流れ
以下実際のプログラムを簡単化して同じエラーとなるようにしたプログラムです。

処理の流れ
1. Contrelloeから、1つ目のウィンドウをとなるView1を実行、mainloop()します。
2. View1のウィンドウであるボタンを押すともう1つのウィンドウとなるView2が立ち上がり、こちらも同様にmainloop()します。
3. View2のウィンドウでまた別のボタンを押すと画像が表示される想定でしたが、画像が表示されずに該当エラーが発生しました。


```python
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クラスを継承させてあげます


対処コード
```python
# 2つめのmianloop()を削除（コメントアウト化）
# self.view2.mainloop()

# class View2(customtkinter.CTk):  ⇚これを削除、代わりにToplevelクラスを継承
class View2(customtkinter.CTkToplevel):

```

## 最終的なコード
```python
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()
```

## 修正後実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1306561/e3b2a984-acf5-2339-bb72-6e19b9d20a71.png)


## まとめ
* mainloop()はアプリケーションで1回だけでそれ以上はNG
* 複数ウィンドウはToplevelを使用してサブウィンドウ化して対応