やっぱGUI付きのアプリはPythonじゃ難しいかなぁ…😅
いいえ!Fletを使えばPythonだけでかっこいいGUIが作れます✨
Pythonで書いたプログラムを実行して使う場合、『GUIで入出力できたら便利なのに…』と思うときはありませんか?
特に誰かにプログラムを使ってもらう場合、CUI(コマンドラインから入出力)はあまり親切とは言えません。ITスキルの高くない人にとっては、拒絶反応を起こすこともあります。
実は「Flet」というライブラリを使うと、PythonだけでモダンなかっこいいGUIが簡単につくれます。
これは私が作ったWindowsとmacOSで動くアプリですが、こんな感じのアプリをPythonだけで作れます。exe(Windows)やapp(macOS)ファイルにできるので、他の人に配布するのも簡単です。
本記事では、PythonだけでかっこいいGUIアプリを作れるFletの使い方を実例とともに解説します。
なお、ここではPythonをある程度使えるという前提で解説しますので、Pythonの基礎文法は説明しません。ご了承くださいませ。
Fletってなに?
以前、私はこちらの記事で「PySimpleGui」というライブラリを使ってPythonだけでGUIを作る方法を解説しました。
当時は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)
実行結果
たったこれだけでモダンな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()を使うと、こんな書き方になります。
1行の中に横に並べたいときには、ft.Row()を使います。たとえば、3行目に部品Bと部品Cを横に並べたいときは、こんな書き方です。
ここでの部品(Control)にはさまざまなものがあり、テキストやチェックボックス、ドロップダウンなどなどモダンで楽しい部品がたくさん用意されています。
4. ウィンドウの生成
ft.app()のtargetにmain関数を指定します。
ft.app(target=main)
アプリの実行
このアプリを実行するにはターミナルで以下のように打てばOKです。アプリが起動します。
flet run main.py -d
コードを変更して保存すると、わざわざアプリを再起動しなくてもその変更が即時に反映されるのでとても便利です。
GUIアプリをつくってみる
雰囲気がわかったところで、実際にモノを作りながら理解を深めていきましょう。
みなさんも、ぜひ手を動かしてみてくださいね。
今回は例として「画像ファイル(jpeg)に埋め込まれたExif情報を表示するプログラム」を書いてみましょう。
Exif情報とは、写真などに自動登録されている撮影日時やカメラの機種名などの情報です
スマホで撮影した写真にはたいてい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)
準備(GUIのデザイン)
まずどんなGUIにしたいのかデザインを決めましょう。紙でも電子でもいいので、デザインを書いてみます。私はこんなGUIを考えました。
「ファイル選択」ボタンをクリックして、ファイルを選択する。
テキストボックスにそのファイルのパスが表示され、画像が表示される。そして、「読み出す」ボタンをクリック。
指定されているファイルの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のデザイン案を行ごとに考えてみます。
1行目
1行目はテキストを表示しているだけです。なので、部品としてはft.Text()を使って以下のようになります。
ft.Text("JPEG画像からExif情報を読み出します")
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行目
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行目
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で表示させるのもポイント!
再び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」で探せます🔎
まとめ
Pythonは読みやすく書きやすく、ライブラリも充実している素晴らしい言語です。
Fletを使うとPythonだけでGUIアプリを作ることができるので、プログラミングの幅がぐっと広がるでしょう。
使いやすいGUIアプリは、自分だけでなく他の人にも使ってもらうことができるので、自分のプログラムを誰かに使ってもらうという楽しみが増えます😄
Fletで楽しいPythonライフを♪
コメント