Post

fletでGUIアプリをつくってみよう

flet とは

flet とは、Python で書いたコードを GUI (Graphical User Interface; コマンドではなくボタンなどをポチポチしてつかえる一般的なソフト) で動かすことができるライブラリです。

Windows や Mac などのPCだけでなく、iOS や Android といったモバイルでも動くアプリをつくることができます。

ちなみに flet の元になっているのは Flutter という Google がつくっている GUI のフレームワークです。Dart という Web 系の言語でつかえるらしい。

なにがいいの?

flet の利点は「かんたんにオシャレなアプリをつくれる」という点にあると思っています。

普段から使い慣れている Python のコードにちょっと手を加えるだけで、モダンなデザイン (マテリアルデザイン) のアプリをつくることができます。

alt

また、Python で GUI アプリをつくるライブラリとしては標準ライブラリの tkinter というものもありますが、正直これはダサい。シンプルなんですがあまりに古臭いので……。

alt

どういうときに使うの?

Python で書いたスクリプトをそのまま実行することはよくあると思いますが、そのスクリプトを実行するために長いコマンドを書かなければいけないとなると面倒ですよね。

なんらかのファイルを処理するものなら、例えばファイルの入力パスと出力パス、処理のパラメータなどを1行にまとめてターミナルなりコマンドプロンプトなりに書かなければいけません。

また、自分だけではなく、特に非エンジニアや専門外のひとにつかってもらうスクリプトやツールであれば、誰でもつかえるようにしておくことに越したことはありません。

実際、以前の投稿で紹介したこともありますが、IVRC展示にあたって必要と考えた Quest のミラーリングや展示アシストにつかったツールは、この flet を用いて GUI アプリ化したことで誰でもかんたんにつかえることを目指していました。 (実際どうだったのかな……)

実際につくってみよう

ものは試しということで実際に flet をつかったアプリをつくっていきましょう。

ここでは WSL2 (Windows Subsystem for Linux) の上で作業をしていますが、Python が動けば Windows でもなんでもOKです。

2024/12/24訂正

flet はマルチプラットフォームに対応しているのですが、ビルドする pyinstaller はその環境のファイルしかビルドできません。 Windows で動く .exe ファイルをつくりたいときは Windows 上でビルドするようにしてください

1. 環境構築

今回つかうライブラリをインストールします。

pip install -U flet pyinstaller

flet は上記の通り GUI を作成するためのライブラリ、pyinstaller はそれをバイナリファイル (.exe など) として書き出すためのライブラリです。

alt

2. アプリの土台をつくる

ライブラリの準備ができたら flet のプロジェクトフォルダを作成します。

flet create <PROJECT NAME>

<PROJECT NAME> のところは自分の好きな名前にしてください。それがプロジェクトフォルダの名前になります。

ちなみにここを flet create . とすると今のフォルダ内にいろいろなファイルが生成されます。

以下のような構造でファイルが生成されるはず。

1
2
3
4
5
6
7
8
9
├── README.md
├── pyproject.toml
├── src
│   ├── assets
│   └── main.py
└── storage
    ├── README.md
    ├── data
    └── temp

この中の src/main.py がアプリ本体になります。とりあえずサンプルがあるので実行してみましょう。

python3 src/main.py もしくは flet run src/main.py で実行できます。

alt

右下のボタンを押すとカウントが増えていくだけのアプリですね。ボタンのデザインがマテリアルデザインでとてもいい。

ついでにコードも見てみましょう。

def main(page: ft.Page): として定義されている main 関数の中でアプリとして表示される部分を構成します。

その中に def increment_click(e) と定義されているのが、ここでは値を +1 するという処理です。ここで +1 される値は counter.value となっていますが、これは先に counter = ft.Text("0", size=50, data=0) として宣言されているテキスト部分の値を示しています。

つまり、ft.Text とすることで文字を表示することができ、.value というメソッドをつけることでその値にアクセスできるということですね。

