はじめに
PySimpleGUI
でのアプリケーション作成時に
複数タブ切り替え時に、どのタブを選択したか参照したい時にちょっと手間取ったのでメモ
PySimpleGUIとは
PythonのGUI作成ライブラリ
PythonではGUI作成を目的とした標準ライブラリとしてtkinter
が組み込まれており、PySimpleGUI
はそのラッパー
tkinter
と比べ直感的にコーディングすることができ、記述量も短いのが特徴
個人的には、公式リファレンスがとても見やすくサンプルも豊富で情報量が多いのが助かります
やりたいこと
- 1つのアプリケーション内で複数機能が存在、機能ごとにタブでレイアウトを切り替える
- どんな機能なのかを表す画像をいい感じに表示、タブ切り替え時に画像も切り替えたい
下図のようなイメージです
とりあえず動かしてみる
自分で1からコーディングするのは手間なので、公式リファレンスのCookbookから参考にできそうなサンプルを動かしてみます
import PySimpleGUI as sg
tab1_layout = [[sg.T('This is inside tab 1')]]
tab2_layout = [
[sg.T('This is inside tab 2')],
[sg.In(key='in')]
]
layout = [
[sg.TabGroup([[
sg.Tab('Tab 1', tab1_layout),
sg.Tab('Tab 2', tab2_layout)
]])],
[sg.Button('Read')]
]
window = sg.Window('My window with tabs', layout)
while True:
event, values = window.read()
print(event,values)
if event == sg.WIN_CLOSED: # always, always give a way out!
break
動作させるとこんな感じ
タブの切り替えができていますね
上記コードはTabgroup
要素を使った最小構成です
これをちょっといじってアプリケーションを作ります
できたもの
import PySimpleGUI as sg
from PIL import Image
import io
# 画像をリサイズ、bytes形式で返す
def load_bytesImg(img_path, size_ratio=1):
img = Image.open(img_path)
resize_img = img.resize((int(img.width*size_ratio), int(img.height*size_ratio)))
output = io.BytesIO()
resize_img.save(output, format="png")
return output.getvalue()
# タブごとのレイアウト
text_width, text_height = 40, 5
tab1_layout = [[sg.Text( 'tab1です', size=(text_width, text_height) )]]
tab2_layout = [[sg.Text( 'tab2です', size=(text_width, text_height) )]]
tab3_layout = [[sg.Text( 'tab3です', size=(text_width, text_height) )]]
# 画像データをbytes形式で用意
img = load_bytesImg("./images/func01.png", size_ratio=0.3)
# 全体のレイアウト
layout = [
[sg.Image(data=img, pad=((220, 10), (0, 0)), key="image")],
[sg.TabGroup([[
sg.Tab('Tab 1', tab1_layout, key="tab1"),
sg.Tab('Tab 2', tab2_layout, key="tab2"),
sg.Tab('Tab 3', tab3_layout, key="tab3")
]], key="tab_group", enable_events=True)],
[sg.Output( size=(40, 5) )]
]
window = sg.Window('TabGroup sample', layout, margins=(20,20))
# イベントループ
while True:
event, values = window.read()
# ウィンドウを閉じたら終了
if event == sg.WIN_CLOSED:
break
# タブ切り替え
elif event=="tab_group":
select_tab = values["tab_group"]
img_name = ""
if select_tab=="tab1":
print(f"select tab1")
img_name = "func01.png"
elif select_tab=="tab2":
print(f"select tab2")
img_name = "func02.png"
elif select_tab=="tab3":
print(f"select tab3")
img_name = "func03.png"
# 表示する画像を更新
img = load_bytesImg(f"./images/{img_name}", size_ratio=0.3)
window["image"].update(data=img)
動作例はこんな感じ
右上に表示する画像はimages
ディレクトリ内に配置しています
ディレクトリ構成はこんな感じ
├─tabgroup.py
├─images
├─func01.png
├─func02.png
├─func03.png
以下、コードの解説です
レイアウト定義部分
# タブごとのレイアウト
text_width, text_height = 40, 5
tab1_layout = [[sg.Text( 'tab1です', size=(text_width, text_height) )]]
tab2_layout = [[sg.Text( 'tab2です', size=(text_width, text_height) )]]
tab3_layout = [[sg.Text( 'tab3です', size=(text_width, text_height) )]]
# 画像データをbytes形式で用意
img = load_bytesImg("./images/func01.png", size_ratio=0.3)
# 全体のレイアウト
layout = [
[sg.Image(data=img, pad=((220, 10), (0, 0)), key="image")],
[sg.TabGroup([[
sg.Tab('Tab 1', tab1_layout, key="tab1"),
sg.Tab('Tab 2', tab2_layout, key="tab2"),
sg.Tab('Tab 3', tab3_layout, key="tab3")
]], key="tab_group", enable_events=True)],
[sg.Output( size=(40, 5) )]
]
PySimpleGUI
にて、レイアウトは基本的にlist
形式で定義します
今回使用しているものは以下の通り
-
Text
- テキストを表示する
-
size
で幅と高さを指定
-
Image
- 画像を表示する
- 画像は パス指定 or
bytes
形式 で渡す -
padding
で右寄せにしている
-
TabGroup
- タブ切り替え要素
-
Output
- 標準出力要素
-
print
などでテキストを表示することができる
要素の役割や引数などはCall referenceで確認できます
注意しなければならないのがTabGroup
要素のenable_events
で、この値をTrue
にしておかなければタブの切り替え時にイベントを検知できません(デフォルトではFalse
)
また、load_bytesImg()
に関しては後述にて解説します
イベントループ部分
window = sg.Window('TabGroup sample', layout, margins=(20,20))
# イベントループ
while True:
event, values = window.read()
# ウィンドウを閉じたら終了
if event == sg.WIN_CLOSED:
break
# タブ切り替え
elif event=="tab_group":
select_tab = values["tab_group"]
img_name = ""
if select_tab=="tab1":
print(f"select tab1")
img_name = "func01.png"
elif select_tab=="tab2":
print(f"select tab2")
img_name = "func02.png"
elif select_tab=="tab3":
print(f"select tab3")
img_name = "func03.png"
# 表示する画像を更新
img = load_bytesImg(f"./images/{img_name}", size_ratio=0.3)
window["image"].update(data=img)
window
オブジェクトを定義、ここでタイトルバーのテキストと先程定義したレイアウトを設定します
全体の流れとしては
イベントループで常にwindow.read()
を呼び出し、返されたevent
でイベントを検知、values
でwindow
内の要素を参照、更新する、という感じです
要素へのアクセスはvalues["keyで指定した文字列"]
という形式で行えます
今回の例では、layout
定義にてkey="tab_group"
と指定しているのでvalues["tab_group"]
で要素へのアクセスができます
この値が何なのかでどのタブを選択しているかを検知することができます
選択したタブに応じた画像ファイル名を指定、要素を更新し表示に反映させます
画像データのbytes変換部分
Image
要素での画像指定は、ファイルのパス(文字列)またはbytes
形式で行いますが
画像のリサイズ機能は無いみたいです
画像の表示範囲は指定できても、画像自体のリサイズはできないので
元々レイアウトに合わせた画像を用意する必要があり、ちょっとめんどくさいです
サイズが合ってない画像だとこうなります
表示範囲を大きくはみでてますね
別ツールで画像をリサイズして保存して...とやるのは非効率なのでアプリ内処理でリサイズくらいはやりたいです
今回はload_bytesImg()
関数がその処理部分に当たります
# 画像をリサイズ、bytes形式で返す
def load_bytesImg(img_path, size_ratio=1):
img = Image.open(img_path)
resize_img = img.resize((int(img.width*size_ratio), int(img.height*size_ratio)))
output = io.BytesIO()
resize_img.save(output, format="png")
return output.getvalue()
この関数でやってることは
- 画像を
Pillow.Image
オブジェクトとして読み込み -
Image.resize
メソッドでリサイズ -
io
モジュールのByteIO
メソッドでbytes
形式に変換して返す
です
引数size_ratio
の値で拡大縮小倍率を指定しています 画像のアスペクト比は固定です
まとめ
PySimpleGUI
を用いて複数機能を搭載した簡単なインタフェースを作成できました
TabGroup
要素によるタブ切り替え時のイベント検知、画像のリサイズについて言及している記事があまりなかったので自分用としてまとめてみました