【Python特化】おすすめのオンラインプログラミングスクール

【Python】デスクトップアプリ作ってみた|開発例から配布方法まで解説!

Pythonの基礎学習を一通り終えたタイミングで、アプリケーション開発に取り掛かりたい人は一定存在すると思います。

本記事の目的
  • Python製デスクトップアプリを作りたい人
  • おすすめのデスクトップアプリ用フレームワークが知りたい人
  • Python製デスクトップアプリのexe化や配布方法を知りたい人

上記の悩みを解決しながら、Python製デスクトップ用フレームワークの紹介から実行環境構築まで解説します。

また、ハンズオン形式でサンプルのPython製デスクトップアプリ開発も実施できるようまとめています。

目次

デスクトップアプリとは

Python製デスクトップアプリの特徴
  • デスクトップアプリの開発用ライブラリが豊富
  • 自身の業務に合わせた業務効率化/自動化に利用できる
  • exe化によるアプリ配布が可能
  • Pythonコードのみで始められるため初心者が取り組みやすい

また、以下はデスクトップアプリの代表的なメリットになります。

デスクトップアプリのメリット
  • 特定OSに直接実行がしやすい
  • オフラインで動作する
  • 使用リソースが少なく軽快な動作
  • Webアプリよりも開発が用意

既存ツール/アプリでは詳細な業務に手が届いていない機能も多いです。

そのため、自作ツールとしてデスクトップアプリは有効です。

よく自作ツールとして開発されるデスクトップアプリ例は別の章で後述します。

デスクトップアプリとGUIアプリの違い

「デスクトップアプリ」を「GUIアプリ」と表記する場合もありますが、両者は微妙に異なる文脈だと考えます。

「GUI」と「CUI」とは

GUI(Graphical User Interface)は、ウィンドウやボタンなどグラフィカルな要素をマウス等で操作するインターフェースを指します。

一方でCUI(Character User Interface)は、ターミナルをコマンドで操作するインターフェースを指します。

また、CUIをCLI(Command Line Interface)と呼ぶこともあり、グラフィカルな要素がない文字ベースのインターフェースであると認識しておけば問題ありません。

一般的に「GUIアプリ」といえば、画面上のボタンやテキストボックスなどの要素をマウス操作するためデスクトップアプリと同義である場合もあります。

ただ、デスクトップアプリが「場所(ローカルPCかWeb上)」に関連する用語に対し、GUIアプリが「インターフェース(GUIかCUI)」に関連する用語であるといった相違点があります。

そのため、GUIアプリはデスクトップ/モバイル/Webなどマルチプラットフォームで利用されることから、デスクトップアプリより広範なカテゴリに属していると言えます。

Python製のデスクトップアプリ用GUIフレームワーク

Pythonには様々なデスクトップアプリ用のGUIフレームワークが存在します。

以下の要素を考慮しながらGUIフレームワークを選択するのがおすすめです。

GUIフレームワーク選択で考慮する点
  • マルチプラットフォーム対応(Windows/Mac/Linux)
  • インストールの有無(Tkinterは標準)
  • ライセンスを考慮する
  • 個人開発する用途に応じたライブラリなのか

また、代表的なPython製デスクトップアプリ用GUIフレームワークをまとめています。

スクロールできます
ライブラリ名概要URL
TkinterPythonの標準ライブラリhttps://docs.python.org/ja/3.13/library/tkinter.html
PySimpleGUI簡単でシンプルなGUIライブラリhttps://www.pysimplegui.com/
TkEasyGUIPySimpleGUI互換のGIUライブラリhttps://github.com/kujirahand/tkeasygui-python/blob/main/README-ja.md
PyQt/PySideGUIツールキットQtのPython用ライブラリhttps://wiki.qt.io/PySide_Documentation/ja
wxPython機能が豊富なGUIツールキットhttps://docs.wxpython.org/
fletFlutterを活用したPythonマルチプラットフォームライブラリhttps://flet.dev/

各ライブラリに関して解説します。

簡易的なアプリの画像とサンプルコードの比較ができるようまとめています。

Tkinter

サンプルコード
import tkinter as tk
import tkinter.messagebox as msg

# ウィンドウを表示する関数
def show_window():
    # メインウィンドウ
    root = tk.Tk()
    root.title("サンプルアプリ")
    # サイズ指定
    root.geometry("300x200")
    # ラベル作成
    tk.Label(root, text="サブウィンドウボタンを押下してください").pack()
    # ボタン作成
    tk.Button(root, text="サブウィンドウ表示", command=click_handler).pack()
    # メインループ開始
    root.mainloop()

# クリックイベント
def click_handler():
    msg.showinfo(title="サンプルアプリ", message="サブウィンドウが表示されました")

if __name__ == "__main__":
    show_window()

Tkinterは、Pythonに標準で搭載されているGUIライブラリです。

Tkinterは1990年代初頭から登場した歴史あるライブラリでWindows/Mac/Linuxと多くのOS上で動作します。

PySimpleGUI

サンプルコード
import PySimpleGUI as sg

