PR

【Python×Flet】かっこいいGUIアプリを作ろう

この記事は約21分で読めます。
らん
らん

やっぱGUI付きのアプリはPythonじゃ難しいかなぁ…😅

るん
るん

いいえ!Fletを使えばPythonだけでかっこいいGUIが作れます✨

Pythonで書いたプログラムを実行して使う場合、『GUIで入出力できたら便利なのに…』と思うときはありませんか?

特に誰かにプログラムを使ってもらう場合、CUI(コマンドラインから入出力)はあまり親切とは言えません。ITスキルの高くない人にとっては、拒絶反応を起こすこともあります。

実は「Flet」というライブラリを使うと、PythonだけでモダンかっこいいGUIが簡単につくれます

これは私が作ったWindowsとmacOSで動くアプリですが、こんな感じのアプリをPythonだけで作れます。exe(Windows)やapp(macOS)ファイルにできるので、他の人に配布するのも簡単です。

fletで作ったアプリ

本記事では、PythonだけでかっこいいGUIアプリを作れるFletの使い方を実例とともに解説します。

なお、ここではPythonをある程度使えるという前提で解説しますので、Pythonの基礎文法は説明しません。ご了承くださいませ。

Fletってなに?

以前、私はこちらの記事で「PySimpleGui」というライブラリを使ってPythonだけでGUIを作る方法を解説しました。

Pythonでも簡単にGUIは作れる - Qiita
PythonだってGUIを作りたいPythonで書いたプログラムを実行して使う場合、**『GUIで入出力できたら便利なのに…』**と思うときはありませんか?誰かにプログラムを配布する場合でも、C…

当時はPySimpleGuiがベストだと思っていましたが、正直なところGUIのデザインはあまりかっこよくはないです。そして最近、商用利用が有償化されたため、やや利用のハードルが上がってしまいました。
また、「Python GUI」で検索すると「Tkinter」という昔からあるフレームワークもたくさん出てきますが、これもGUIは古めかしい感じを受けます。

そんな中、華麗に登場したのが「Flet」です✨

FletはPythonだけですばやくアプリを作れるフレームワークです。
Googleがリリースした「Flutter」というアプリ開発フレームワークを搭載しているので、現代風のかっこいいGUIであることも特徴です。

百聞は一見にしかずなので、まずは下記のコードと実行結果をご覧ください。

import flet as ft


def main(page: ft.Page):
    page.title = "サンプルプログラム"  # タイトル
    page.window_width = 600  # 幅
    page.window_height = 300  # 高さ
    page.theme = ft.Theme(color_scheme_seed="green")

    # 部品を配置する
    page.add(
        ft.Column(
            [
                ft.Text("ここは1行目"),
                ft.Row(
                    [
                        ft.Text("ここは2行目"),
                        ft.TextField(hint_text="文字を入力してください"),
                    ]
                ),
                ft.Row([ft.ElevatedButton("OK"), ft.ElevatedButton("キャンセル")]),
            ]
        )
    )


ft.app(target=main)

実行結果

fletで作成したアプリの画面
るん
るん

たったこれだけでモダンなUIのアプリができちゃいました!

Fletの基本を解説

ではFletの基本的な使い方について解説していきます。
なお、Google ColabではGUIが表示できないので、自分のパソコンでPythonを動かしてくださいね。

準備(インストール)

fletは標準ライブラリではないので、pipコマンドでインストールする必要があります。

pip install flet

コード

1. インポート

このfletというライブラリを使うためにimportをします。このときftという別名をつけるのが慣例なのでそれに従います。

import flet as ft

2. ウィンドウとテーマの設定

page.title = "サンプルプログラム"
page.window_width = 720
page.window_height = 580
page.theme = ft.Theme(color_scheme_seed="green")

ウィンドウのタイトルやテーマ(配色)を指定しています。

page.titleウィンドウのバーに表示されるタイトル
page.window_widthウィンドウの幅
page.window_heightウィンドウの高さ
page.themeアプリの配色”green”や”blue”などの色を指定すればOK

3. 部品とレイアウト

ここがGUIの肝心な部分ですが、ウィンドウに配置する部品(fletではControlといいます)とレイアウトを設定していきます。

page.addという関数で部品を表示させることができます。
レイアウトは基本的には縦に並べていくft.Column()を使うと、こんな書き方になります。

fletの基本レイアウト(Column)

1行の中に横に並べたいときには、ft.Row()を使います。たとえば、3行目に部品Bと部品Cを横に並べたいときは、こんな書き方です。

fletの基本レイアウト(ColumnとRow)

ここでの部品(Control)にはさまざまなものがあり、テキストやチェックボックス、ドロップダウンなどなどモダンで楽しい部品がたくさん用意されています。

Flet公式ドキュメント Controls

fletのさまざまなコントロール

4. ウィンドウの生成

