catch-img

PythonでGUIツールを作成(venv + tkinter + pyinstaller)

Pythonで便利なツールを作っても、月に一度程度しか使わないとコマンドオプションの使い方を忘れてしまったり、そもそもコマンドでのフォルダーの移動方法などを忘れてしまったりすると Pythonツールを使うことを諦めてしまうこともあるでしょう。そこで今回は Pythonを Windowsアプリのような GUIツール化できる tkinter と pyinstaller を紹介します。また、仮想環境を構築する venvにも触れます。


目次[非表示]

  1. 1.Python仮想環境の準備
  2. 2.pyinstallerをインストール
  3. 3.tkinterでGUIアプリを作成
  4. 4.pyinstallerで Pythonスクリプトを exeファイルに
  5. 5.application1.pyのソースコード


Python仮想環境の準備

Visual Studio Codeを起動

まずは Visual Studio Code(以下、VSCODE)を起動し、メニューの「ファイル」から「フォルダーを開く」を選択して(または Ctrl+K、Ctrl+O)適当なフォルダーを開きます。

ターミナルが表示されていない場合は、メニューの「ターミナル」から「新しいターミナル」を選択して(または Ctrl+Shift+@)ターミナルを表示します。


venvで仮想環境を作る

ターミナルで「python -m venv .(ピリオド)」を入力して仮想環境を作成します。

仮想環境が作られると、「Include」、「Lib」、「Scripts」のフォルダーと「pyenv.cfg」というファイルが作られることが分かります。

続いて、ターミナルで「.\Scripts\activate」を実行して仮想環境を有効にします。「s」を入力して Tabキーを押して「.\Scripts」をオートコンプリートし、続けて「a」を入力して Tabキーで「activate」をオートコンプリートし、最後に Enterキーを押します。


pip.iniを作成

以前の記事でも書いた記憶がありますが、使っている PCのネットワーク環境によっては pip(Pythonのパッケージ管理ツール)でサードパーティパッケージをインストールしようとしたときに SSLエラーが発生する場合があるので、pip.iniファイルにインストール元サーバーを信頼済みとして記述することで SSLエラーを回避します。メモ帳などのテキストエディタで php.iniを作成して、作業用フォルダに保存しても構いませんし、ターミナルで pythonを起動して作っても構いません。


作成した php.ini


[global]
trusted-host=pypi.python.org pypi.org files.pythonhosted.org


pyinstallerをインストール

Pythonスクリプトを Windowsの実行形式 exeファイルにする pyinstallerをインストールします。VSCODEのターミナルで「pip install pyinstaller」を実行し、「Successfully installed 以下略」が表示されたらインストールは成功しています。

筆者の PCは別の仮想環境に pyinstallerをインストールしているため、あらためて pyinstallerがダウンロードされることはありませんでしたが、初めてインストールする場合は下図のようにダウンロードしながらインストールされます。

これで準備は完了です。



tkinterでGUIアプリを作成

ここから Pythonによる GUIアプリのプログラミングになります。 tkinterという Pythonの標準パッケージを使います。GUIアプリを作成するためのパッケージはたくさんあり、最近では PySimpleGUIというパッケージが人気なようですが、本記事では Pythonの標準パッケージである tkinterを使いたいと思います。なお、本記事は GUIの作り方の紹介なので、アプリの主処理は省きます。


VSCODEで新規ファイルを開く

VSCODEのメニュー「ファイル」から「新規ファイル」を選択(または Ctrl+N)で新しいファイルを開きます。

「Untitled-1」が開いたら「言語の選択」をクリック。

「言語モードの選択」に「python」を入力して Enterキーを押します。

VSCODEの下方の青いステータスバーに「Python」が表示されます。


tkinterをインポート

tkinterと tkinter.ttk、そしてよく使うモジュールとして filedialog、messageboxをインポートします。tkinterは tk、tkinter.ttkは ttkに別名定義して扱うことが一般的です。


ウィンドウフォーム

tk.Tk() でアプリのメインウィンドウになるフォームを作成します。ここではオブジェクト名を form1とします。


ラベル

tk.Label()でラベルのオブジェクトを作成します。第一引数にラベルの親オブジェクトとなる form1を指定し、表示する文字列を textパラメータで指定します。そして、そのラベルオブジェクトの表示位置を gridで指定します。gridは HTMLの tableタグに似た形でオブジェクトをレイアウトすることができます。rowと columnで行・列を指定します。今回は使っていませんが、 rowspanや columnspanでグリッドセルの結合もできます。 padxと padyはオブジェクトの外側の padding値をピクセル単位で指定できます。stickyはグリッドの中での配置です。tk.Wが左詰め、tk.Eが右詰めを意味します。


