Pythonを使ったGUIアプリを「お手軽に」作りたかった件

# はじめに
業務自動化といえばRPAとかPython等が良く使われるが、これらは基本的に判断が多い複雑な業務の自動化には向かず、やったとしても自動実行→手作業→自動実行→手作業…と言ったように人の判断が居るところで業務プロセスが分断されてしまうのが悩みどころ。
そこで「GUI上で自動実行前に手作業」＋「バックグランドで自動実行」と両方の機能を持ち、業務を分断する事なく作業できるアプリを作ろうと考えた。

結論から言うと「Flet」を採用した。以下経緯説明。

# お手軽なGUIライブラリを探す旅

## Pythonはデスクトップアプリ開発には不向き？
アプリを開発するにしろメインの処理は自動化なので、アプリ開発に特化したフレームワークではなく、Pythonを使用出来る柔軟性の高そうなGUIライブラリが無いか探すことにした。
巷でたまに耳にするが、デスクトップアプリ開発はC#やJavaが多くPythonは環境が貧弱だという声もある。

確かにデスクトップアプリではPythonはメジャーな言語ではないが、かといって「機能的に」C#が絶対的優位かというとそういう訳でもなく(WindowsFormsとWPFを1~2年触った程度の私見ですが)、むしろ最近はWPFやUWPがあまり浸透しなかったお陰で、C#の勢いが下火になっている感もある。

あと個人的にGUI上で部品を配置していくVisualStudioより、VSCodeのテキストベース開発の方が性に合ってるので割と好みの問題感があるかも。

## デザイン面での話
個人的に技術選定をする上で最も重視しているのがUIの「見た目」だった。(たかが業務アプリに拘りを持つなとも言われそうだが)
「Python GUI」と調べたらよく出てくるTkinter・PyQt・wxPythonなどは見た目が好みにヒットしなかったので真っ先に候補から消えた。
WindowsFormsもデフォルトの見た目が気に入らず一から画像を用意してGUIを作り上げた経験があり、面倒なのが分かっていたので除外。

## 学習コストの話
### Kivy
まず最初に目に入ったのは、Pythonで比較的モダンなGUIを作れるというKivy。
ただ、今回は「お手軽」にGUIを作りたいので、その身からすれば「Kv言語」の学習コストが高いし、採用したとして社内に使いこなせる人が少なそうなので除外。

### WPF
WindowsFormsの後継で、XAML形式でカスタム性の高いGUIを作成できる。半年前位に一度触れてみたが、とにかく公式ドキュメントが分かりづらいという難点に頭を悩ませられた。後C#のフレームワークなのでPythonとの連携をさせるのが少し面倒。除外。

# 最終候補
## 1.Electron
https://www.electronjs.org/ja/docs/latest

個人的に一押しのデスクトップフレームワーク。何より(元)フロントエンドエンジニアからすると、GUIをHTML+CSSやReact等のjsフレームワークで構築できるのが非常にありがたい。
UIとビジネスロジック両方をJavaScriptで書けるというのも、コードや型定義(TS)等が流用できて非常にありがたい。
ただしPythonを使うとなると逆にビジネスロジックはJavaScriptではなくPythonで書きたくなる。
何故なら言語を跨ぐと業務ロジックがコードから非常に読み解きにくくなるからである。
保守性を考えると、今回の案件だと少し難があると感じた。
後Pythonしか扱えないエンジニアにとってはHTML+CSS+JavaScript、追加で自分はTypeScriptとReactを使っているので学習コストがメチャ高いのも難点。社内の保守メンバーを考えるとあまり積極的に採用できる状況では無かった。

## 2.Eel
https://qiita.com/inoory/items/f431c581332c8d500a3b

簡単に言うとElectronのmainプロセス(GUI以外の部分)がPythonになったフレームワーク。
バックグランドがPythonなのでElectronよりは大分導入ハードルが下がったが、そもそも今回の案件は、React等を使って複雑なUIを開発するというよりも、静的な入力フォームを何個か用意するだけの小規模プロジェクトであるので、web技術を使えるメリットがあまり生かしきれないと感じた。もっとお手軽さが欲しいという事で保留にする事に。

## 3.CustomTkinter
https://qiita.com/key353/items/e59927dcf20916f9d4ac

序盤で弾いたtkinterを元にして作られたライブラリ。まず何よりデフォルトの部品(ウィジット)の見た目が良く、ファーストインプレッションは非常に宜しかった。かつプロパティを弄って角の丸さや色を弄れる等、カスタム性も高くなっている。
かつtkinterと同じようにお手軽さも兼ね備えており、今回の案件に向いていると判断した。
そんなこんなで最初はこれを用いて開発をしていたが、途中で後述のFletに乗り換える事に。
### 理由1. レイアウト調整が思った通りにいかない
元々tkinterはレイアウト調整に癖があり、それをCustomTkinterも受け継いでしまっている。
widthとheightでサイズ指定しても、子要素を入れるとそれに合わせて縮小してしまう(特定のプロパティを設定すれば対処できるが毎回やるのが面倒)所とか、CSSで言うflexbox的なものが無く全部FrameとGridを細かく用意してレイアウトしないといけない…等、お手軽さを求めてたのに思ったより手間がかかる羽目に。

