2
1

More than 1 year has passed since last update.

PySimpleGUIで内容を更新する

Last updated at Posted at 2023-07-30

目次

はじめに

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

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

マルチラインのテキストを更新・追記する

PySimpleGUIで複数行のテキストを表示するのは sg.Multiline()
一行テキストの sg.InputText() と同じく、表示と入力の両方に対応している。
公式のドキュメントはこちら

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

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

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

サンプルコード

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

自動で画面更新する

PySimpleGUIでイベント取得する際には

        event, values = window.read()

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

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

人の入力なしで更新される例

これだけでは動かないので解読してください。

    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

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

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

更新されるが時計が止まってしまう例

これだけでは動かないので解読してください。

    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

マルチスレッドを使う

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

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

終わりに

ということで続く。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1