テキストボックス

tk.Entry()でテキストボックスを作成します。第一引数に親オブジェクトの form1を指定し、widthパラメータでテキストボックスの幅を指定します。テキストボックスも gridでレイアウトします。


ボタン

tk.Button()でボタンを作成します。上述のテキストボックスにファイル選択ダイアログで選択したファイル名を表示するためのボタンにします。tk.Buttonの第一引数は親オブジェクトの form1を指定し、textパラメータでボタンに表示する文字列を指定します。そして、commandパラメータに実行する命令を記述します。ここではファイル選択の関数として select_file関数を実行するように記述します。ボタンも gridでレイアウトします。ボタンオブジェクトの bind関数で Enterキー(リターンキー)を押したときも select_file関数を実行するようにします。

なお、この時点で select_file関数は未定義なのでエラー表示されています。エラー表示をフォーカスするとエラーの内容を見ることができます。


エラー表示がずっと残っているのも気持ち悪いので、 先に select_file関数を作ってしまいましょう。VSCODEのエディタで前の方にもどり、importのブロックの直下に関数を作ります。select_file関数は、 tkinterの filedialog.askopenfilenameで表示するファイル選択ダイアログで選択されたファイル名を関数の引数で受け取ったテキストボックスにセットするものとします。

filedialog.askopenfilenameの filetypesパラメータでファイル拡張子によるファイルの種類を指定することができます。また、テキストボックス(tkinter.Entry)内の文字列を書き換えるには、一旦、delete関数でテキストボックス内の先頭から末尾までを削除し、insert関数で先頭に文字列を挿入します。

上図の if文にある「:=」は Python3.8で採用されたセイウチ演算子という演算子で、変数に値を代入すると同時に、変数を利用することができる記法です。上の例では、ファイル選ダイアログの戻り値を filename変数に代入して、併せて if文で filenameの有効無効を判定しています。



ラジオボタン

tk.Radiobutton()でラジオボタンを作ることができます。ただ、ここではラジオボタンの説明に入る前に gridで体裁を整える方法をまず説明します。幅の長いテキストボックスの下にラジオボタンなどをきれいに配置する方法です。

# 一行目
label1.grid(row=1, column=1, sticky=tk.W)
textbox1.grid(row=1, column=2, sticky=tk.W)
button1.grid(row=1, column=3, sticky=tk.W)

# 二行目
label2.grid(row=2, column=1, sticky=tk.W)
radio1.grid(row=2, column=2, sticky=tk.W)
radio2.grid(row=2, column=3, sticky=tk.W)
radio3.grid(row=2, column=4, sticky=tk.W)

単純に1列ずつラジオボタンを配置するとこのようになります。

ちなみに、複数オブジェクトを同じセルに配置すると、後に grid指定したオブジェクトが、前のオブジェクト上に上書きされたような表示になります。


columnspanを使う方法


columnspanでグリッドセルを結合することができます。テキストボックスのように幅をとるオブジェクトは結合したセルに配置することで、他の行のセル幅をバランスよくすることができます。行を結合する場合は rowspanを使うことができます。columnspanや rowspanは画面に表示するオブジェクトが少ない場合は問題ありませんが、行数が増え、様々な幅のオプションを表示する場合はきちんと画面設計してからコーディングしないと、画面が崩壊するので注意が必要です。


# 一行目
label1.grid(row=1, column=1, sticky=tk.W)
textbox1.grid(row=1, column=2, columnspan=3, sticky=tk.W)
button1.grid(row=1, column=5, sticky=tk.W)

# 二行目
label2.grid(row=2, column=1, sticky=tk.W)
radio1.grid(row=2, column=2, sticky=tk.W)
radio2.grid(row=2, column=3, sticky=tk.W)
radio3.grid(row=2, column=4, sticky=tk.W)


canvasを使う方法

ラジオボタンをフォーム上に直に配置するのではなく、canvasオブジェクトを作成して、ラジオボタンをそのキャンバスの子オブジェクトにする方法です。この方法だと画面設計の手間をかけずともそれなりのデザインをすることができますので、クライアントに納品するようなアプリケーションではなく、手元で使う簡単な Pythonツールのような類でしたらこの方法が良いのではないでしょうか。


# 一行目
label1.grid(row=1, column=1, sticky=tk.W)
textbox1.grid(row=1, column=2, sticky=tk.W)
button1.grid(row=1, column=3 sticky=tk.W)

# ラジオボタン用キャンバス
canvas1 = tk.Canvas(form1)
canvas1.grid(row=2, column=2, sticky=tk.W)