ft.app()のtargetにmain関数を指定します。

ft.app(target=main)

アプリの実行

このアプリを実行するにはターミナルで以下のように打てばOKです。アプリが起動します。

flet run main.py -d

コードを変更して保存すると、わざわざアプリを再起動しなくてもその変更が即時に反映されるのでとても便利です。

Flet公式ドキュメント Running Flet app

GUIアプリをつくってみる

雰囲気がわかったところで、実際にモノを作りながら理解を深めていきましょう。
みなさんも、ぜひ手を動かしてみてくださいね。

今回は例として「画像ファイル(jpeg)に埋め込まれたExif情報を表示するプログラム」を書いてみましょう。

るん
るん

Exif情報とは、写真などに自動登録されている撮影日時やカメラの機種名などの情報です

らん
らん

スマホで撮影した写真にはたいていExif情報が登録されてます

アプリの完成形はこんな感じです。

fletで作成した「Exifよみだしくん」

jpeg画像からExif情報を読み取る部分は関数化しておきます。今回はここは重要ではないので、コードの説明は割愛させていただきます。

なお、ここで使っているpiexifは pip install piexifでインストールできます。

import piexif


def load_exif(path):
    """
    jpegファイルのパスを入力すると、Exif情報を返す
    """
    exif_dict = piexif.load(path)
    exif_info = []

    for ifd in ("0th", "Exif", "GPS", "1st"):
        for tag in exif_dict[ifd]:
            name = piexif.TAGS[ifd][tag]["name"]
            if type(exif_dict[ifd][tag]) is bytes:
                value = exif_dict[ifd][tag].decode()
            else:
                value = exif_dict[ifd][tag]
            print(name, value)

            exif_info.append(f"{name} {value}")

        return "\n".join(exif_info)

これから例示するアプリのコードは異常系の処理などを十分に考えていないため、アプリとしては不完全です。
ここではFletの使い方をわかりやすく学んでいただくことを優先しているため、その点はご理解ください。

準備(GUIのデザイン)

まずどんなGUIにしたいのかデザインを決めましょう。紙でも電子でもいいので、デザインを書いてみます。私はこんなGUIを考えました。

fletで作るGUIの例1

「ファイル選択」ボタンをクリックして、ファイルを選択する。

fletで作るGUIの例2

テキストボックスにそのファイルのパスが表示され、画像が表示される。そして、「読み出す」ボタンをクリック。

fletで作るGUIの例3

指定されているファイルのExif情報を読み出して画像の横に表示される。

実装

あらかじめ「pip install flet」でfletはインストールしておきましょう。

1. インポート

import flet as ft

2. テーマの設定

ウィンドウの基本的な設定を行います。

# ページ情報の設定
page.title = "Exifよみだしくん"  # タイトル
page.window_width = 750  # ウィンドウの幅
page.window_height = 600  # ウィンドウの高さ
るん
るん

デザインにこだわりたい人はpage.themeも設定してもOK

3. ウィンドウの部品とレイアウト

GUIのデザイン案を行ごとに考えてみます。

GUIを行ごとに分けて考える

1行目

GUIの1行目

1行目はテキストを表示しているだけです。なので、部品としてはft.Text()を使って以下のようになります。

ft.Text("JPEG画像からExif情報を読み出します")

2行目

GUIの2行目

2行目には2つのコンポーネントを横に並べて表示します。

■1つめのコンポーネント
2つめのコンポーネントである「ファイル選択」ボタンで選択したファイルのパスを表示するためのボックスです。
ft.TextField()です。

hint_textでヒントテキストを表示します。また、このボックスは表示するだけで直接入力させないので、read_only=Trueとします。

中身が変わりますから、この部品は変数です。
なので、以下のように変数に格納しておきます。中身が変わらない部品については変数にする必要はないのですが、コードのわかりやすさから、此処から先の部品はすべて一旦変数に格納することにします。

file_path_display = ft.TextField(hint_text="ファイルを指定", read_only=True)

■2つめのコンポーネント
ファイルをブラウザで選択するためのボタンです。選択したファイルのパスは1つめのコンポーネントに表示します。ボタンにはいろいろと種類があるのですが、ここでは最もオーソドックスなft.ElevatedButton()を使います。

file_select_btn = ft.ElevatedButton(
        "ファイルを選択",
    )

これら2つのコンポーネントを横に並べるにはft.Row()を使います。

ft.Row([file_path_display, file_select_btn])

さて、ファイル選択ボタンについてですが、このままだとボタンを押しても何も起こりません。当然ですよね、ボタンを押したときの動作を何も定義していないわけですから。

この「ファイル選択動作」は少し特殊でFilePickerというユーティリティを使います。やや特殊な書き方になってしまうので、説明は一旦飛ばして先に進みます。

るん
るん

最後にまた戻って説明しますね