# ウィンドウ
win = sg.Window(
    title = "サンプルアプリ",
    layout = [
        [sg.Text("サブウィンドウボタンを押下してください")],
        [sg.Button("サブウィンドウ表示")]
    ]
)

# イベントループ
while True:
    event, _ = win.read()
    if event == "サブウィンドウ表示":
        sg.popup("サブウィンドウを表示しました")
    if event == sg.WIN_CLOSED:
        break

PySimpleGUIは、手軽にデスクトップアプリを作成するGUIライブラリです。

独自のGUIライブラリではなく、Tkinterなどの既存GUIライブラリをラップしています。

内部でTkinterを利用しているため、動作環境が幅広いのが特徴です。

TkEasyGUI

サンプルコード
import TkEasyGUI as eg

# ウィンドウ表示
win = eg.Window(
    title = "サンプルアプリ",
    layout = [
        [eg.Text("サブウィンドウボタンを押下してください")],
        [eg.Button("サブウィンドウ表示")]
    ]
)

# イベントループ
while win.is_alive():
    event, _ = win.read()
    if event == "サブウィンドウ表示":
        eg.popup("サブウィンドウを表示しました")

win.close()

TkEasyGUIは、PySimpleGUIと互換性のあるGUIライブラリです。

オープンソースで開発されており、商用利用可能で制限の緩いMITライセンスを採用しています。

PyQt/PySide

サンプルコード
import sys
from PySide6 import QtWidgets as qt

def show_window():
    # ウィンドウの初期設定
    app = qt.QApplication(sys.argv)
    win = qt.QWidget()
    win.setGeometry(300, 300, 300, 200)
    win.setWindowTitle("サンプルアプリ")
    # ラベル作成
    label = qt.QLabel("サブウィンドウボタンを押下してください", win)
    label.setGeometry(10, 10, 200, 20)
    # ボタン作成
    button = qt.QPushButton("サブウィンドウ表示", win)
    button.setGeometry(10, 40, 100, 30)
    button.clicked.connect(show_message)
    win.show()
    app.exec()

# メッセージを表示する関数
def show_message():
    qt.QMessageBox.information(None, "サンプルアプリ", "サブウィンドウが表示されました")

if __name__ == "__main__":
    show_window()

PyQt/PySideは、Qt(キュート)といったGUIツールキットのPython用ライブラリです。

マルチプラットフォームに対応しており、Windows/Mac/LinuxのOS上で動作します。

もともとC++のために開発されたライブラリであり、幅広く利用され人気です。

wxPython

サンプルコード
import wx

def show_window():
    app = wx.App()
    frame = wx.Frame(None, wx.ID_ANY, "サンプルアプリ")
    panel = wx.Panel(frame, wx.ID_ANY)
    # ラベル作成
    wx.StaticText(panel, wx.ID_ANY, "サブウィンドウボタンを押下してください", pos=(10, 10))
    # ボタン作成
    button = wx.Button(panel, wx.ID_ANY, "サンプルアプリ", pos=(10, 40))
    button.Bind(wx.EVT_BUTTON, show_message)
    frame.Show()
    app.MainLoop()

# メッセージを表示する関数
def show_message(e):
    wx.MessageBox("サブウィンドウが表示されました")

if __name__ == "__main__":
    show_window()

wxPythonは、C++のGUIライブラリである「wxWidgets」のPython用ライブラリです。

コンポーネントの描画をOSに任せているのが特徴で、OSに調和したGUI部品を表示します。

こちらもマルチプラットフォーム対応でWindows/Mac/LinuxのOS上で動作します。

flet

サンプルコード
import flet as ft

def main(page: ft.Page):
    page.title = "サンプルアプリ"
    page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
    t = ft.Text("サブウィンドウボタンを押下してください")
    
    def handle_close(e):
        page.close(dlg_modal)
        #page.add(ft.Text(f"Modal dialog closed with action: {e.control.text}"))

    dlg_modal = ft.AlertDialog(
        modal=True,
        title=ft.Text("サブウィンドウ表示"),
        content=ft.Text("閉じるボタンを押下してください"),
        actions=[
            ft.TextButton("閉じる", on_click=handle_close),
        ],
        actions_alignment=ft.MainAxisAlignment.END,
    )

    page.add(
        t,
        ft.ElevatedButton("サブウィンドウ表示", on_click=lambda e: page.open(dlg_modal)),
    )

ft.app(main)

Fletは、Googleが開発したFlutterを基盤とし、Pythonのシンプルさを組み合わせたGUIアプリ開発優れたフレームワークです。

Fletを使えば、同じコードでWeb/デスクトップ/モバイルアプリといったクロスプラットフォーム対応を実現できます。

Pythonで作れるデスクトップアプリの例

開発者のアイデア次第ではありますが、Pythonで開発するデスクトップアプリといった側面に着目すると様々なアプリを自作できます。

開発可能なPython製デスクトップアプリ例
  • 基本的なアクセサリ(関数電卓/スケジュール管理系)
  • メモ帳や専用テキストエディタ
  • データ収集系ツール
  • データ分析系ツール
  • ファイルマネージャー
  • クリップボード履歴管理
  • ネットワーク監視ツール
  • パスワード管理ツール
  • PDF/Excel/Office系のバックオフィス管理ツール
  • AI(API経由)連携ツール

