【Tkinter】Python tkinterはClassで書いたほうが良い

# はじめに
以前Tkinterに関する記事を書いたときはわからなかったのだが、いろいろツールを作成しているとClassを使ったほうがイケてるし使いやすいと感じるようになった。
自分だけが使うのであればコマンドラインで使用してしまうが、ツールを普及させようと思うとGUI化がマストになってくる。ここらでTkinterの考えをfixし、見やすいコードを書こう(自戒)

以前の記事

https://qiita.com/yulily/items/42f84226d4ecdf282dd8

# Classを使用するメリット
- `root = tk.Tk()`が複数ウィンドウでごちゃつかない
- ウィンドウと関数(メソッド)間で変数を共有できる
    - `entry`を増やしたり消したりした際の修正が楽
- 思いついたら追記するよ

# 目標
こういうGUIを作る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2716347/2fe5edaa-2215-64c8-6e04-fe6829c60582.png)

ボタンを押すと入力した文字がポップアップされる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2716347/a9d245d7-55a2-ce80-0176-f5293bca5d48.png)

# 基本の考え方
以前の記事と重複するが、基本の考え方は
「ヒエラルキーの高いウィジェットから定義していき、ヒエラルキーの低いウィジェットから配置していく」

:::note info
tkinterでGUI作成する際は次のような考え方をする。

親ウィジェットの定義
子ウィジェットの定義
孫ウィジェットの定義
...
孫ウィジェットの配置
子ウィジェットの配置
親ウィジェットの配置
:::
https://qiita.com/yulily/items/42f84226d4ecdf282dd8

# コード
変数に`self.`をつけておくことで`class`内どこからでも共通して呼び出したり書き換えたりすることができる。
うまくやれば`def`文の引数が`self`のみで済む。

```python
import tkinter as tk
from tkinter import messagebox


class MainGui():
    def __init__(self):
        # =====================================================================
        # 定義セクション
        # =====================================================================
        # 画面そのもの（親）の定義
        self.root = tk.Tk()
        self.root.title("タイトル")

        # フレーム（子）の定義
        self.frame1 = tk.Frame(self.root)
        self.frame2 = tk.Frame(self.root)

        # エントリーとボタン（孫）の定義
        self.entry = tk.Entry(self.frame1, width=50)
        self.button = tk.Button(self.frame2, width=10, text="ボタン",
                                command=lambda: self.popup_text())

        # =====================================================================
        # レイアウトセクション
        # =====================================================================
        # エントリーとボタン（孫）の配置
        self.entry.grid(row=0, column=0)
        self.button.grid(row=0, column=0)

        # フレーム（子）の配置
        self.frame1.grid(row=0, column=0)
        self.frame2.grid(row=1, column=0)

        # 画面そのもの（親）の配置
        self.root.mainloop()

    def popup_text(self):
        messagebox.showinfo("title", self.entry.get())


MainGui()
```

`class MainGui():`の部分でメインのウィンドウを構成するパーツと、内部で使用する関数(メソッド)を定義する。

`def __init__(self):`の部分で階層の高いウィジェットから順に定義し、階層の低いウィジェットから順にレイアウトする。最後に`self.root.mainloop()`でウィンドウを表示してコンストラクタを終了する。

`def popup_text(self):`の部分は内部で呼び出す関数(メソッド)を定義している。クラス化し`entry`をジェットを`self.entry`と定義したことで`self.entry.get()`を引数なしで使用することができる。

`MainGui()`でクラスをインスタンス化し、実際にウィンドウを起動する。

# テンプレ化
ここからは自己満足。
過去記事で作成したデコレータを追加している。

https://qiita.com/yulily/items/a22f7494c5a9c4b9fa36


- 1つのウィンドウに1つのクラスを定義する
- 別ウィンドウに遷移する場合はもとのウィンドウを`self.root.destroy()`で消す
- `class`内に`def`文を追加する際は`@show_on_error`デコレータをつける
- `def close(self):`で終了処理を記入、何も書かない消せないウィンドウになってしまう

```python
import tkinter as tk
from tkinter.messagebox import showerror
from functools import wraps
import traceback


def show_on_error(func):
    # エラー時にポップアップするデコレータ
    @wraps(func)
    def popup(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(traceback.format_exc())
            title = e.__class__.__name__
            msg = traceback.format_exc(limit=0)
            showerror(title, msg)
    return popup


class GUI():
    @show_on_error
    def __init__(self):
        # top
        self.root = tk.Tk()
        self.root.title("title")

        # 2nd
        self.frame = tk.Frame(self.root)

        # 3rd
        # ウィジェットの定義

        # 3rd
        # ウィジェットの配置

        # 2nd
        self.frame.grid(row=0, column=0)

        # top
        self.root.protocol("WM_DELETE_WINDOW", lambda: self.close())
        self.root.mainloop()

    @show_on_error
    def close(self):
        self.root.destroy()
        return


def main():
    GUI()
    return


if __name__ == "__main__":
    main()
    
```