LoginSignup
16

More than 1 year has passed since last update.

PythonアプリをGUI化、Exe化で使いやすくしよう

Last updated at Posted at 2023-02-22

はじめに

Pythonは機械学習を気軽に試すには便利な言語です。しかしながら、作ったものを非エンジニアの人に使ってもらおうと思うと、環境構築やコマンドライン実行はハードルが高いものです。
非エンジニアの人が気軽に使えるように、GUIの導入とExe化を行ったので、記事にまとめました。
GUI化はTkinter、Exe化はPyinstallerを使います。

開発環境

  • python:3.10.9
  • conda:4.10.1
  • pyinstaller:5.6.2
  • pillow:9.3.0 (画像変換用)

Tkinterとは?

PythonでGUIを構築するためのライブラリです。ティーケーインターと読みます。
Pythonの標準ライブラリなので、追加でパッケージを入れる必要はありません。

公式ドキュメント:https://docs.python.org/ja/3.10/library/tk.html

Widget

WidgetはTkinterのGUIの部品です。
Widget内の文字列やチェックボタンの状態を動的に設定・取得したい場合は、ウィジェット変数(StringVar・BooleanVar・IntVar・DoubleVar)をバインドして使用します。
Tkinterが用意してくれているWidgetはいっぱいありますが、よく使いそうなWidgetを記載します。

Widget 用途 見た目 機械学習での利用例
Label テキストラベル image.png 説明の表示
Entry テキストボックス(1行) image.png パラメータの指定
Text テキストボックス(複数行) image.png ログの表示
Button ボタン image.png 開始・停止・ファイル読み込みボタン
Checkbutton チェックボタン image.png パラメータの指定
Radiobutton ラジオボタン image.png パラメータの指定
Canvas 画像表示 - 読み込んだ画像の表示、画像認識した結果の表示
Frame Widgetをまとめる箱 - Widgetの配置を見やすくするために

Widgetの配置に関して

Widgetを配置する方法として pack, grid, placeがあります。

配置する方法 用途 備考
pack 指定した方向から詰めて配置 2段以上で配置したい場合はパラメータの調整はかなり面倒です
place 指定した座標に配置 配置は楽だが、後で配置をずらしたいときに、修正範囲が大きくなります
grid グリッド形式で配置 行列の境界線を引きたいときは、専用のWidget(Separator)を配置します

サンプルアプリ

以下のような機能を持つサンプルアプリを作ります。

  • 画像を選択して表示する
  • ラジオボタン、チェックボタンの切り替えを、ログに表示する
  • キー入力の結果を表示する (Tkinterのbindを使用します)

以下のコードでこのような見た目になります。
image.png

sample.py
# Tkinterライブラリのインポート(ティーケーインター : Python標準)
import tkinter as tk
from tkinter import filedialog
import tkinter.ttk as ttk
import os
# jpeg変換用
from PIL import Image, ImageTk

IMG_WIDTH  = 250
IMG_HEIGHT = 250

# Window作成
root = tk.Tk()
# 常に最前表示
root.attributes("-topmost", True)
# Windowsのタイトル設定
root.title("Tkinter Sample")
# 「幅x高さ+x座標+y座標」のフォーマットで指定
root.geometry('350x500+50+100')

####################################################################
# 画像を選択して表示する
# 配置:グリッド形式
####################################################################
# 画像ファイル選択ボタンが押された時の処理
def select(): 
    # 選択可能な拡張子を指定
    filetype = [("Image file", ".png .jpg .gif")]
    # ファイル選択の初期表示のディレクトリ
    path = os.path.abspath(os.path.dirname(__file__))
    # ファイル選択ダイアログ表示
    cap_filepath = filedialog.askopenfilename(filetype = filetype, initialdir = path)
    # Widgetにbindされた変数にファイル名を設定
    filename = os.path.basename(cap_filepath)
    cap_filename.set( filename )
    display_image(cap_filepath)

# 画像表示
def display_image(file): 
    # create_image()実行には時間がかかる。
    # 関数を抜けてもImageを保持しておく必要があるので、globalで定義
    global img
    img = None
    # tkinterではjpegをサポートしてないので、他のライブラリを使って変換が必要
    img = Image.open(open(file, 'rb'))
    img.thumbnail((IMG_WIDTH, IMG_HEIGHT), Image.ANTIALIAS)
    img = ImageTk.PhotoImage(img)
    canvas.create_image(  # キャンバス上にイメージを作成
       0,  # x座標
       0,  # y座標
       image=img,
       anchor=tk.NW  # 配置の起点を指定
    )

# グリッド配置用のフレームを作成
frame_grid = tk.Frame(root, 
    relief=tk.GROOVE,  # フレームの枠線の種類を指定
    borderwidth=2)
frame_grid.pack(side=tk.TOP)
# グリッド内にフレームを作成
frame_file = tk.Frame(frame_grid)
# 1行、1列目に配置
frame_file.grid(column=0, row=0, padx=5, pady=5)
# 画像ファイル選択ボタン
btn_select = tk.Button(frame_file, 
    text="File Select",  # ボタンに表示するテキスト
    command=select,      # ボタン選択時の処理
    bg='#2196f3')