自身の業務あるいは趣味など様々なピンポイント作業に取り入れることで作業効率が何倍にも高められると考えます。

特に、既存ツールの機能だとどうしても手が届かない業務内容があり、なおかつ手作業になっている作業工程も数多くあります。

Pythonによるデスクトップアプリは、自身のニーズに沿ったツール開発に最適だと思います。

Pythonによるデスクトップアプリで作業効率化/自動化

筆者自身が実務として経験していますが、以下はまだまだ手動のままになっている業務だと感じます。

なんとなく手作業になりがちな業務例
  • 分析やマーケティングに必要な膨大なCSVファイル作成
  • 社内調整で必要な多人数会議予約やZOOM会議室作成とパスワード
  • 自動で記述する音声認識の議事録作成ツール
  • マジで無駄なメール返信作業
  • 提案資料などの情報整理
  • 技術マニュアル作成やテンプレート作成

データ収集先URLが固定あるいは選定されていれば、Seleniumを利用したスクレイピングツールが作成できます。

また、社内調整で必要な多人数の予定もGoogleカレンダーと連携し、予約ツールを作成できそうです。

さらに、メールに対する社内外のテンプレートメール管理ツールや、AI連携したメール要約と返信をセットにしたツールも考えられます。

提案資料や技術マニュアルもローカルPC上に保持する各ファイルを読み込ませ、AI(あるいはAI Agent)にプロンプトで指示し資料のひな形を出力するツールも想像できます。

特に、近年ではいくつもAIサービスがあるため、各AIサービスをまとめたツールを作成し、一度で数個のAIに同時出力させ評価が高い出力を採用し、業務に活用する形も目指せそうです。

このように、Pythonとデータサイエンス関連またはAI技術と親和性が非常に高いため、ちょっとしたアイデアで様々なニーズに応えられるデスクトップアプリが開発できます。

Pythonさえ理解すれば、UIの質はともかく機能としては作業効率化/自動化に貢献できるアプリ開発が実現できると考えます。

Pythonによるデスクトップアプリの開発手順

ここでは、ハンズオン形式でPython製デスクトップアプリを開発していきます。

そのために必要な準備として、Pythonによるデスクトップアプリの開発手順を以下に記載します。

デスクトップアプリの開発手順
  • VSCodeのインストール
  • Pythonのインストール
  • デスクトップアプリ用フレームワークのインストール
  • デスクトップアプリ開発

上記の開発手順をそれぞれ解説します。

VSCodeのインストール

Visual Studio Code(VSCode)のインストーラーをダウンロードして、自身のPCにインストールします。

以下のURLからVisual Studio Codeをダウンロードできます。

PCのOSに合ったパッケージの安定版(Stable)を選択してインストーラーをダウンロードします。

ダウンロードしたインストーラーを起動すると、使用許諾画面が表示されるため、「同意する」にチェックし「次へ」をクリックします。

追加タスクの選択画面では、必要なオプションを選んだら「次へ」をクリックします。

インストール準備の完了画面で「インストール」をクリックします。

インストールを終えたら、「完了」をクリックします。

上記の操作にて、Visual Studio Codeのインストールが完了します。

Pythonのインストール

お使いのPCにダウンロードしたインストーラーを起動します。

また、本記事では以下のOSに対するインストール方法を解説します。

インストール解説するOS
  • Windows用のPythonインストール
  • Mac用のPythonインストール

また、PythonによるローカルPC上での仮想環境を作成したい人は「【Python】ダウンロードとインストール方法から開発環境構築まで解説!」を一読ください。

Pythonのインストール|Windows

Windows用PCにダウンロードしたインストーラーを起動します。

起動すると、ウィンドウにてインストール画面が表示されます。

環境変数のPATHに自動で追加する場合、"Add python.exe to PATH"へチェックを入れてください。(デフォルトではオフ)
PATHに追加することで、Pythonプログラムを実行できるようになります。

"Use admin privileges when installing py.exe"は管理者権限によるインストールになります。(デフォルトではオフ)

「Install Now」をクリックすると、インストールが始まります。

インストール中の画面へ切り替わるため、インストール完了まで待機します。

インストールが終了すると、「Setup was Successful」といったインストール完了画面になります。

以上で、プログラミング言語Pythonのインストールは完了です。

Pythonのインストール|Mac

Mac用PCにダウンロードしたインストーラーを起動します。

起動すると、ウィンドウにてインストール画面が表示されます。

「続ける」をクリックすると、「大切な情報」といった項目画面に遷移します。

「続ける」をクリックすると、「使用許諾契約」といった項目画面に遷移します。

「続ける」をクリックすると、画面上部からインストールを続けるために“同意する”かを求められます。

インストールを実行するために、“同意する”をクリックしてください。

次に、「インストール先」の選択になりますが、基本的に変更する必要はありません。

「続ける」をクリックすると、「インストールの種類」といった項目画面に遷移します。

