はじめに
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の配置に関して
Widgetを配置する方法として pack, grid, placeがあります。
配置する方法 | 用途 | 備考 |
---|---|---|
pack | 指定した方向から詰めて配置 | 2段以上で配置したい場合はパラメータの調整はかなり面倒です |
place | 指定した座標に配置 | 配置は楽だが、後で配置をずらしたいときに、修正範囲が大きくなります |
grid | グリッド形式で配置 | 行列の境界線を引きたいときは、専用のWidget(Separator)を配置します |
サンプルアプリ
以下のような機能を持つサンプルアプリを作ります。
- 画像を選択して表示する
- ラジオボタン、チェックボタンの切り替えを、ログに表示する
- キー入力の結果を表示する (Tkinterのbindを使用します)
# 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化の導入の助けになれば、幸いです。