PysimpleGUI 3分クッキング

## はじめに

Pythonで簡単なGUIアプリを作りたいけどどんなライブラリがあるんだろう、と思って調べた結果 PysimpleGUI という素晴らしいものに出会いました。

レイアウトが直感的でイベント処理も明解。[公式のReadMe](https://pysimplegui.readthedocs.io/en/latest/readme%20Japanese%20Version/)にある通り、コードを書いてて楽しくなるライブラリです。

[公式クックブック](https://pysimplegui.readthedocs.io/en/latest/cookbook/)がとても充実しているのも魅力なのですが、充実しすぎて全部読むのは躊躇するのと英語版しかないというハードルがあると思われます。

なので、ここでは３分くらいで読める量にまとめてみました。

## シンプルなフォーム

OKを押すと名前が出力される簡単な入力フォームは、以下のようなコードで実現します。

```python
import PySimpleGUI as sg

def main():
    window = sg.Window("名前",
                       layout=[
                           [sg.Text("名前を入力してください。")],
                           [sg.Input("")],
                           [sg.Button("OK"), ]
                       ])
    while True:
        event, values = window.read()
        if event == sg.WIN_CLOSED:
            break
        else:
            sg.popup(values[0])

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

![2021-05-09-14-07-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439193/74d44ce6-98f6-6dd0-05b9-1640a74f7aae.png)

## Windowのレイアウト

行ごとに配置する要素を決め、リスト配列で定義します。直感的に組めて分かりやすいです。

```python
window = sg.Window("HogeHoge", layout=[
    [sg.Text("以下を入力してください。")],
    [sg.Text("名前"), sg.Input("")],
    [sg.Text("メールアドレス"), sg.Input("")],
    [sg.Button("クリア"), sg.Button("OK")]
])
```

![2021-05-09-14-11-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439193/c926a20f-e5ad-f7f4-2bca-0851484e110e.png)

画面内にさらに分割したレイアウトを組みたいときは「Frame」を使います

```python
frame = sg.Frame("入力", layout=[
        [sg.Text("以下を入力してください。"), sg.Button("クリア")],
        [sg.Text("名前"), sg.Input("")],
        [sg.Text("メール"), sg.Input("")]
    ])
window = sg.Window("HogeHoge", layout=[
    [sg.Text("登録してください")],
    [sg.Text("右にフレーム"), frame], # フレームを挿入
    [sg.Button("OK")]
])
```

![2021-05-09-14-15-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439193/d194e315-4f63-4136-df21-c6c0178b6b97.png)

後述のイベント管理も使えば、Frameを使って機能ごとにコンポーネントを分割することもできそうです。

## 盛り込める要素

ボタンやリストボックス、コンボボックス、チェックボックスなど、欲しいものは大体あります。

```python
window = sg.Window("HogeHoge", layout=[
    [sg.Input("文字の入力")],
    [sg.Listbox(["AAA", "BBB", "CCC"], size=(4, 3))],
    [sg.Combo(["A", "B", "C"])],
    [sg.Checkbox("チェックボックス")],
    [sg.Button("OK")]
])
```

![2021-05-09-17-29-07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439193/6abecf77-46b6-4ca5-0721-f04e6cab4aa0.png)

## イベントの管理

`window.read()`は、ウィンドウ内の各要素からイベントが発生するまで待機し、発生時はイベント名(`event`で取得)と各要素の保持している値(`values`で取得)を返します。ループ内で`window.read()`を待機させ、発生したイベントに応じた処理を実行し、待機に戻ると言うのが基本になります。

`event`には要素を定義する際に `key=` という引数に渡したものが返され、指定していない場合は0から順番に割り振られた番号になります。

`values`はInput等に入力した値、ListBoxで選択した値などが辞書形式で返されます。辞書のkeyは各要素のkeyとなっています。

`window[keyの値]`で各要素に直接触れるため、内容の変更を行ったりもできます。

```python
window = sg.Window(
    "HogeHoge",
    layout=[
        [sg.Text("以下を入力してください。")],
        # key= でイベント発生時のkeyを指定
        [sg.Text("名前"), sg.Input("", key="name")],
        [sg.Text("メール"), sg.Input("", key="mail")],
        [sg.Button("クリア", key="clear"), sg.Button("OK", key="ok")]
    ])

while True:
    event, values = window.read()
    print(event, values)
    # OKボタンを押したとき
    # ok ,{'name': nameの入力値, 'mail': mailの入力値}
    # クリアボタンを押したとき：
    # clear, {'name': nameの入力値, 'mail': mailの入力値}
    if event == sg.WIN_CLOSED:
        break
    elif event == "ok":
        # ok ボタンを押したとき
        # ポップアップでインプットの値を表示
        message = "name:" + values['name'] + "\nmail:" + values['mail']
        sg.popup(message)
    elif event == 'clear':
        # クリアボタンを押したとき
        # 各入力欄を空にする。
        window["name"].update("")
        window["mail"].update("")
```

ボタン要素はクリック時にイベントを発生させますが、多くの要素はクリックだけではイベントを発生させません。`enable_events=True`としてあげるとイベントが発生するようになります。

if-elifの繰り返しはイマイチ好きじゃない場合はイベントハンドラも設定できます。
KeyとFuncitonを辞書に入れて呼び出すだけですが。

```python
# キーを事前に定義しておく
NAME = 'name'
OK = 'ok'
MAIL = 'mail'
CLEAR = 'clear'
window = sg.Window(
    "HogeHoge",
    layout=[
        [sg.Text("以下を入力してください。")],
        [sg.Text("名前"), sg.Input("", key=NAME)],
        [sg.Text("メール"), sg.Input("", key=MAIL)],
        [sg.Button("クリア", key=CLEAR), sg.Button("OK", key=OK)]
    ])

def clear_input(v):
    # 引数のとり方を他と同じように設定する
    window[NAME].update("")
    window[MAIL].update("")

event_handler = {
    OK: lambda v: sg.popup(f"name:{v[NAME]}\nmail:{v[MAIL]}"),
    CLEAR: clear_input
}

while True:
    event, values = window.read()
    if event == sg.WIN_CLOSED:
        # ループをぬける場合は別に用意
        break
    func = event_handler[event]
    func(values)
```

辞書形式で定義することで、子のコンポーネントで定義したハンドラーを親のハンドラーに引き渡して管理するということも可能だと思います。

## 参考

以下の記事を参考にしました。

<https://qiita.com/KatsunoriNakamura/items/376da645e52f7ef7f9ef>
<https://qiita.com/dario_okazaki/items/656de21cab5c81cabe59>