ここで、インストール実行するためにPCのパスワード入力が求められるため、入力後にインストールをクリックしてください。

インストールが開始されたら、各ファイルがインストールされるまで待機します。

以上で、MacにおけるPythonのインストールは完了です。

デスクトップアプリ用フレームワークのインストール

ここでは、上述で紹介した各ライブラリのインストールコマンドを記載します。

また、Tkinterに関してはPythonに同梱されたライブラリであるため省略しています。

PySimpleGUI|インストール
python -m pip install -U PySimpleGUI
TkEasyGUI|インストール
python -m pip install -U TkEasyGUI
PyQt/PySide|インストール
python -m pip install -U PySide6
wxPython|インストール
python -m pip install -U wxPython

自身が利用したデスクトップ用ライブラリをインストールしてください。

以下から、例としてPySimpleGUIを利用したデスクトップアプリ開発を記載します。

Python製デスクトップアプリ作ってみた

ここでは、以下のディレクトリ構成でデスクトップ用のメモ帳アプリを開発します。

memo
└─ memo.py
ディレクトリ構成
  • memo:開発用ディレクトリ名
  • memo.py:メモ帳アプリのPython実行ファイル

個々人で任意のディレクトリ/ファイル名で構いません。

PySimpleGUIによるメモ機能実装

PySimpleGUIによるメモ機能を実装していきます。

# メモ帳アプリ
import os
import PySimpleGUI as sg

# 保存ファイル指定
SCRIPT_DIR = os.path.dirname(__file__)
SAVE_FILE = os.path.join(SCRIPT_DIR, "memo.txt")

# レイアウト定義
layout = [
    [sg.Multiline(size=(60, 20), key="text")],
    [sg.Button("保存"), sg.Button("開く")],
]

window = sg.Window("MEMO", layout=layout)

# イベントループ
while True:
    # イベントと入力値の取得
    event, values = window.read()
    # 保存ボタン押下時
    if event == "保存":
        # 指定ファイルに保存
        with open(SAVE_FILE, "w", encoding="utf-8") as f:
            f.write(values["text"])
            sg.popup("保存しました")
    # 開くボタン押下時
    if event == "開く":
        # 保存先ファイルの存在確認
        if not os.path.exists(SAVE_FILE):
            sg.popup("ファイルがありません")
            continue
        # 保存されたファイル読み込み
        with open(SAVE_FILE, "r", encoding="utf-8") as f:
            text = f.read()
        # 読み込みデータをテキストボックスへ反映
        window["text"].update(text)
    # 閉じるボタン押下時
    if event == sg.WINDOW_CLOSED:
        break

# 終了処理
window.close
memo.py
  • 各モジュールのインポート
  • 保存ファイル指定
  • 画面に対するレイアウト定義
  • while文によるイベントループ
  • 各イベント処理
import os
import PySimpleGUI as sg

osモジュールは、直接ローカルPC上のファイルを扱うためにインポートしています。

また、デスクトップアプリで利用するためPySimpleGUIもインポートします。

SCRIPT_DIR = os.path.dirname(__file__)
SAVE_FILE = os.path.join(SCRIPT_DIR, "memo.txt")

osモジュールのメソッドを利用し、ディレクトリ指定します。

また、ディレクトリに対してmemo.txtをファイルとして保存できるようSAVE_FILEを定義します。

layout = [
    [sg.Multiline(size=(60, 20), key="text")],
    [sg.Button("保存"), sg.Button("開く")],
]

window = sg.Window("MEMO", layout=layout)

レイアウト定義は、PySimpleGUIのMulitilineメソッドを利用し、テキスト入力ボックス(スクロール可)を生成します。

また、保存用ボタンと開くボタンも生成しておきます。

変数windowにて、レイアウトを格納しておきます。

while True:
    # イベントと入力値の取得
    event, values = window.read()
    # 保存ボタン押下時
    if event == "保存":
        # 指定ファイルに保存
        with open(SAVE_FILE, "w", encoding="utf-8") as f:
            f.write(values["text"])
            sg.popup("保存しました")
    # 開くボタン押下時
    if event == "開く":
        # 保存先ファイルの存在確認
        if not os.path.exists(SAVE_FILE):
            sg.popup("ファイルがありません")
            continue
        # 保存されたファイル読み込み
        with open(SAVE_FILE, "r", encoding="utf-8") as f:
            text = f.read()
        # 読み込みデータをテキストボックスへ反映
        window["text"].update(text)
    # 閉じるボタン押下時
    if event == sg.WINDOW_CLOSED:
        break

# 終了処理
window.close

while文によるイベントループを開始させます。

変数eventとvaluesを定義し、イベント内容と入力値の取得を.read()メソッドで実行します。

if文ではそれぞれイベント処理に合わせ、保存/開く/終了を実装しています。

終了イベントが発生した場合、window.closeでアプリを終了させます。

メモ帳アプリの動作確認

任意のディレクトリにて、以下のコマンドを実行します。

python memo.py

Python製デスクトップアプリをおしゃれにするには

もともとPythonは、バックエンド言語の一つとして開発が進められています。

