PySimpleGUIでGUIアプリを作ってみた ~その3~ イベント処理

PySimpleGUI

PySimpleGUIでデスクトップアプリを作ってみたので日記も兼ねてこんな事出来ますよというのを書きなぐっていきます。第3弾はイベントの処理についてです。今回は自作アプリの実装ではなく、解説に重きを置いてみました。需要があればいいんですが・・・

環境 Python 3.8, PySimpleGUI 4.18.0

2021.5追記 応用編を書いてみました。

PySimpleGUI イベント処理の基本

PySimpleGUIでは、ユーザーが画面をクリックするなどした際にイベントが発生するようになっています。

window.read()でイベントを受け取る

以下はチュートリアルなどでよく紹介されている一般的なイベント処理のコードですね。

window.read()はinput()のように入力待ちの状態です。入力を受け取るまではこの行でずっと待機していて、イベントが発生したら(key, value)のタプル形式でイベントを受け取ります。

下のコードではそれぞれevent, valueの変数にアンパッキングして代入していますね。

import PySimpleGUI as sg

layout = [[sg.Text('PySimpleGUIを使っていますか?')],
          [sg.Button('はい', key='-OK-')]]

window = sg.Window('Sample', layout=layout)

while True:
    event, value = window.read()  # ここで入力待ち状態になる
    # ボタンのクリック等のイベントを(key, value)で受け取る

    if event is None:
        break

window.close()

window.read()で受け取る(key, value)の “key” について

keyはイベントの発生源を示しています

例えばボタンが押された場合は押されたボタンを示す文字列です。具体的にどういう文字列かというと、各エレメント(ButtonやTextなど)を宣言する時にkey引数に設定した文字列になります。

今回はButtonのkeyに’-OK-‘を指定しているので、ボタンを押すと(‘-OK-‘, value)という形でイベントを受け取ります。valueについては後述します。

なお、画面のバツボタンを押すと(None, None)が返ってくる仕様になっています。

import PySimpleGUI as sg

layout = [[sg.Text('PySimpleGUIを使っていますか?')],
          [sg.Button('はい', key='-OK-')]]

window = sg.Window('Sample', layout=layout)

while True:
    event, value = window.read()

    print(event, value)
    # 'はい'Buttonを押した時は event = -OK-, value = {}
    # 画面のバツボタンを押した時は event = None, value = None  どちらもNoneType

    if event is None:
        break

window.close()

ちなみにエレメントを宣言する時にkey引数を省略することも可能で、Buttonの場合はButtonのテキスト(今回だと ‘はい’ )がkeyとして設定されます。

しかし、その他のエレメント(InputTextなど)の場合、数値の0から順に昇順でkeyが設定されていきます。だいぶわかりにくくなるので基本的には全てのエレメントでkey引数を指定するのが推奨されているようです。

補足ですが、画面で何をやったらイベントになるかというのはデフォルトで設定されていて、チェックボックスを押す、ボタンを押すなどだいたいイベントっぽいやつはイベントとして認識してくれます。

そうでないものをイベントとして処理したい時は、以下のようにenable_events引数をTrueに設定します。

import PySimpleGUI as sg

# enable_eventsをTrueに
layout = [[sg.Text('PySimpleGUIを使っていますか?', enable_events=True, key='-Text-')],
          [sg.Button('はい', key='-OK-')]]

window = sg.Window('Sample', layout=layout)

while True:
    event, value = window.read()

    print(event, value)
    # 表示されているテキストをクリックすると event = -Text-, value = {}

    if event is None:
        break

window.close()

window.read()で受け取る(key, value)の “value” について

(key, value)で受け取るイベントのうちkeyはイベントの発生源を示していて、実体はエレメント宣言時のkey引数でした。

ではvalueはなにかというと、画面全体の情報です。

イベントが発生した時に、画面の上から下まで全てを読み込んで辞書型のデータを構築しています。具体的なデータの中身は、{key1: 入力値1, key2: 入力値2}のようになっています。

以下のような例で見てみます。

import PySimpleGUI as sg

layout = [[sg.Text('Sample Text')],
          [sg.Text('入力してください', size=(15, 1)), sg.InputText(key='Input-1'), sg.Button('OK', key='-OK-')],
          [sg.Text('入力してね', size=(15, 1)), sg.InputText(key='Input-2'), sg.Button('OKOK', key='-OKOK-')],
          [sg.Submit(), sg.Cancel()]]
          # 'Submit', 'Cancel'というテキストのButtonが標準で用意されています。

window = sg.Window('Sample', layout=layout)

while True:
    event, value = window.read()

    print(event, value)
    # 'OK'が押された時: event = -OK- value = {'Input-1': '', 'Input-2': ''}
    # 'OKOK'が押された時: event = -OKOK- value = {'Input-1': '', 'Input-2': ''}

    if event is None:
        break

window.close()

まずvalueとして読み込まれるのはsg.InputTextのように画面上で入力ができるエレメントです。sg.Textやsg.Buttonなど画面上で編集できないエレメントについては読み込まれません。

続いて読み込まれた際のデータですが、エレメントのkeyとその入力値が{key: 入力値}として辞書に登録されます。なお辞書の登録順は画面の上から下、左から右の順番です。

上記の例ですと、2つのInputTextがそれぞれ登録された辞書がvalueとして返ってきます。

そして当然ですが、画面全体を読み込んでいるので、イベントがどこで発生しても受け取るvalueは同じです。

valueの中身が変わるのは、下図のように画面上で入力が行われた後にイベントが発生した時です。

この画面でOKを押すとevent = -OK-, value = {‘Input-1’: ‘競プロ楽しい’, ‘Input-2’: ”}となります。