# 二行目
label2.grid(row=2, column=1, sticky=tk.W)
radio1 = tk.Radiobutton(canvas1, text= 以下略) # 親オブジェクトは canvas1
radio2 = tk.Radiobutton(canvas1, text= 以下略)
radio3 = tk.Radiobutton(canvas1, text= 以下略)
radio1.grid(row=1, column=1, sticky=tk.W)  # canvas1の中での gridなので 1行目から開始
radio2.grid(row=1, column=2, sticky=tk.W)
radio3.grid(row=1, column=3, sticky=tk.W)

メインフォーム上の gridが赤枠、その 2行目の 2列目に canvas1(青枠)を配置して、その中に新たな grid(緑枠)でラジオボタンを配置しているイメージです。

canvasというネーミングからは、大きく(広く)フォーム上に展開して、他のオブジェクトの下地になる使い方が正しいように思いますが、こういう小さなキャンバスも有りですかね・・・?


さて、ラジオボタンの話に戻ります。ラジオボタンは一般的に複数のオプションの中から1つを選択するコントロールオブジェクトですが、第一引数に親オブジェクト、textパラメータにオプションの説明、valueパラメータにはそのオプションを選択したときの値、そして variableに値をセットする変数を指定します。アプリケーションの主処理でどのオプションが選択されているかを判別するときに、この変数を使用します。

tkinterの Radiobuttonでは、複数作成した tk.Radiobuttonオブジェクトに指定する変数に、共通の変数を指定することで、1つのグループとします。下図のコードを見た方が早いでしょう。55行目は tk.IntVar関数で変数 option_varを宣言しています。tk.IntVarの引数、value=0は初期値に 0をセットすることを意味しています。そして、56、58、60行目でラジオボタンオブジェクトを作成する際に、variableパラメータに変数 option_varを指定していることで、ラジオボタンオブジェクト radio01、radio02、radio03を1つのグループにしています。つまり、radio02を選択すると radio01と radio03が非選択状態になり、radio03を選択すると radio01と radio02が非選択になります。


チェックボックス

tk.Checkbutton()でチェックボックスのオプションを作成することができます。第一引数に親オブジェクト、textパラメータにオプションの説明、variableパラメータにチェック状態をセットするための変数を指定します。チェックボックスは ONまたは OFFの二極状態を選択するオプションなので、変数は tk.BooleanVarで宣言した変数になります。


コンボボックス

プルダウン、ドロップダウン、コンボボックスなど呼び方は様々ですが、クリックすると選択肢がリスト表示され、その中の1つを選択することができるオブジェクトです。コンボボックスはこれまでのコントロールオブジェクトとは違い、tkinterパッケージの ttkモジュールに含まれています。

第一引数に親オブジェクト、textvariableパラメータに選択された値をセットするための変数、valuesパラメータに選択肢のタプル(またはリスト)を指定します。コンボボックスで選択した値は文字列として取得できるので、tk.StringVarで定義した変数を使います。また、このコントロールオブジェクトで初めて stateパラメータが出てきましたが、stateパラメータはそのコントロールの状態を指定するもので、readonlyを指定すると読み取り専用になり、disabledを指定すると無効状態、normalを指定すると通常(有効)状態になり、コンボボックス以外でも同様に使えるパラメータです。



実行ボタンと閉じるボタン

Windowsアプリらしく「閉じる」ボタンと「実行」ボタンを作成します。コードの説明は割愛しますが、アプリを閉じるにはメインフォームを quit()することは覚えておきましょう。


このアプリは主処理が無いので、とりあえずよく使う messageboxで警告、エラー、情報のメッセージ表示を入れておきます。



メインウィンドウの詳細設定

フォーム上のコントロールオブジェクトの配置ができたらメインフォームの表示処理にかかります。あらかじめウィンドウサイズが決まっている場合はこれらの処理を tk.Tk()でフォームを作成した直後に行ってもよいのですが、ウィンドウサイズを任せる(すべてのコントロールオブジェクトが表示されるウィンドウサイズに自動的に設定される)場合は、最後に書きます。

form1.update_idletaskes()は前方のコントロールの作成などのタスクが処理されるのを待ちます(実際には、アプリの中で実行中のタスクや不安定になっているタスクを適切に収束させるというもののようです)。form1.titleでウィンドウのヘッダー部にタイトルを表示します。form1.resizableでウィンドウのサイズ変更の可否を指定します。width=True、height=Falseといった変則的な設定も可能です。

