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を使ってイベントの振り分けをやるとかっこいいかも
コメント
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.
No, thank YOU very much!
Your GUI package is great.
Next time I make a GUI application, I will surely use PySimpleGUI.
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()
“`
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()
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.