Text

では試しに次のようにコードを書き足してみましょう。page.title = "Counter"

alt

アプリを実行してみるとどうでしょう、ウィンドウ上部に名前がつきますね。

alt

このように、page という単位で画面があり、そこに page.title のように属性をつけることで、ウィンドウそのものをカスタムしていくことができます。

3. 実用的なものをつくってみる

ただ数字を数えるだけの暇人専用アプリをいじるのもいいですが、もうちょっと実用的なものをつくってみましょう。

前回の記事で紹介した、PDFファイル内にある画像をまとめて書き出すスクリプトを GUI アプリにしていきます。

元のコードはこんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import os, sys
import fitz

def main(fname):
  dist = os.path.splitext(fname)[0] # 画像保存先ディレクトリ

  os.makedirs(dist, exist_ok=True) # ディレクトリが存在しない場合は作成

  with fitz.open(fname) as doc:
    for i, page in enumerate(doc):
      for j, img in enumerate(page.get_images(full=True)):
        x = doc.extract_image(img[0])
        name = os.path.join(dist, f"{i:04}_{j:02}.{x['ext']}")
        with open(name, "wb") as ofh:
          ofh.write(x['image'])

if __name__ == "__main__":
  main(sys.argv[1]) # 引数でファイルを受け取る

このスクリプトは引数でファイルを受け取って処理するようにしているので、例えば python3 main.py ./my-paper.pdf のようにつかいます。

さらに、このスクリプトでは PyMuPDF というライブラリをつかっているので、スクリプトを使いたいとなるとわざわざインストールしなければなりません。

GUI アプリにしてパッケージ化してしまうことで、それらの手間をなくして、どこでもつかえるようになります。

ではでは早速。

まず、main() 関数はアプリ画面を構成するためにつかうので、上記の処理は関数として外に出しておきます。ついでにライブラリも入れておきましょう。

pip install -U pymupdf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import os, sys
import fitz # PyMuPDF のこと
import flet as ft # 慣例的に flet は ft としておきます

def extract_images(fname):
    dist = os.path.splitext(fname)[0] # 画像保存先ディレクトリ
    os.makedirs(dist, exist_ok=True)  # ディレクトリが存在しない場合は作成

    with fitz.open(fname) as doc:
        for i, page in enumerate(doc):
            for j, img in enumerate(page.get_images(full=True)):
                x = doc.extract_image(img[0])
                name = os.path.join(dist, f"{i:04}_{j:02}.{x['ext']}")
                with open(name, "wb") as ofh:
                    ofh.write(x['image'])

続いて、アプリのメイン画面において、「入力ファイルの選択」と「画像を抽出する処理の実行」を行う関数とボタンを追加します。

まず関数がこちら。

1
2
3
4
5
6
7
8
9
10
    def on_file_picker_result(e: ft.FilePickerResultEvent):
        if e.files:
            file_path.value = e.files[0].path
            page.update()

    def on_extract_click(e):
        if file_path.value:
            extract_images(file_path.value)
            result.value = "画像の抽出が完了しました。"
            page.update()

e: ft.FilePickerResultEvent というのが見慣れませんが、後述するボタンを押したあと、ピックしたファイルの情報が e に入ると思ってください。

そしてボタンの方ですが、このように書くことで設定できます。

1
2
3
4
5
6
7
    file_picker = ft.FilePicker(on_result=on_file_picker_result) # ファイル選択を行うファイルピッカー
    file_path = ft.TextField(label="選択されたファイル", read_only=True) # 選択したファイルのパスを表示するエリア (読み取り専用)
    pick_file_button = ft.ElevatedButton(text="ファイルを選択", on_click=lambda _: file_picker.pick_files()) # 上記ファイルピッカーを起動するボタン
    extract_button = ft.ElevatedButton(text="画像を抽出", on_click=on_extract_click) # 抽出する処理を実行するボタン
    result = ft.Text() # 処理結果を表示するテキストエリア (最初は何も表示しない)

    page.overlay.append(file_picker) # ファイルピッカーを使用するために必要な要素