3行目

GUIの3行目

3行目には左に画像、右にExif情報を表示します。

  • 画像はft.Image(画像ファイルのパス)です。最初はファイル選択されていませんから、ファイルパスであるsrcには空文字列を指定しておきます。
  • Exif情報はテキストですからft.Text()です。
selected_img = ft.Image(
        src="", width=400, height=400, fit=ft.ImageFit.SCALE_DOWN
    )
exif_display = ft.Text("")

ft.Row([selected_img, exif_display])

さて、この2つの部品についてはアプリが起動した時点では表示しておく必要がありません。
部品の表示・非表示は各コンポーネントのvisibleというパラメータにTrue/Falseを設定することでコントロールできます。

selected_img = ft.Image(
        src="", width=400, height=400, fit=ft.ImageFit.SCALE_DOWN, visible=False
    )
exif_display = ft.Text("", visible=False)

ft.Row([selected_img, exif_display])

4行目

GUIの4行目

4行目は読み出しボタンを1つ配置するだけです。

read_btn = ft.ElevatedButton("読み出す")

これだけだと、ボタンを押しても何も起こりません。

ボタンをクリックしたときに呼び出す関数を定義して、その関数をボタンの引数on_clickに指定すればOKです。

def btn_clicked():
    // ボタンをクリックしたときに行いたい処理

ft.ElevatedButton("読み出す",on_click=btn_clicked)
るん
るん

関数名(=上記コードのbtn_clicked())は何でも構いません。わかりやすい名前をつけてください。

らん
らん

問題はread_btn_clicked()の中身よね…

ここで行いたい処理は次の2つです。

  • 選択された画像ファイルからExif情報を読み出す
  • 読み出したExif情報を3行目の右に表示する
def read_btn_clicked(e):
    # 選択された画像ファイルからExif情報を読み出す
    exif_info = load_exif(selected_file_path)
    # 読み出したExif情報を3行目の右に表示する
    exif_display.value = exif_info
    exif_display.visible = True
    # 画面を更新する
    page.update()

ft.ElevatedButton("読み出す", on_click=read_btn_clicked)
るん
るん

exif_displayは最初非表示にしたので、exif_display.visible = Trueで表示させるのもポイント!

page.update()をしないと画面表示が更新されないのでお忘れなく!

再び2行目

GUIの2行目

さて、ボタンクリック時の動作の実装がわかったところで、飛ばしていた2行目に戻ります。
理屈としてはon_clickにファイルを選択させる動作を指定すればいいのですが、ここではFilePickerのExapleの書き方をマネしましょう。

def pick_files_result(e: ft.FilePickerResultEvent):
    if e.files:  # ファイルが選択された場合
        # ファイル選択後に行いたい処理
    else:  # ファイルを選択せず「キャンセル」でダイアログを閉じた場合
        pass  # 何もしない


pick_files_dialog = ft.FilePicker(on_result=pick_files_result)
page.overlay.append(pick_files_dialog)

file_select_btn = ft.ElevatedButton(
    "ファイルを選択",
    on_click=lambda _: pick_files_dialog.pick_files(allow_multiple=False),
)

ポイントは太字の部分です。ここにファイルが選択された後の処理を書きます。

ファイルが選択された後に行いたい処理は以下の3つですね。

ファイル選択後に行いたい動作

先ほどのコードに当てはめると以下のようになります。

def pick_files_result(e: ft.FilePickerResultEvent):
    if e.files:  # ファイルが選択された場合
        # ファイルパスを表示
        file_path_display.value = e.files[0].path
        # 画像を表示
        selected_img.src = e.files[0].path
        selected_img.visible = True
        # "読み出す"ボタンを表示
        read_btn.visible = True
        # 画面を更新
        page.update()
    else:  # ファイルを選択せず「キャンセル」でダイアログを閉じた場合
        pass  # 何もしない


pick_files_dialog = ft.FilePicker(on_result=pick_files_result)
page.overlay.append(pick_files_dialog)

file_select_btn = ft.ElevatedButton(
    "ファイルを選択",
    on_click=lambda _: pick_files_dialog.pick_files(allow_multiple=False),
)
るん
るん

e.files[0].pathに選択されたファイルのパスがテキストで格納されています

さて、以上で各部品が出揃いました。あとは1行目~4行目の各コンポーネントを縦に並べていけばいいので、ft.Columnを使って配置します。それをpage.add()で画面に追加すればOKです。

page.add(
    ft.Column(
        [
            ft.Text("JPEG画像からExif情報を読み出します"),
            ft.Row([file_path_display, file_select_btn]),
            ft.Row([selected_img, exif_display]),
            read_btn,
        ]
    )
)

4. ウィンドウの生成

ft.app(target=main)

これでOKです。

コードの全文

コードの全文をまとめて載せておきます。

import flet as ft
import piexif