イベントを振り分ける

ここまでの内容さえ分かってしまえばあとは簡単ですね。

event変数の中に、イベントの発生源を示す値が入っているので、適宜やりたいことを振り分けていけば良いです。

If文で愚直にやる

チュートリアルなどではこの実装が多いですね。

eventに入っているkeyを確認して条件分岐させるだけです。

import PySimpleGUI as sg


def ok_func():
    pass  # 実際はここに実現したい処理を書く


def okok_func():
    pass  # 実際はここに実現したい処理を書く


def submit_func():
    pass  # 実際はここに実現したい処理を書く


layout = [[sg.Text('Sample Text')],
          [sg.Button('OK', key='-OK-')],
          [sg.Button('OKOK', key='-OKOK-')],
          [sg.Submit(), sg.Cancel()]]
          # 'Submit', 'Cancel'というテキストのButtonが標準で用意されています。

window = sg.Window('Sample', layout=layout)

while True:
    event, value = window.read()

    # eventに代入されているkeyに応じて処理を振り分けていく
    if event in [None, 'Cancel']:
        break
    
    elif event == '-OK-':
        ok_func()
    
    elif event == '-OKOK-':
        okok_func()
        
    elif event == 'Submit':
        submit_func()
        

window.close()

handlerを用意する

難しい言葉を覚えるとすぐに使いたくなる悪い癖があるんですが、handlerまたはdispatcherという役割を辞書型で実現することでも、イベントの振り分けが可能です。

かっこつけましたが、実行する関数と対応するkeyをセットにして辞書として宣言するだけです。

handlerとdispatcherって同じようなものという認識であってるんですかね??(OSのディスパッチャと混同するからhandlerの方がいいのかな?・・・)

import PySimpleGUI as sg


def ok_func():
    pass

def okok_func():
    pass

def submit_func():
    pass


# 使いたい関数とkeyをセットにして辞書にするだけ
handler = {
    '-OK-': ok_func,
    '-OKOK-': okok_func,
    'Submit': submit_func,
}


layout = [[sg.Text('Sample Text')],
          [sg.Button('OK', key='-OK-')],
          [sg.Button('OKOK', key='-OKOK-')],
          [sg.Submit(), sg.Cancel()]]


window = sg.Window('Sample', layout=layout)

while True:
    event, value = window.read()

    print(event, value)
    if event in [None, 'Cancel']:
        break
    
    function = handler[event]  # handlerからeventに応じた関数を呼び出す
    function()

window.close()

こっちの方がスッキリするかもしれませんね。あとなんか上級者っぽくてかっこいい!

まとめ

  • window.read()でイベントを受け取る
  • イベントで受け取った(key, value)でよしなにやる
  • 各エレメントにはしっかりkeyを設定する
  • handlerを使ってイベントの振り分けをやるとかっこいいかも

コメント

  1. Mike より:

    Thank you for writing SO much about PySimpleGUI. I wish I had more time to study and understand everything you’ve written. I’ll eventually get the time to study it in more detail, but for now that best I can do is thank you for taking the time to teach others about PySimpleGUI. I really appreciate the time you’ve put into presenting your ideas and observations.

    • parsely1231 より:

      No, thank YOU very much!
      Your GUI package is great.
      Next time I make a GUI application, I will surely use PySimpleGUI.

  2. Mike より:

    It’s been a while since I saw this tutorial…. in a Udemy course I’m recording, I talk about several dispatchers that can be used to handle events.

    You could take your example and make it even simpler by using the function as the key. Keys can be anything. If you wanted to have both a string and the function, make it a tuple.

    “`python
    import PySimpleGUI as sg

    def ok_func():
    print(‘ok func’)
    pass

    def okok_func():
    print(‘okok func’)
    pass

    def submit_func():
    print(‘submit func’)
    pass

    layout = [[sg.Text(‘Sample Text’)],
    [sg.Button(‘OK’, key=ok_func)],
    [sg.Button(‘OKOK’, key=okok_func)],
    [sg.Submit(key=(‘-SUBMIT-‘, submit_func)), sg.Cancel()]]

    window = sg.Window(‘Sample’, layout=layout)

    while True:
    event, value = window.read()

    print(event, value)
    if event in (sg.WIN_CLOSED, ‘Cancel’):
    break

    if callable(event):
    event()
    elif isinstance(event, tuple):
    print(f’your string key = {event[0]}’)
    event[1]()

    window.close()
    “`

    • Mike より:

      Oh… no… the code didn’t format right… sorry… trying again…

      import PySimpleGUI as sg

      def ok_func():
      print(‘ok func’)
      pass

      def okok_func():
      print(‘okok func’)
      pass

      def submit_func():
      print(‘submit func’)
      pass

      layout = [[sg.Text(‘Sample Text’)],
      [sg.Button(‘OK’, key=ok_func)],
      [sg.Button(‘OKOK’, key=okok_func)],
      [sg.Submit(key=(‘-SUBMIT-‘, submit_func)), sg.Cancel()]]

      window = sg.Window(‘Sample’, layout=layout)

      while True:
      event, value = window.read()

      print(event, value)
      if event in (sg.WIN_CLOSED, ‘Cancel’):
      break

      if callable(event):
      event()
      elif isinstance(event, tuple):
      print(f’your string key = {event[0]}’)
      event[1]()

      window.close()

      • parsely1231 より:

        Thanks for the great information.
        That method looks very nice to me!
        Since it’s been so long since I wrote this article, I’ll take this opportunity to check out the new version of PySimpleGUI and update the article as well.

タイトルとURLをコピーしました