PySimpleGUIでデスクトップアプリを作ってみたので日記も兼ねてこんな事出来ますよというのを主に実装面について書きなぐっていきます。第2弾はオブジェクト指向への挑戦とViewの実装についてです。
一応先に断っておくと、私はシステム開発についてはド素人です。設計やオブジェクト指向を勉強しながら、実践もかねてこんな考えでやってみましたという日記なのでご注意ください。
MVPアーキテクチャ
GUIアプリを作り始めたころ、以前にWebアプリを少しかじったことがあったので、その時に勉強したMVCモデルで色々と設計を考えて実際コードを書いていました。
しかしどうにもしっくりこないというか、コードが分かりにくいし書きにくかったので、なんでかなぁと色々調べていたらそもそもMVCモデル以外にも色々な設計思想があるということがわかりました。
見つけたアーキテクチャの中で一番しっくりきたのがMVPモデルというもので、ModelとViewをがっつり切り離して、その間をPresenterが仲介するというものでした。
まぁ参照する記事が変わると同じアーキテクチャでも言ってることが変わったりするのでMVPという名前にあまり意味はないのかもしれないですが、とにかく中途半端にModelとViewの繋がりを持たせずに情報のやりとりを仲介役に全て任せるという思想が個人的にはしっくりきた次第です。
何が本当の正解なのかはわかりませんが、システム開発をやりやすくするのが本来の目的だと思うので、自分的にしっくりくる思想でコードを書くというのはありなのかなと思います。(個人開発に限ると思いますが・・・・)
Viewの実装
今回Viewは以下の3つのファイルで構築しました。
- view.py:メイン画面のレイアウト
- style.py:各画面要素の色やサイズ(HTMLのCSS的なポジション)
- popup.py:メインから必要時に呼び出されるポップアップ(その1で紹介)
view.py
メインの画面は以下のように実装しました。画面全体は4つのフレーム(input, preview, edit, output)で構成されていて、それぞれのフレームを個別に宣言したあと、最後にlayoutの中で結合させました。
各フレームが独立しているので編集も簡単ですし、何か大きめのものを追加する場合は新たにフレームを作ることで対応できそうかなと思っています。あと個人的には結構見通しが良くなったと思っています。
また、通常ウィジェット(ボタンやテーブル等)を宣言する時に、文字の色やサイズ等を指定する必要があるんですが、いちいち書いているとコードがごちゃごちゃしてきてすごく見にくくなったので、HTMLに習って、スタイルを指定するコードは別ファイルに書くことにしました。(画面に表示させる文章自体はview.pyに書いています)
後述するstyle.pyに辞書型で宣言しておくことで、キーワード引数としてそのまま突っ込めるので、全体的にスッキリしてメイン画面の全容を掴みやすくなった気がしています。
import PySimpleGUI as sg
import typing
from views.style import *
class InterFace:
    def __init__(self):
        self.window: typing.Optional[sg.Window] = None
        self.input_frame = [sg.Frame('Input', font='Any 15', layout=[
                    [sg.Text('Input Text File(Original Format)  or  ASCII Files', **input_text_style)],
                    [sg.Button('Input Text', **input_button_style)],
                    [sg.Button('Input ASCII', **input_ascii_button_style)]
                    ])]
        self.preview_frame = [sg.Frame('Preview', font='Any 15', layout=[
                    [sg.Listbox(**list_box_style), sg.Table(**table_style)]
                    ])]
        self.edit_frame = [sg.Frame('Edit', font='Any 15', layout=[
                    [sg.Button('Calc RRT', **calc_rrt_btn_style),
                     sg.Button('Set Peak Name', **peak_name_btn_style),
                     sg.Button('Set Exclude', **exclude_btn_style)]
                    ])]
        self.output_frame = [sg.Frame('Output', font='Any 15', layout=[
                    [sg.Button('Export Excel File', **export_btn_style)]
                    ])]
        self.layout = [self.input_frame,
                       self.preview_frame,
                       self.edit_frame,
                       self.output_frame]
    def show_window(self):
        self.window = sg.Window(title='LC Data Manager', layout=self.layout, **window_style)
    def close_window(self):
        self.window.close()