ここで設定したボタンを画面に表示するために、どの順番で GUI に配置するかを指定します。

1
    page.add(pick_file_button, file_path, extract_button, result)

左から順番に、上から下に配置するようになります。今回の場合だと、ファイル選択ボタン、ファイルパス表示エリア、抽出処理実行ボタン、結果表示エリア の順番ですね。

この時点で main() はこのようになっていると思います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def main(page: ft.Page):

    def on_file_picker_result(e: ft.FilePickerResultEvent):
        if e.files:
            file_path.value = e.files[0].path
            page.update()

    def on_extract_click(e):
        if file_path.value:
            extract_images(file_path.value)
            result.value = "画像の抽出が完了しました。"
            page.update()

    file_picker = ft.FilePicker(on_result=on_file_picker_result)
    file_path = ft.TextField(label="選択されたファイル", read_only=True)
    pick_file_button = ft.ElevatedButton(text="ファイルを選択", on_click=lambda _: file_picker.pick_files())
    extract_button = ft.ElevatedButton(text="画像を抽出", on_click=on_extract_click)
    result = ft.Text()

    page.overlay.append(file_picker)
    page.add(pick_file_button, file_path, extract_button, result)

最後に以下を追加して、実行してみましょう。Python ではお決まりの構文ですね。ft.app(target=main) でアプリを起動できます。

1
2
if __name__ == "__main__":
    ft.app(target=main)

alt

どうでしょう?適当な論文やスライドなどのPDFファイルを食わせてみると、そのファイルの場所にフォルダが作成されて、画像がすべて書き出されていればOKです。


これで最低限動くアプリはできましたが、ちょっとだけカスタマイズしてみましょう。

1
2
3
4
5
6
def main(page: ft.Page):

    page.window.width = 400
    page.window.height = 300
    page.title = "PDFから画像を抽出"
    page.theme = ft.Theme(color_scheme_seed="cyan")

window 属性をいじることでアプリウィンドウの大きさを規定したり、theme でカラーテーマを設定したりできます。(カラーパレットはここ)

最終的なコードはこのような感じになっているとおもいます。

4. アプリのパッケージ化

現在の状態では、結局 Python ファイルを実行しないとアプリをつかえません。これを .exe ファイルとして書き出すことで、どのPCでも Python 環境の有無にかかわらずつかえるようになります。

つまり、.exe ファイルの中に必要な Python ライブラリも内蔵されるということですね。

パッケージ化は簡単です。プロジェクトフォルダのルートで以下のコマンドを実行します。

1
pyinstaller main.py --onefile --noconsole --name PDF-Image-Extractor-GUI

オプションの意味も補足しておきます。

  • --onefile: 必要なファイルをすべて .exe ファイル内にまとめる
  • -noconsole: アプリ実行時にコンソール (黒い画面) を表示しない (デバッグ用では出しておくと便利)
  • --name: アプリファイルの名前

これを実行してしばらくすると、dist ディレクトリの中に .exe ファイルができるはずです。おつかれさまでした。

試しにほかのPCで実行してみるなどでテストしてみるといいでしょう。

alt

あ、今回つくったアプリは GitHub で公開しておきますね。Releases ページにはビルドした .exe ファイルも置いてます!

https://github.com/hiroyamochi/pdf-image-extractor-gui

おわりに

flet でかんたんに美しい GUI アプリがつくれるので布教ついでに自分の備忘録として書いてみました。

この調子で自分用のスクリプトも簡単なデスクトップアプリにして便利につかえるようにしていきたいです。

ところで全く関係ないですが、今回このデモのために初めて Python の仮想環境 venv をつかってみたのですが、これいいですね。必要なライブラリだけインストールしてクリーンな環境でつかえます。

This post is licensed under CC BY 4.0 by the author.

Trending Tags