そのため、デザイン性や高度なUI実装に不向きなプログラミング言語です。

フロントエンド領域とバックエンド領域で主な役割とニーズが異なるため、デザイン重視のアプリ開発はPythonのみだと難しいです。

フロントエンド領域で求められるニーズ
  • デザインや高度なUIの流行を実現
  • 最新デバイスへの対応
  • 各ブラウザのアップデート対応

一方で、バックエンド領域で主に求められるニーズは何になるか考えてみましょう。

バックエンド領域で求められるニーズ
  • 誰がどこにいても繋がれるアクセス対応
  • 24時間365日の連続稼働
  • ネットセキュリティに対する対応

大まかではありますが、フロントエンドとバックエンドで求められる要件が異なることが分かると思います。

そのため、モダンなUIによるデスクトップアプリを開発したい場合におすすめなのがfletになります。

Fletを使用すると、開発者はPythonでリアルタイムのWeb/モバイル/デスクトップアプリを同一コードで簡単に構築できます。

Flutterを基盤としており、出来上がったモダンなUIコンポーネントを活用するため、フロントエンドの経験は必要ありません。

おすすめのPython製デスクトップアプリの作り方

デザインではなく機能実装に専念したい人は、「flet」によるデスクトップアプリ開発がおすすめです。

また、フロントエンド経験がなくても実践できるのが特徴で、ある程度のモダンなUIは簡単に実現できるようコンポーネントが用意されています。

fletチュートリアル
  • fletによるデスクトップ用ToDoアプリ開発

上記のデスクトップアプリ開発を実施していきます。

fletによるデスクトップ用ToDoアプリ開発

上記は、fletによるデスクトップ用ToDoアプリの完成画像になります。

以下の手順に沿って、ToDoアプリ開発を進めていきます。

fletによるアプリ開発工程
  1. fletのインストール
  2. ページコントロールとイベント処理
  3. 再利用可能なUIコンポーネント作成
  4. リストの表示/編集/削除
  5. リストのフィルタリングとフッター作成
  6. ToDoアプリのexe化

Pythonのインストールや任意のディレクトリは、すでに準備できている想定で進めています。

fletのインストール

まず任意のディレクトリにcd(カレントディレクトリ)コマンドで移動したら、fletをインストールします。

pip install flet

次に、fletによる「Hello World!」を表示する簡単なアプリを作成します。

import flet as ft

def main(page: ft.Page):
    page.add(ft.Text(value="Hello, world!"))

ft.app(main)

fletの特徴として、fletが用意する数多くのUIコンポーネントが存在します。

UIコンポーネントは、コントロールといった概念で構成されています。

各コントロールには、親子になるコントロールがあり、親コントロール(parent control)の中に子コントロール(child control)を含める形になります。

ここでは、fletにおけるコントロールの概念は割愛します。

一度実行した画面が以下になります。

ページコントロールとイベント処理

ここでは、シンプルなテキストフィールドとフローティングボタンを設置します。

import flet as ft

def main(page: ft.Page):
    def add_clicked(e):
        page.add(ft.Checkbox(label=new_task.value))
        new_task.value = ""
        page.update()

    new_task = ft.TextField(hint_text="ToDoを入力してください")
    page.add(new_task, ft.FloatingActionButton(icon=ft.Icons.ADD, on_click=add_clicked))

ft.app(main)

アプリ実行画面が以下になります。

page.add()の箇所を見ると分かりますが、子コントロールを追加しながらページを拡張していることが分かります。

また、page.update()することで再レンダリングが実行され、SPA(シングルページアプリケーション)がPythonでもfletを通して実現できます。

ページ全体の見栄えをよくするため、上部中央に位置できるよう調整しています。

import flet as ft

def main(page: ft.Page):
    def add_clicked(e):
        tasks_view.controls.append(ft.Checkbox(label=new_task.value))
        new_task.value = ""
        view.update()

    new_task = ft.TextField(hint_text="ToDoを入力してください", expand=True)
    tasks_view = ft.Column()
    view=ft.Column(
        width=600,
        controls=[
            ft.Row(
                controls=[
                    new_task,
                    ft.FloatingActionButton(icon=ft.Icons.ADD, on_click=add_clicked),
                ],
            ),
            tasks_view,
        ],
    )

    page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
    page.add(view)

ft.app(main)

アプリ全体をページの上部中央に配置し、幅を600ピクセルにしています。

TextFieldとFloatingButtonの「+」ボタンは水平に揃え、アプリの幅全体を占めるようにします。

再利用可能なUIコンポーネント作成

関数内のコード記述を続けることも可能ですが、再利用可能なUIコンポーネントにまとめるのがよいです。

ここでは、クラス化によってコードの可読性を高めることを意識しています。

import flet as ft

class TodoApp(ft.Column):
    def __init__(self):
        super().__init__()
        self.new_task = ft.TextField(hint_text="ToDoを入力してください", expand=True)
        self.tasks_view = ft.Column()
        self.width = 600
        self.controls = [
            ft.Row(
                controls=[
                    self.new_task,
                    ft.FloatingActionButton(
                        icon=ft.Icons.ADD, on_click=self.add_clicked
                    ),
                ],
            ),
            self.tasks_view,
        ]

    def add_clicked(self, e):
        self.tasks_view.controls.append(ft.Checkbox(label=self.new_task.value))
        self.new_task.value = ""
        self.update()

