PySimpleGUIで内容を更新する

# 目次
 - PySimpleGUIで内容を更新する　　　　　　　　　←今ここ
 - [PySimpleGUIでマルチスレッドで内容を更新する](https://qiita.com/mo256man/items/7902087f9943a51d4754)（20230827公開）

# はじめに
Excel VBAやAccess VBAでフレームを利用したGUIアプリを作る際のこだわりが私にはある。
黒地に白のテキストボックス（ターミナルを模している）を用意して「◯◯処理を開始します」「◯◯処理を完了しました　全◯件です」といったメッセージを逐次表示するというものだ。

PySimpleGUIで同じことをやろうとしたときいろいろ苦労したので知見をここに残す。


# マルチラインのテキストを更新・追記する
PySimpleGUIで複数行のテキストを表示するのは **sg.Multiline()** 。
一行テキストの **sg.InputText()** と同じく、表示と入力の両方に対応している。
公式のドキュメントは[こちら](https://www.pysimplegui.org/en/latest/call%20reference/#multiline-element)。

インプットテキストやマルチラインでテキストを更新（書き換え）するにはその要素に対して **update()** をおこなう。内容をクリアする際はヌルストリングを指定する。

マルチラインでは更新だけでなく追記もできる。 **print()** を使えばよい。
Python標準関数の print() と同様、そのままでは改行される。
改行させたくないときはendオプションを指定する。これも普通の print() と同じ。

ちなみにマルチラインとほぼ同等の機能を持つ **sg.Output()** なるものもある。
こちらは普通に print() するだけでターミナルでなくアウトプットにメッセージが表示される。

### サンプルコード
```Python:
import PySimpleGUI as sg

class Gui():
    def __init__(self):
        layout = [[sg.Multiline(size=(40,10), key="-LOG-")],
                  [sg.Button("print w/ LF", key="-PRINT1-"),
                   sg.Button("print w/o LF", key="-PRINT2-"),
                   sg.Button("update", key="-UPDATE-"),
                   sg.Button("clear", key="-CLEAR-")
                   ]]
        self.window = sg.Window("GUI sample", layout)

def main():
    gui = Gui()
    window = gui.window
    cnt = 0
    while True:
        event, values = window.read()
        if event == sg.WIN_CLOSED:
            break
        elif event == "-PRINT1-":
            cnt += 1
            window["-LOG-"].print(cnt)
        elif event == "-PRINT2-":
            cnt += 1
            window["-LOG-"].print(cnt, end=",") # テキストの後に改行でなくカンマが付く
        elif event == "-UPDATE-":
            cnt += 1
            window["-LOG-"].update(cnt)
        elif event == "-CLEAR-":
            cnt = 0
            window["-LOG-"].update("")

    window.close()

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

![gui_1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/b252b62c-4264-42fd-bf8f-927d96d98cf6.gif)


# 自動で画面更新する
PySimpleGUIでイベント取得する際には

```Python:
        event, values = window.read()
```
という一文をかませる。画面更新もこのタイミングでおこなわれる。
eventにはボタンが押されたなどのイベントが、valuesにはインプットテキストの内容などが格納される。ただし今回はこの部分には深く触れない。

動作の途中で画面を更新するには **window.read()** とは別のタイミングで能動的に画面更新をしてやればよい。**window.refresh()** がそれだ。

### 人の入力なしで更新される例
これだけでは動かないので解読してください。
```Python:
    while True:
        event, values = window.read()           # イベント発生するまでずっと待つ
        if event == "-TASK-":
            for phrase in ["天は", "自ら助くる者を", "助く"]:
                window["-LOG-"].print(phrase)   # テキスト追記
                window.refresh()                # 画面更新
                time.sleep(1)                   # 1秒停止
```
![gui_3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/e3694d39-5f97-a69a-8b1c-79c081f1966e.gif)

動くは動くが、このようにイベントを拾ってタスクを発動させるループの中で時間がかかるタスクをそのまま実行させるのは本当はよろしくない。
追加で時計機能を実装することを考えてみればわかる。長時間のタスクが発動したとき、それが動いているあいだ時計が止まってしまっては意味がない。

もちろん「細かいことはいいんだよ」の精神でこのまま開発を続けていくのもありだ。

### 更新されるが時計が止まってしまう例
これだけでは動かないので解読してください。
```Python:
    while True:
        event, values = window.read(timeout=1)  # 1ミリ秒だけ待ち、タイムアウトしたら
                                                # '__TIMEOUT__'というイベントが発生して進む
        if event == "-TASK-":
            for phrase in ["天は", "自ら助くる者を", "助く"]:
                window["-LOG-"].print(phrase)   # テキスト追記
                window.refresh()                # 画面更新
                time.sleep(1)                   # 1秒停止
        now = datetime.datetime.now()
        str_now = now.strftime("%H:%M:%S")
        window["-CLOCK-"].update(str_now)
```
![gui_4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/2523f09e-df7e-5650-0566-5c9838848f97.gif)


# マルチスレッドを使う
無限ループは遅延なく回り続け、それとは別に時間がかかる処理をおこない必要なタイミングで画面を更新させたい。そんなときはマルチスレッドを使う。

[ここ](https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Multithreaded_Long_Tasks.py)に公式のサンプルがあるのだが、非常にわかりづらく、私が情報科の先生だったら落第点を与えたくなるような代物。
そこで次回は公式のドキュメントを参照しながらさまざまな機能を持つコードを紹介していく。

https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Multithreaded_Long_Tasks.py

# 終わりに
ということで続く。