続いて 98~98行目。form1.winfo_screenwith()と form1.winfo_screenheight()でフォームが表示される PCのディスプレイサイズ(解像度)、および form1.winfo_width()と form1.winfo_height()でメインウィンドウのサイズを取得。100行目、form1.geometry()でウィンドウサイズと表示位置を指定して、画面の真ん中にウィンドウ表示されるようにしています。geometry('600x480')とするとウィンドウを幅600px、高さ480pxで作成しますが、geometry('600x480+10+20')とすると、600x480pxのウィンドウをディスプレイ座標の(10,20)(左端から10px、上端から20px)にウィンドウの左上の角が位置するように表示します。101行目の form1.deicony()はアイコン表示から戻す命令です。


メインウィンドウを待機状態にする

そして最後の最後に、form1.mainloop()でウィンドウを表示して、何らかのアクションがされるのを待機します。



アプリケーションを保存する

コードを書き終わったら VSCODEのメニュー「ファイル」から「保存」(または Ctrl+S)を選択し、適当なファイル名を付けて保存します。ここでは application1.pyとします。



アプリケーションを実行する

アプリを実行するにはターミナルで「python」、ブランク、アプリのファイル名を指定して Enterキーを押します。コマンドは Tabキーを使ってオートコンプリートしてすばやく入力できるように慣れてください。

前述以外にもコードを一部追加しましたが、アプリを実行すると下図のようなウィンドウが開きます。resizableを False、Falseにしたので、最大化のアイコンもグレーアウトされています。

「実行」ボタンをクリックすると、下図のメッセージボックスが次々と表示されます。また、「閉じる」ボタンをクリックするとアプリが終了します。



pyinstallerで Pythonスクリプトを exeファイルに

コードが完成したら pyinstallerを使って exeファイル化します。

pyinstaller Pythonファイル名 --onefile --noconsole

VSCODEのターミナルで pythoninstallerを実行します。pyinstaller.exeはデフォルトでは Scriptsフォルダにインストールされていますので「.\Script\pyinstaller」と入力し、ブランクを空けて、Pythonファイル名「.\application1.py」を入力。さらにブランクを空けて「--onefile --noconsole」と入力して Enterキーを押します。 

--onefileオプションは exeファイルを1つのファイルにするもので、これを指定しない場合は exeと exeから使用されるライブラリがバラされた複数ファイルが作成されます。アプリを Python環境が無い同僚などに提供する場合は1ファイルにした方がよいのですが、自身で使う場合には --onefileオプションを使わないほうがアプリを軽量化できます。

--noconsoleオプションはアプリを実行したときにコンソールを表示しないオプションです。アプリのデバッグ用 print()を埋め込んでいる場合はこのオプションを外します。

(中略)



実行形式ファイル

pyinstallerで問題なく exeファイルが生成できた場合は「dist」フォルダに保存されます。



exeファイルを実行する

先ほども言いましたが、 --onefileオプションをつけて exe化した Pythonプログラムは Pythonがインストールされていない PCでも実行することが可能です。



application1.pyのソースコード

最後に application.pyのコードを貼っておきます。

import tkinter as tk
import tkinter.ttk as ttk
from tkinter import filedialog
from tkinter import messagebox


def select_file(textbox):
    ftypes = (('テキストファイル', '*.txt'), ('すべてのファイル', '*.*'))
    if filename := filedialog.askopenfilename(filetypes=ftypes):
        textbox.delete(0, tk.END)
        textbox.insert(0, filename)

def select_folder(textbox):
    if dirname := filedialog.askdirectory():
        textbox.delete(0, tk.END)
        textbox.insert(0, dirname)

def submit():
    messagebox.showwarning('tkinter - messagebox', 'showwarning')
    messagebox.showerror('tkinter - messagebox', 'showerror')
    messagebox.showinfo('tkinter - messagebox', 'showinfo')


form1 = tk.Tk()

# ファイル選択
label1 = tk.Label(form1, text='ファイル')
label1.grid(row=1, column=1, padx=1, pady=1, sticky=tk.W)

textbox1 = tk.Entry(form1, width=60)
textbox1.grid(row=1, column=2, padx=1)

button1 = tk.Button(form1, text='参照', command=lambda: select_file(textbox1))
button1.grid(row=1, column=3, ipadx=20)
button1.bind('<Return>', lambda event: select_file(textbox1))

# フォルダ選択
label2 = tk.Label(form1, text='フォルダ')
label2.grid(row=2, column=1, padx=1, pady=1, sticky=tk.W)

textbox2 = tk.Entry(form1, width=60)
textbox2.grid(row=2, column=2, padx=1)

button2 = tk.Button(form1, text='参照', command=lambda: select_folder(textbox2))
button2.grid(row=2, column=3, ipadx=20)
button2.bind('<Return>', lambda event: select_folder(textbox2))