def load_exif(path):
    """
    jpegファイルのパスを入力すると、Exif情報を返す
    """
    exif_dict = piexif.load(path)
    exif_info = []

    for ifd in ("0th", "Exif", "GPS", "1st"):
        for tag in exif_dict[ifd]:
            name = piexif.TAGS[ifd][tag]["name"]
            if type(exif_dict[ifd][tag]) is bytes:
                value = exif_dict[ifd][tag].decode()
            else:
                value = exif_dict[ifd][tag]
            print(name, value)

            exif_info.append(f"{name} {value}")

        return "\n".join(exif_info)


def main(page: ft.Page):
    # ページ情報の設定
    page.title = "Exifよみだしくん"  # タイトル
    page.window_width = 750  # ウィンドウの幅
    page.window_height = 600  # ウィンドウの高さ

    # 部品を配置する

    # ファイルの選択
    file_path_display = ft.TextField(hint_text="ファイルを指定", read_only=True)

    def pick_files_result(e: ft.FilePickerResultEvent):
        if e.files:  # ファイルが選択された場合
            # ファイルパスを表示
            file_path_display.value = e.files[0].path
            # 画像を表示
            selected_img.src = e.files[0].path
            selected_img.visible = True
            # "読み出す"ボタンを表示
            read_btn.visible = True
            # 画面を更新
            page.update()
        else:  # ファイルを選択せず「キャンセル」でダイアログを閉じた場合
            pass  # 何もしない

    pick_files_dialog = ft.FilePicker(on_result=pick_files_result)
    page.overlay.append(pick_files_dialog)

    file_select_btn = ft.ElevatedButton(
        "ファイルを選択",
        icon=ft.icons.FILE_OPEN,
        on_click=lambda _: pick_files_dialog.pick_files(allow_multiple=False),
    )

    # 画像とExif情報の表示
    selected_img = ft.Image(
        src="", width=400, height=300, fit=ft.ImageFit.SCALE_DOWN, visible=False
    )
    exif_display = ft.Text("", visible=False)

    # 読み出しボタン
    def read_btn_clicked(e):
        # 選択された画像ファイルからExif情報を読み出す
        exif_info = load_exif(selected_img.src)
        # 読み出したExif情報を3行目の右に表示する
        exif_display.value = exif_info
        exif_display.visible = True
        # 画面を更新する
        page.update()

    read_btn = ft.ElevatedButton(
        "読み出す", icon=ft.icons.FIND_IN_PAGE, visible=False, on_click=read_btn_clicked
    )

    page.add(
        ft.Column(
            [
                ft.Text("JPEG画像からExif情報を読み出します"),
                ft.Row([file_path_display, file_select_btn]),
                ft.Row([selected_img, exif_display]),
                read_btn,
            ]
        )
    )


ft.app(target=main)
るん
るん

ボタンにはicon引数を指定して「アイコン」を指定しています✨

アイコンはFletの「Icons Browser」で探せます🔎

実はこのアプリ、ちょっと動きがおかしいところがあります。ぜひご自身で動かしながら考えてみてくださいね。

画像を選択し直すと、前回の画像のExif情報が残ったままになっています。

直し方はいろいろありますが、一つの方法は「ファイル選択後に、Exif情報の表示コントロールを非表示にする」という方法です。

def pick_files_result(e: ft.FilePickerResultEvent):
    if e.files:  # ファイルが選択された場合
        # ファイルパスを表示
        file_path_display.value = e.files[0].path
        # 画像を表示
        selected_img.src = e.files[0].path
        selected_img.visible = True
        # Exif情報を非表示
        exif_display.visible = False
        # "読み出す"ボタンを表示
        read_btn.visible = True
        # 画面を更新
        page.update()
    else:  # ファイルを選択せず「キャンセル」でダイアログを閉じた場合
        pass  # 何もしない

まとめ

Pythonは読みやすく書きやすく、ライブラリも充実している素晴らしい言語です。
Fletを使うとPythonだけでGUIアプリを作ることができるので、プログラミングの幅がぐっと広がるでしょう。
使いやすいGUIアプリは、自分だけでなく他の人にも使ってもらうことができるので、自分のプログラムを誰かに使ってもらうという楽しみが増えます😄

るん
るん

Fletで楽しいPythonライフを♪

プロフィール
この記事を書いた人
千鳥 るん | Chidori Run

画像生成AIで思い通りのイラストを描くためのノウハウを試行錯誤で模索しています。IT企業でAI戦略に関わっていたこともあるAIエンジニアです。大学生の頃から趣味でイラストを描いていましたが、仕事が忙しくなり一旦筆を置きました。最近、NovelAIと出会ってまたお絵描きへの情熱を取り戻しています。

千鳥るんをフォローする
プログラミング
スポンサーリンク
シェアする
千鳥るんをフォローする

コメント