Python Mac アプリ作成 tkinter py2app

今回は、簡単なMacのスタンドアローンアプリを実装して、動作させてみるメモです。
具体的には、PythonのGUIライブラリTkinterを使用して簡単なアプリを実装し、
その後、py2appを利用してMac用にApp形式にビルドして実行してみます。

尚、dmgファイルを作成する内容は含まれません。
Windows用にexe形式にビルドすることも含まれません。
(しかし、メモの内容で実装してpy2exeを利用すれば、exeファイルを生成できると思います)

環境

  • macOS:Sonoma 14.6
  • PyCharm 2024.1.4 (Professional Edition)
  • Python:3.11
  • Tkinter:8.6
  • py2app:0.28.8

GUIアプリ実装

Tkinterを使用して、以下のような簡単なアプリを実装してみます。

  • 初期表示で、テキストエリア、リセットボタン、メニュー(フォルダ選択、解析結果保存、終了)を配置
  • フォルダを選択した場合そのフォルダ内容を解析して、
    存在するフォルダとファイルをテキストエリア部分に一覧表示する

    • サイズ(MB)表示有り
    • 末尾にトータルファイル数表示、トータルサイズ(MB)表示
  • Save Structureで、現状のテキストエリアの内容を保存

Tkinter py2app Mac App Sample

Tkinter py2app Mac App Sample

Tkinter py2app Mac App Sample

Tkinter py2app Mac App Sample

実装コード

import tkinter as tk
from tkinter import filedialog
import os
import threading


class FolderAnalyzeApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Folder Structure Viewer")

        # メインフレームを作成
        main_frame = tk.Frame(self.root)
        main_frame.pack(expand='yes', fill='both')

        # テキストエリアを作成し、メインフレームに配置
        self.text_area = tk.Text(main_frame, wrap='word')
        self.text_area.pack(expand='yes', fill='both', side='top')

        # リセットボタンを作成し、メインフレームに配置
        reset_button = tk.Button(main_frame, text="Reset", command=self.reset_text_area)
        reset_button.pack(side='bottom')

        # メニューバーを作成
        menu_bar = tk.Menu(self.root)
        file_menu = tk.Menu(menu_bar, tearoff=0)
        file_menu.add_command(label="Select Folder", command=self.select_folder)
        file_menu.add_command(label="Save Structure", command=self.save_structure)
        file_menu.add_separator()
        file_menu.add_command(label="Exit", command=self.exit_app)
        menu_bar.add_cascade(label="File", menu=file_menu)

        # メニューバーをウィンドウに設定
        self.root.config(menu=menu_bar)

    def select_folder(self):
        # フォルダ選択ダイアログを表示
        folder_path = filedialog.askdirectory()
        if folder_path:
            # 別スレッドでフォルダ構造を取得
            threading.Thread(target=self.process_folder, args=(folder_path,)).start()

    def process_folder(self, folder_path):
        # 処理中メッセージを表示
        self.text_area.delete(1.0, tk.END)
        self.text_area.insert(tk.END, "Processing...\n")
        self.root.update_idletasks()

        # フォルダ構造を取得し、テキストエリアに挿入
        folder_structure, total_files, total_size = self.get_folder_structure(folder_path)
        self.text_area.delete(1.0, tk.END)
        self.text_area.insert(tk.END, folder_structure)
        # 総ファイル数と総サイズをテキストエリアの最後に挿入
        self.text_area.insert(tk.END, f"\nTotal files: {total_files}\n")
        self.text_area.insert(tk.END, f"Total size: {total_size:.2f} MB\n")

    def get_folder_structure(self, folder_path):
        structure = ""
        total_files = 0
        total_size = 0.0
        # フォルダを走査
        for root, dirs, files in os.walk(folder_path):
            level = root.replace(folder_path, '').count(os.sep)
            indent = ' ' * 4 * level
            folder_size = sum(os.path.getsize(os.path.join(root, f)) for f in files) / (1024 * 1024)
            structure += f"{indent}{os.path.basename(root)}/ ({folder_size:.2f} MB)\n"
            sub_indent = ' ' * 4 * (level + 1)
            for f in files:
                # ファイルサイズを取得し、MBに変換
                file_size = os.path.getsize(os.path.join(root, f)) / (1024 * 1024)
                structure += f"{sub_indent}{f} ({file_size:.2f} MB)\n"
                total_files += 1
                total_size += file_size
        return structure, total_files, total_size

    def save_structure(self):
        # ファイル保存ダイアログを表示
        file_path = filedialog.asksaveasfilename(defaultextension=".txt",
                                                 filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")])
        if file_path:
            # テキストエリアの内容をファイルに書き込む
            with open(file_path, 'w') as file:
                file.write(self.text_area.get(1.0, tk.END))

    def reset_text_area(self):
        # テキストエリアをクリア
        self.text_area.delete(1.0, tk.END)

    def exit_app(self):
        # アプリケーションを終了
        self.root.quit()


if __name__ == "__main__":
    root = tk.Tk()
    app = FolderAnalyzeApp(root)
    root.mainloop()

py2app Appビルド

作成したPythonプログラムをApp形式にビルドしてみます。
今回は、py2appを利用します。

py2appインストール

pip3 install -U py2app

pip install -U py2app

後述するsetup.pyを先に用意した場合、以下コマンドよりインストールすることもできます。

python setup.py install

setup.py 作成

py2appでアプリをビルドするには、
setup.pyという設定ファイルが必要になります。
以下のコマンドでsetup.pyを自動生成することができます。

py2applet --make-setup FolderAnalyzeApp.py(GUIアプリのPythonファイルを指定)

py2applet --make-setup App.py

py2applet --make-setup App.py

自動生成したままでも利用できますが、
簡単なアイコンなどを設定してみたいと思います。

"""
This is a setup.py script generated by py2applet

Usage:
    python setup.py py2app
"""

from setuptools import setup

APP = ['FolderAnalyzeApp.py']
DATA_FILES = []
OPTIONS = {
    'iconfile': 'logo.icns',  # アイコンファイルのパスを指定
    'plist': {
        'CFBundleName': 'FolderAnalyzeApp',  # アプリケーション名
        'CFBundleDisplayName': 'Folder Analyze App',  # 表示名
        'CFBundleVersion': '0.1.0',  # バージョン
        'CFBundleIdentifier': 'com.test.FolderAnalyzeApp',  # バンドル識別子
    },
}

setup(
    app=APP,
    data_files=DATA_FILES,
    options={'py2app': OPTIONS},
    setup_requires=['py2app'],
)

This is a setup.py script generated by py2applet

ビルド実行

以下コマンドでビルドを行います。

python setup.py py2app

Done!の文字が表示されて、エラーが無ければ、distというフォルダが生成されます。

python setup.py py2app

もしもエラーになった場合や、再度ビルドしたい場合には、
こちらのdistフォルダごと削除して再実行を行います。

python setup.py py2app dist

IDEによって見え方は異なりますが、
Finder等で表示すると、アプリケーションとして認識されます。

py2app app build

py2app app build

Finder等でアプリ名.appファイルをダブルクリックするだけで起動することができます。

py2app app build run

py2app app build run


今回のメモは以上となります。
Tkinterとpy2appを利用すれば、
Pythonでも簡単にMacアプリを作成できます。

ただし、少し試した感じですと、
入力系などのところでシビアな性能を求められるアプリの場合、
Tkinterだけですと調整が困難になる気がします。

しかし、ちょっとした自作ツールを作成するには十分だと思います。
今度、dmgファイルとして配布する場合の対応もメモしてみたいと思います。

都内でエンジニアをやっています。 2017年に脱サラ(法人設立)しました。 仕事で調べたことや、気になったことをメモしています。
投稿を作成しました 173

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


関連投稿

検索語を上に入力し、 Enter キーを押して検索します。キャンセルするには ESC を押してください。

トップに戻る