def main(page: ft.Page):
    page.title = "Flet ToDo App"
    page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
    page.update()
    todo = TodoApp()
    page.add(todo)

ft.app(main)

リストの表示/編集/削除

タスク項目がチェックボックスとして表示される簡易なToDoアプリが作成されました。

タスク名(チェックボックスのラベル)の横に「編集」と「削除」ボタンを追加し、機能とUIを改良します。

また、「編集」ボタン押下時はタスク名を編集モードに切り替えています。

各タスク項目はdisplay_viewによる「チェックボックス」「編集ボタン」「削除ボタン」のある行と、edit_viewによる「テキストフィールド」「保存ボタン」を実装します。

また、display_viewとedit_viewを新たなTaskクラスで実装します。

class Task(ft.Column):
    def __init__(self, task_name, task_delete):
        super().__init__()
        self.task_name = task_name
        self.task_delete = task_delete
        self.display_task = ft.Checkbox(value=False, label=self.task_name)
        self.edit_name = ft.TextField(expand=1)

        self.display_view = ft.Row(
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.display_task,
                ft.Row(
                    spacing=0,
                    controls=[
                        ft.IconButton(
                            icon=ft.Icons.CREATE_OUTLINED,
                            tooltip="Edit To-Do",
                            on_click=self.edit_clicked,
                        ),
                        ft.IconButton(
                            ft.Icons.DELETE_OUTLINE,
                            tooltip="Delete To-Do",
                            on_click=self.delete_clicked,
                        ),
                    ],
                ),
            ],
        )

        self.edit_view = ft.Row(
            visible=False,
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.edit_name,
                ft.IconButton(
                    icon=ft.Icons.DONE_OUTLINE_OUTLINED,
                    icon_color=ft.Colors.GREEN,
                    tooltip="Update To-Do",
                    on_click=self.save_clicked,
                ),
            ],
        )
        self.controls = [self.display_view, self.edit_view]

    def edit_clicked(self, e):
        self.edit_name.value = self.display_task.label
        self.display_view.visible = False
        self.edit_view.visible = True
        self.update()

    def save_clicked(self, e):
        self.display_task.label = self.edit_name.value
        self.display_view.visible = True
        self.edit_view.visible = False
        self.update()

    def delete_clicked(self, e):
        self.task_delete(self)

また、「追加」ボタンがクリックされたときにTaskクラスのインスタンスを保持するようにToDoAppクラスを変更しました。

class TodoApp(ft.Column):
    def __init__(self):
        super().__init__()
        self.new_task = ft.TextField(hint_text="ToDoを入力してください", expand=True)
        self.tasks = ft.Column()
        self.width = 600
        self.controls = [
            ft.Row(
                controls=[
                    self.new_task,
                    ft.FloatingActionButton(
                        icon=ft.Icons.ADD, on_click=self.add_clicked
                    ),
                ],
            ),
            self.tasks,
        ]

    def add_clicked(self, e):
        task = Task(self.new_task.value, self.task_delete)
        self.tasks.controls.append(task)
        self.new_task.value = ""
        self.update()

    def task_delete(self, task):
        self.tasks.controls.remove(task)
        self.update()
全体コード
import flet as ft

class Task(ft.Column):
    def __init__(self, task_name, task_delete):
        super().__init__()
        self.task_name = task_name
        self.task_delete = task_delete
        self.display_task = ft.Checkbox(value=False, label=self.task_name)
        self.edit_name = ft.TextField(expand=1)

        self.display_view = ft.Row(
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.display_task,
                ft.Row(
                    spacing=0,
                    controls=[
                        ft.IconButton(
                            icon=ft.Icons.CREATE_OUTLINED,
                            tooltip="Edit To-Do",
                            on_click=self.edit_clicked,
                        ),
                        ft.IconButton(
                            ft.Icons.DELETE_OUTLINE,
                            tooltip="Delete To-Do",
                            on_click=self.delete_clicked,
                        ),
                    ],
                ),
            ],
        )

        self.edit_view = ft.Row(
            visible=False,
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.edit_name,
                ft.IconButton(
                    icon=ft.Icons.DONE_OUTLINE_OUTLINED,
                    icon_color=ft.Colors.GREEN,
                    tooltip="Update To-Do",
                    on_click=self.save_clicked,
                ),
            ],
        )
        self.controls = [self.display_view, self.edit_view]

    def edit_clicked(self, e):
        self.edit_name.value = self.display_task.label
        self.display_view.visible = False
        self.edit_view.visible = True
        self.update()

    def save_clicked(self, e):
        self.display_task.label = self.edit_name.value
        self.display_view.visible = True
        self.edit_view.visible = False
        self.update()

    def delete_clicked(self, e):
        self.task_delete(self)