# ラジオボタン
label3 = tk.Label(form1, text='選択')
label3.grid(row=3, column=1, padx=1, pady=1, sticky=tk.W)

canvas1 = tk.Canvas()
canvas1.grid(row=3, column=2, ipadx=1, ipady=1, sticky=tk.W)

option_var = tk.IntVar(value=0)
radio01 = tk.Radiobutton(canvas1, text='オプション①', value=0, variable=option_var)
radio01.grid(row=1, column=1)
radio02 = tk.Radiobutton(canvas1, text='オプション②', value=1, variable=option_var)
radio02.grid(row=1, column=2)
radio03 = tk.Radiobutton(canvas1, text='オプション③', value=2, variable=option_var)
radio03.grid(row=1, column=3)

# チェックボックス
label4 = tk.Label(form1, text='フラグ')
label4.grid(row=4, column=1, padx=1, pady=1, sticky=tk.W)

option_flag = tk.BooleanVar(value=True)
checkbox1 = tk.Checkbutton(form1, text='フラグオプション', variable=option_flag)
checkbox1.grid(row=4, column=2, sticky=tk.W)

# ドロップダウン
label5 = tk.Label(form1, text='ドロップダウン')
label5.grid(row=5, column=1, padx=1, pady=1, sticky=tk.W)

option_str = tk.StringVar(value='東京')
dropdown1 = ttk.Combobox(
    form1,
    textvariable=option_str,
    state='readonly',
    values=('東京','大阪','名古屋','札幌','福岡','沖縄')
    )
dropdown1.grid(row=5, column=2, sticky=tk.W)

# 閉じる/実行ボタン
button3 = tk.Button(form1, text='閉じる', command=lambda: form1.quit())
button3.grid(row=6, column=1, padx=5, pady=5, ipadx=20, sticky=tk.W)
button3.bind('<Return>', lambda event: form1.quit())

button4 = tk.Button(form1, text='実行', command=lambda: submit())
button4.grid(row=6, column=3, padx=5, pady=5, ipadx=20, sticky=tk.E)
button4.bind('<Return>', lambda event: submit())

# ウィンドウを画面の中心に
form1.update_idletasks()
form1.title('tkinter Sample Application')
form1.resizable(False, False)

sw, sh = form1.winfo_screenwidth(), form1.winfo_screenheight()
fw, fh = form1.winfo_width(), form1.winfo_height()
form1.geometry(f'{fw}x{fh}+{sw//2-fw//2}+{sh//2-fh//2}')
form1.deiconify()

# ウィンドウを表示し続ける
form1.mainloop()



トランスコスモスShopifyエンジニア
トランスコスモスShopifyエンジニア
トランスコスモスは日本で7社しかない最上位の「Shopifyプラスパートナー」です。最上位認定を堅持しShopifyでのECサイト制作・Shopifyアプリ開発を担うのが、トランスコスモスShopifyエンジニアチームです。Shopifyアプリの解説・紹介、Shopify基本的な使い方など制作や開発の過程で得られら知見をご紹介します。

Shopify導入についてのサービス問い合わせ

Shopifyアプリ解説・紹介記事


【無料】セミナー動画視聴


EC関連サービス


トランスコスモスのEC全領域を網羅するサービス


従業員(メンバー)インタビュー


トランスコスモスの定量的な強み

お気軽に
お問い合わせください

Shopify(ショッピファイ)
ECストア構築・運用代行

トランスコスモスは日本最上位の「Shopifyプラスパートナー」企業です
スピードロジ
shopify

【週間ランキング】Shopify人気記事

ロジスティクス関連 新着記事

【初心者必見!】Shopifyマニュアル

 Tag(タグ)一覧

 記事検索

 記事カテゴリー

 【無料】募集中セミナー

正社員募集(求人)

 【無料】資料ダウンロード

Shopify認定パートナー企業

shopify plus partner
トランスコスモスは最上位の「Shopify Plus partner(ショッピファイプラスパートナー)」です。
Shopifyでのサイト制作ならお任せください!
ECソリューションをお届けするサービスサイト

トランスコスモス株式会社
デジタルトランスフォーメーション総括 ECX本部

〒150-0011
東京都渋谷区東1-2-20
渋谷ファーストタワー
050-1751-7700(代表)


経済産業省が定める「DX認定事業者」
トランスコスモスは経済産業省が定める「DX認定事業者」
トランスプラス
トランスコスモスの全社的な情報を発信するオウンドメディア
cotra
コンタクト/コールセンターに携わる方への情報サイト
shopify
shopify構築・制作・運用