### 理由2. UIのツリー構造が扱いづらい
HTMLやXAMLはツリー構造を使って記述する言語なので、一目見ただけでUIの構造が分かるがtkinter(CustomTkinter)はそうはいかない。具体的なコードで示してみる。
```python:ctk_example.py
import customtkinter as ctk

class App(ctk.CTk):
    def __init__(self):
        super().__init__()
        #アプリ本体の設定
        self.fonts = ('Meiryo UI', 15)
        self.geometry("1000x800")
        self.title("CustomTkinter App")
        #ウィジットの配置
        self.textbox = ctk.CTkEntry(master=self, width=220, font=self.fonts) #master=selfで親要素に接続。この段階では表示されない
        self.button = ctk.CTkButton(master=self, text="Button", font=self.fonts)
        self.textbox.place(x=10, y=10)# このコードで配置する。事前に親要素に接続した上で指定する必要がある
        self.button.place(x=100, y=10)

if __name__ == "__main__":
    app = App()
    app.mainloop()#アプリ起動
```




tkinterでアプリを作る際はこのようなコードになるのだが、一番の問題点はコード上で親子関係が全く見えてこない事である。tkinterでは「子要素上」で接続先の親要素を指定する必要があるので、ウィジット同士が相互依存・つまり蜜結合になりやすくメンテ性が悪くなる。
とはいえ、1画面+数個のウィジットしか持たない簡易アプリを作る上では気にならないと思うので、規模感的に今回の案件には合わなかったという事である。

### 理由3. デザインのカスタマイズ性に限界があった
それっぽい見た目のアプリを手軽に作れる点においては最強のCustomTkitnerだが、少しデザインに拘ろうとした段階で早くも細かな欠点が見えてきた。
具体的には、テーブルが無い(tkinter.ttk内のTreeViewで代用できるが、見た目が古い・カスタム性が悪い)、ボタンやフレームに影が付けられない、アニメーションが標準搭載されていないなど、思ったより機能に制限があり、思った通りにGUIを作る事が出来なかった。

結果、採用したのはFlet君でした。

# 結果、Fletを採用
https://qiita.com/NasuPanda/items/48849d7f925784d6b6a0

■ 公式ドキュメント

https://flet.dev/docs/

CustomTkinterに挫折した結果、最近少し話題になっているFlutterUIをPythonで扱えるフレームワーク「Flet」で出来るかどうか試してみる事にした。

FletはUI部品の事を「Control」と言い、「Control」内には他のControlを入れる事が出来る。つまり構造的にはHTMLと一緒の事が出来るのだ。

```python:flet_example.py
import flet as ft

def home():
    #このようにControl内に他のControlを入れる事が出来る。
    return ft.View("/home", [
        ft.Text('click')
        ft.ElevatedButton(content=text, on_click=lambda _: print('clickされたよ'))
    ])

def main(page: ft.Page):
    # レイアウト
    page.title = "Navigation and routing"
    page.window_width = 1000
    page.window_height = 800

    # 初期表示
    page.view.append(home())#ホーム画面(view)を表示

ft.app(target=main)
```


その他にも
- CustomTkitnerでは出来なかった影の描画やアニメーションが簡単に実現可能
- flexbox感覚でレイアウト調整できるContainer,Column,Row コントロールの存在
- 標準のUI部品が優秀でほぼ弄る必要がない
- ホットリロードに対応している
- マルチプラットフォーム対応。かつデスクトップアプリ/webアプリ両方にデプロイ可能

といった点も非常にgood。
以前よりもUIのカスタムコードを書くケースが減らせた上に、UIを変更した時にいちいち「Ctrl+Cをして強制終了→コマンド叩いて再起動」 という面倒な作業から解放されて作業効率がUP。

学習コストについても、公式ドキュメントも結構分かりやすく、構文もシンプルなので個人的にtkinterとほぼ変わらない学習コストであると感じた。
多少学習コストがtkinterよりかかったとしても、学んだ後の作りやすさは桁違いなので、かなり有望なフレームワークであるのではないだろうか。

# まとめ
Fletは、今まで触れてきたGUIフレームワーク/ライブラリの中で最もコストパフォーマンスに優れたフレームワークだった。
勿論、機能やデザインのカスタマイズ性はElectronやEel、情報量の多さではtkinterが優勢であるため、Fletを採用するかどうかはケースバイケースであるが、検討候補に入れる価値は十分にあるかと思われる。

以上、Pythonを使ったGUIアプリを「お手軽に」作りたかった件でした。