class TodoApp(ft.Column):
    def __init__(self):
        super().__init__()
        self.new_task = ft.TextField(hint_text="ToDoを入力してください", expand=True)
        self.tasks = ft.Column()
        self.width = 600
        self.controls = [
            ft.Row(
                controls=[
                    self.new_task,
                    ft.FloatingActionButton(
                        icon=ft.Icons.ADD, on_click=self.add_clicked
                    ),
                ],
            ),
            self.tasks,
        ]

    def add_clicked(self, e):
        task = Task(self.new_task.value, self.task_delete)
        self.tasks.controls.append(task)
        self.new_task.value = ""
        self.update()

    def task_delete(self, task):
        self.tasks.controls.remove(task)
        self.update()

def main(page: ft.Page):
    page.title = "Flet ToDo App"
    page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
    page.update()
    todo = TodoApp()
    page.add(todo)

ft.app(main)

ここまでの実装が正常に動作するか確認しましょう。

「編集」と「削除」が実施できることが確認できました。

リストのフィルタリングとフッター作成

タスクの「作成」「編集」「削除」といった機能的な実装ができたので、タスクをステータス別でフィルタリングしていきます。

# ...省略

class TodoApp(ft.Column):
    def __init__(self):
        super().__init__()
        self.new_task = ft.TextField(hint_text="ToDoを入力してください", expand=True)
        self.tasks = ft.Column()

        self.filter = ft.Tabs(
            selected_index=0,
            on_change=self.tabs_changed,
            tabs=[ft.Tab(text="ALL"), ft.Tab(text="実行中"), ft.Tab(text="完了")],
        )

# ....省略

タスクのステータスに応じてリストを表示するため、「ALL」「実行中」「完了」の3つにタブを分けています。

また、ここでは同じリストを保持しステータスに応じたタスク表示のみを実行する方法になります。

before_update()は全てのタスクを反復処理し、タスクのステータスに応じてプロパティを更新します。

# ...省略

def before_update(self):
    status = self.filter.tabs[self.filter.selected_index].text
    for task in self.tasks.controls:
        task.visible = (
            status == "ALL"
            or (status == "実行中" and task.completed == False)
            or (status == "完了" and task.completed)
        )

# ....省略

フィルタリングはタブをクリックする、あるいはステータス変更時に実行されます。

そのため、タブ選択時とタスクのステータス変更時の関数をそれぞれ追加します。

class TodoApp(ft.Column):

# ...省略

    def tabs_changed(self, e):
        self.update()

    def task_status_change(self, e):
        self.update()

    def add_clicked(self, e):
        # 
        task = Task(self.new_task.value, self.task_status_change, self.task_delete)

# ...省略

class Task(ft.Column):
    def __init__(self, task_name, task_status_change, task_delete):
        super().__init__()
        self.completed = False
        self.task_name = task_name
        self.task_status_change = task_status_change
        self.task_delete = task_delete
        self.display_task = ft.Checkbox(
            value=False, label=self.task_name, on_change=self.status_changed
        )

    def status_changed(self, e):
        self.completed = self.display_task.value
        self.task_status_change()

# ...省略

また、フッターによるカウント数の表示と一部を追加/調整し、以下の全体コードになります。

本アプリの全体コード
import flet as ft

class Task(ft.Column):
    def __init__(self, task_name, task_status_change, task_delete):
        super().__init__()
        self.completed = False
        self.task_name = task_name
        self.task_status_change = task_status_change
        self.task_delete = task_delete
        self.display_task = ft.Checkbox(
            value=False, label=self.task_name, on_change=self.status_changed
        )
        self.edit_name = ft.TextField(expand=1)

        self.display_view = ft.Row(
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.display_task,
                ft.Row(
                    spacing=0,
                    controls=[
                        ft.IconButton(
                            icon=ft.icons.CREATE_OUTLINED,
                            tooltip="Edit To-Do",
                            on_click=self.edit_clicked,
                        ),
                        ft.IconButton(
                            ft.icons.DELETE_OUTLINE,
                            tooltip="Delete To-Do",
                            on_click=self.delete_clicked,
                        ),
                    ],
                ),
            ],
        )

        self.edit_view = ft.Row(
            visible=False,
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.edit_name,
                ft.IconButton(
                    icon=ft.icons.DONE_OUTLINE_OUTLINED,
                    icon_color=ft.colors.GREEN,
                    tooltip="Update To-Do",
                    on_click=self.save_clicked,
                ),
            ],
        )
        self.controls = [self.display_view, self.edit_view]

    def edit_clicked(self, e):
        self.edit_name.value = self.display_task.label
        self.display_view.visible = False
        self.edit_view.visible = True
        self.update()

    def save_clicked(self, e):
        self.display_task.label = self.edit_name.value
        self.display_view.visible = True
        self.edit_view.visible = False
        self.update()

    def status_changed(self, e):
        self.completed = self.display_task.value
        self.task_status_change(self)

    def delete_clicked(self, e):
        self.task_delete(self)