btn_select.pack(side=tk.LEFT, padx=10)

# ファイル名表示用のテキストボックス
# 文字列型のウィジェット変数を準備
cap_filename = tk.StringVar()
textbox_cap = tk.Entry(frame_file, 
    textvariable=cap_filename,  # ウィジェット変数をバインドする
    width=30) # widthはピクセル数ではなく、システムフォント'0'の入る個数
textbox_cap.pack(side=tk.LEFT, padx=10)

# グリッドの境界線を引く
separator = ttk.Separator(frame_grid, 
    orient="horizontal")  # 境界線の方向(horizontal:水平方向、vartival:垂直方向)
separator.grid(column=0, row=1, 
    sticky="ew") # ew:水平方向に配置

# 画像ファイル表示
canvas = tk.Canvas(frame_grid, width=IMG_WIDTH,  height=IMG_HEIGHT )
canvas.grid(column=0, row=2, padx=5, pady=5)


####################################################################
# ラジオボタン、チェックボタンの切り替えを、ログに表示する
# 配置:pack形式
####################################################################
# ラジオボタンが押されたときの処理
def display_radio() : 
    if radio_var.get() == 0:
        # 1行目、0文字目に表示
        logbox.insert(1.0, "ラジオボタン:A\n")
    else:
        logbox.insert(1.0, "ラジオボタン:B\n")

# チェックボタンが押されたときの処理
def display_chkbox() : 
    if chkbox_var.get():
        logbox.insert(1.0, "チェックボックス:ON\n")
    else:
        logbox.insert(1.0, "チェックボックス:OFF\n")

# グリッド配置用のフレームを作成
frame_pack = tk.Frame(root, relief=tk.GROOVE, borderwidth=2)
frame_pack.pack(side=tk.TOP, pady=5)

# ラジオボタン
radio_frame = tk.Frame(frame_pack, padx=3, pady=3)
radio_frame.pack(side=tk.TOP)
# 整数型のウィジェット変数を準備
radio_var = tk.IntVar( value = 1 )  # 初期値を設定
radio1 = tk.Radiobutton(radio_frame, 
    value=0,
    variable=radio_var, # ウィジェット変数をバインドする
    text='A',
    command=display_radio) # ラジオボタン選択時の処理
radio1.pack(side=tk.LEFT)
radio2 = tk.Radiobutton(radio_frame,
    value=1,
    variable=radio_var,
    text='B',
    command=display_radio)
radio2.pack(side=tk.LEFT)

# チェックボタン
# Boolean型のウィジェット変数を準備
chkbox_var = tk.BooleanVar()
chkbox_var.set(True) # 初期値を設定
chkbox = tk.Checkbutton(frame_pack, 
    variable=chkbox_var,
    text="Checkbutton",
    command=display_chkbox) # チェックボタン選択時の処理
chkbox.pack(side=tk.TOP)

# ログ表示 エリア
log_label = tk.Label(frame_pack, text='ログ')
log_label.pack(side=tk.LEFT, padx=5)

logbox = tk.Text(frame_pack, width=40, height=5)
logbox.pack(side=tk.TOP)

####################################################################
# キー入力の結果を表示する (Tkinterのbindを使用します)
# 配置:place形式
####################################################################
# 入力されたキーをtextbox_keyに表示
def key_press(e):
    # Entryの中を一度消して、再設定
    textbox_key.delete( 0, tk.END ) 
    textbox_key.insert( 0, e.char+" Press" ) 

# キー入力確認
lbl_key = tk.Label(root, text='キー入力')
lbl_key.place(x=20, y=445)

textbox_key = tk.Entry(root, width=20)
textbox_key.place(x=50, y=470)
textbox_key.insert(0, "キーを押してください") 
# キーバインド
root.bind('<KeyPress>', key_press)


# 表示開始+ループしてイベント発生を待つ
root.mainloop()

Pyinstallerとは?

Pythonアプリケーションとその依存関係を1つのパッケージするためのツールです。
公式ドキュメント:https://pyinstaller.org/en/stable/

インストール

condaで環境構築しているので、condaでインストールします。
pipで環境構築している人はpipを使用してください。

conda install pyinstaller

Exe化

pyinstaller sample.py --onefile --noconsole

このコマンドで、distフォルダの下にsample.exe が作成できます。

オプションの説明
--onefile:1ファイルで実行ファイルを作成します。アプリの起動は、1ファイルにしない方が早いです。
--noconsole:標準的な入出力のためのコンソールウィンドウを表示しない。

まとめ

今回は各Widgetの詳細な説明や個人的に利用頻度の低いと思うWidgetの説明は省略しましたが、他にも様々な機能があるのでもっとリッチなUIにすることもできます。
この記事がPythonアプリGUI化、Exe化の導入の助けになれば、幸いです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16