style.py
既に述べましたがウェブサイトでいうところのCSS的なポジションとしてファイルを分けてみました。中身自体は簡単で、各ウィジェットのスタイルを指定するための引数名とその値を辞書型で実装しているだけです。
# -------------Window--------------
window_style = {
    'auto_size_text': False,
    'auto_size_buttons': False,
    'default_element_size': (20, 1),
    'text_justification': 'right',
}
# -----------Input Frame-----------
input_text_style = {
    'font': (None, 12),
    'justification': 'left',
    'size': (40, 1),
}
input_button_style = {
    'size': (15, 1),
    'key': '-InputText-',
}
input_ascii_button_style = {
    'size': (15, 1),
    'key': '-InputASCII-',
}
# -----------Preview Frame-----------
list_box_style = {
    'values': [],
    'font': (None, 12),
    'select_mode': 'LISTBOX_SELECT_MODE_SINGLE',
    'enable_events': True,
    'size': (20, 20),
    'key': '-SampleList-',
}
table_cols = ["Name", "RT", "RRT", "Area", "Area%", "ex-Area%"]
cols_width = [20, 20, 20, 40, 40, 20]
def_data = [['xxxxxx', '0.000', '0.000', '00000000', '00.0', '00.0']]
table_style = {
    'values': def_data,
    'headings': table_cols,
    'max_col_width': 200,
    'def_col_width': cols_width,
    'num_rows': 15,
    'auto_size_columns': True,
    'key': '-TABLE-'
}
# -----------Edit Frame-----------
base_rt_entry_style = {
    'width': 40,
}
calc_rrt_btn_style = {
    'size': (15, 1),
    'key': '-CalcRRT-',
}
peak_name_btn_style = {
    'size': (15, 1),
    'key': '-PeakName-',
}
exclude_btn_style = {
    'size': (15, 1),
    'key': '-Exclude-',
}
# -----------Output Frame-----------
export_btn_style = {
    'button_color': ('white', 'blue'),
    'size': (20, 1),
    'key': '-Output-',
}popup.py
その1で紹介した通りです。コードの詳細は前記事をご覧ください。
すこし複雑めのポップアップを実装したかったので、ポップアップ用のファイルを別に用意して実装を行いました。
ポップアップ画面についてもView, Presenterあたりで分けることも考えたんですが、逆にわかりにくくなりそうだったので今回はポップアップは独立させてそれ自体で処理を完結させています。(こういう部分的に例外をつくるのはあんまりよくない気がしますね。うーむ・・・・)
ポップアップ画面がもっとたくさん増えるか、あるいはポップアップ画面で行う処理がもっと複雑になる場合はもっとやり方を考えないといけないかなと思っています。
できあがった画面
最終的にはこんな画面になりました。
細かいデザインにこだわらなければ、これくらいの画面であれば簡単に実装できてしまうのでPySimpleGUIはすげえです。

まとめ
- MVPモデルというのを勉強して採用してみた
- メイン画面はフレームごとに分けて実装してみた
- スタイルの指定を別ファイルにしてみた(キーワード引数便利)
- ポップアップは独立に実装してみた
以上Viewの実装でした。画面の表示に関してはこの3つのファイルで完結していますし、中身の処理に一切気を配らなくても画面の実装ができているので、ModelとViewの分離には成功しているのかなと思います。
まぁそもそもPySimpleGUI自体が、画面の設計と中身の処理が分離できるよう作られているだけな気もしますね・・・・・・・
次回はもし書けたら、画面からの情報をPresenterで処理する部分を紹介できればと思います。
 
  
  
  
  


コメント