class TodoApp(ft.Column):
    def __init__(self):
        super().__init__()
        self.new_task = ft.TextField(
            hint_text="ToDoを入力してください", on_submit=self.add_clicked, expand=True
        )
        self.tasks = ft.Column()

        self.filter = ft.Tabs(
            scrollable=False,
            selected_index=0,
            on_change=self.tabs_changed,
            tabs=[ft.Tab(text="ALL"), ft.Tab(text="実行中"), ft.Tab(text="完了")],
        )

        self.items_left = ft.Text("0 items left")

        self.width = 600
        self.controls = [
            ft.Row(
                [ft.Text(value="Todos", theme_style=ft.TextThemeStyle.HEADLINE_MEDIUM)],
                alignment=ft.MainAxisAlignment.CENTER,
            ),
            ft.Row(
                controls=[
                    self.new_task,
                    ft.FloatingActionButton(
                        icon=ft.icons.ADD, on_click=self.add_clicked
                    ),
                ],
            ),
            ft.Column(
                spacing=25,
                controls=[
                    self.filter,
                    self.tasks,
                    ft.Row(
                        alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
                        vertical_alignment=ft.CrossAxisAlignment.CENTER,
                        controls=[
                            self.items_left,
                            ft.OutlinedButton(
                                text="Clear completed", on_click=self.clear_clicked
                            ),
                        ],
                    ),
                ],
            ),
        ]

    def add_clicked(self, e):
        if self.new_task.value:
            task = Task(self.new_task.value, self.task_status_change, self.task_delete)
            self.tasks.controls.append(task)
            self.new_task.value = ""
            self.new_task.focus()
            self.update()

    def task_status_change(self, task):
        self.update()

    def task_delete(self, task):
        self.tasks.controls.remove(task)
        self.update()

    def tabs_changed(self, e):
        self.update()

    def clear_clicked(self, e):
        for task in self.tasks.controls[:]:
            if task.completed:
                self.task_delete(task)

    def before_update(self):
        status = self.filter.tabs[self.filter.selected_index].text
        count = 0
        for task in self.tasks.controls:
            task.visible = (
                status == "ALL"
                or (status == "実行中" and task.completed == False)
                or (status == "完了" and task.completed)
            )
            if not task.completed:
                count += 1
        self.items_left.value = f"{count} active item(s) left"

def main(page: ft.Page):
    page.title = "ToDo App"
    page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
    page.scroll = ft.ScrollMode.ADAPTIVE
    page.add(TodoApp())

ft.app(main)

実行画面が以下になります。

ToDoアプリのexe化

最後に、開発したToDoアプリをexe化します。

fletコマンドでexe化を実現するためには、別パッケージのPyInstallerをインストールする必要があります。

pip install pyinstaller

PyInstallerをインストールすることで、fletコマンドからパッケージ化できるようになります。

flet pack <your_program.py> --name <bundle_name>

今回のアプリの場合は、以下で実行しています。

flet pack -n todo -D main.py

また、flet pack -hにて各オプションを確認できます。

最後は、作成されたtodo.exeファイルを起動することでアプリを無事実行できました。

Python製デスクトップアプリのexe化

実行ファイル形式であるexe化は、いくつか方法があります。

スクロールできます
ツール名概要
PyInstallerWindows/Mac/Linuxに対応した変換ツールで単一ファイルに固められる
cx_FreezeWindows/Mac/Linuxに対応した変換ツールで設定ファイルに詳細内容を指定できる
Py2exeWindows用の実行ファイルに変換するツール
Py2appMacOS用の実行ファイルに変換するツール

以下は、PyInstllerによる実行ファイル作成を実施します。

PyInstallerによるexeファイル作成

ここでは、PyInstallerによるexeファイル作成を実施します。

以下のコマンドで任意のディレクトリにPyInstallerをインストールします。

python -m pip install PyInstaller

また、exe化したいファイルがあれば以下のコマンドで実行できます。

pyinstaller --onefile --noconsole memo.py

適宜、必要なオプションを利用すると管理しやすくなります。

スクロールできます
オプション説明
–onedirまたは-D出力を一つのディレクトリにまとめる
–onefileまたは-F実行ファイルを一つにまとめる
–noconsoleまたは-wターミナルを非表示にする
–clean前回作成したキャッシュファイルを削除してから再作成
PyInstallerのオプション

Python製デスクトップアプリの配布方法

デスクトップアプリ開発が完遂できた人は、自分以外に利用してもらう場合にアプリの配布方法を考える必要があります。

代表的な配布方法として、以下の方法があります。

アプリの配布方法
  • exeファイル形式で配布する
  • プログラムをそのまま配布する
  • 仮想環境を丸ごと配布する

exe化した実行ファイル形式の配布方法が最も一般的かなと思います。

ただし、プログラムをそのまま配布する場合や仮想環境(例:Docker)を配布する場合は、設定ファイルや必要なパッケージ群を整理/調整する必要があります。

requirements.txtやイメージファイル/コンテナファイルの設定を確認しておきましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

sugiのアバター sugi SUGI

【経歴】玉川大学工学部卒業→新卒SIer企業入社→2年半後に独立→プログラミングスクール運営/受託案件→フリーランスエンジニア&SEOコンサル→Python特化のコンテンツサイトJob Code運営中

コメント

コメントする

目次