スポンサーリンク
スポンサーリンク

【Python】デスクトップアプリの作り方(GUI実装とexe化)

緑の波Python

「Pythonでデスクトップアプリの作り方が知りたい。」

「デスクトップアプリにはGUIが必要だけど、どのライブラリがいいの?」

上記の疑問にお答えします。

Pythonでデスクトップアプリを作成したい人に向けて、プログラミング学習をはじめて間もない方でも簡単につくれるように、実例を入れつつ説明します。

具体的には以下について解説します。

  • GUI実装に必要なライブラリ
  • GUI実装のソースコード&解説
  • 実行ファイルの作成方法

 

「偉そうに語るおまえは誰やねん。」と思われるので、私のことも少し。

この記事を書いている私ですが、プログラミング歴は約5年です。

今でもPythonやWeb系のプログラミングを勉強しつつ、プログラミングスキルを活かして仕事の効率化を図ったり、ゲームをつくったりしています。

 

別のパソコンやほかの人が使う場合、せっかくプログラミングをしても、相手にPythonを実行する環境がなければ使えません。そんなときデスクトップアプリにして渡せば、パソコン環境に関係なく使うことができます。

仕事や趣味にいろいろアイデアをお持ちの方は、今回お伝えする方法を参考にデスクトップアプリをつくって、お友達や同僚と共有してみてください。

 

1.GUI実装に必要なライブラリ

まず、これからPythonを学ぶ方でも扱いやすい、簡単にGUIを実装できる「Tkinter」と「PySimpleGUI」という2つのライブラリをご紹介します。

ここではそれぞれの特徴をざっくりと説明し、そのあとで具体的な実装例をお見せしたいと思います。

・Tkinter

Tkinterは、PythonでGUI構築、操作のための標準ライブラリです。

標準ライブラリなのでインストールは不要で、importで呼び出すだけで使用できます。

他にも次のような特徴があります。

  • 主要OS(Window、Mac、Linux)に対応
  • 文法がシンプルで記述しやすい一方で、コードが煩雑になりがち
  • デフォルトのデザインが凡庸。変更はできるけど手間

 

そのまま使うとデザインはおしゃれとは言えませんが、OSはWindow、Mac、Linuxに対応しているので、実行環境に神経質にならずに利用できる使い勝手のよいライブラリです。

実装もシンプルなコーディングで出来るので、手軽にGUIを開発することができます。ただ、簡素なレイアウトのうちは気になりませんが、ボタンなど、パーツごとに記述しないといけないため、パーツが多くなるにつれソースコードが煩雑になりがちです。

それでも私の経験上、デザイン云々よりも素早く実装して個人や社内で使いたい場合は、TKinterで十分だと思います。

 

・PySimpleGUI

PySimpleGUIは、前述のTkinterやQt、WxPythonなどGUIライブラリのラッパー(もともとの機能などを変えずに別の環境に提供する仕組み)です。

主な特徴は、次のとおりです。

  • Tkinterと違いライブラリのインストールが必要
  • TkinterやQt、WxPythonなどと比べて少ないコード量でGUIが実装できる
  • デザインテーマが沢山ある

 

使ってみた感触では、直感的なコーディングでGUIが実装できるのがいいですね。コード量も少なく済む印象です。このあたりは実例のソースコードを提示するので、TkinterとPySimpleGUIを比較して判断いただければと思います。

デザインテーマも140種類と豊富。好みのテーマを選ぶだけなので、デザインに頭を悩ませることが少なくなります。

デザインに配慮したい方はTkinterよりもPySimpleGUIがおすすめです。

 

2.GUI実装のソースコード&解説

では、実際にTkinterとPySimpleGUIでGUIをつくってみたいと思います。

GUI以外の処理部分は、次の記事でつくったPDFファイルをWordファイルに変換するプログラムをベースに作成しています。(入出力フォルダを指定できるよう少し変更しています。)

詳細な説明は割愛しますので、気になる方はこちらの記事もご覧ください。

【Python】複数のPDFファイルをWordファイルに変換する方法
Pythonのpdf2docxライブラリを使って複数のPDFファイルをWordファイルに変換する方法を解説しています。この記事を読めば、簡単にたくさんのPDFファイルをWordファイルに変換することができます。

 

ボタン配置など、GUIの完成形は以下をイメージしてつくります。

GUI完成形イメージ

<主な構成物>
画面タイトル:PDF-Word変換ツール
テキストラベル:PDF置き場、Word出力先
テキスト入力欄:(ディレクトリパス)
ボタン:実行

 

・ソースコードと解説(Tkinter)

全文は以下のとおりです。

from pdf2docx.main import parse
import glob,os,re,time
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox

#----------------------------------------------------------------
# #PDFファイルからWordファイル変換の処理部分
#----------------------------------------------------------------
def pdf_word():
    #GUIのテキスト入力を代入
    in_path = in_path_box.get()
    out_path = out_path_box.get()
    
    #正常処理
    try:      
      in_file = glob.glob(in_path + "\*.pdf") #PDFファイルのパスごと取得
        
      #PDFファイル名のみを取得
      files = []
      pdflist = []
      pdf_search = re.compile(r'(.pdf)$')

      #指定フォルダ直下の全ファイルをリスト化
      for i in os.listdir(in_path):
        if os.path.isfile(os.path.join(in_path, i)):
          files.append(i)

      #PDFのみリスト化
      for j in files:
        if pdf_search.search(j):
          pdflist.append(j)
        
      filenum = len(pdflist) #PDFファイル数を取得
        
      #PDFをWORDに変換
      for i in range(filenum): 
        pdf_file = in_file[i] #PDFファイル
        docx_file = out_path + "\\" + pdflist[i].replace(".pdf", ".docx") #WORDファイル
        parse(pdf_file, docx_file)      
      
      #正常終了メッセージ  
      time.sleep(2)
      
      messagebox.showinfo("完了", "ファイルの変換が終了しました。")
      in_path_box.delete(0, tk.END)
      out_path_box.delete(0, tk.END)

    #異常終了
    except:
        messagebox.showerror('エラー', 'エラー発生が発生しました。処理を終了します。\r\n入力した値を確認してください。')
        in_path_box.delete(0, tk.END)
        out_path_box.delete(0, tk.END)


#----------------------------------------------------------------
# GUI設計
#----------------------------------------------------------------
#画面全体
main_win = tk.Tk()
main_win.title("PDF→Word変換ツール")
main_win.geometry("360x160")

#フレーム
frame1 = ttk.Frame(main_win)
frame1.grid(column=0, row=0, sticky=tk.NSEW, padx=5, pady=10)

#入力元
in_path_label = ttk.Label(frame1, text="PDF置き場")
in_path_box = ttk.Entry(frame1)
in_path_box.insert(tk.END, "")

#出力先
out_path_label = ttk.Label(frame1, text="Word出力先")
out_path_box = ttk.Entry(frame1)
out_path_box.insert(tk.END, "")

#実行ボタン
app_btn = ttk.Button(frame1, text="実行", width=12, command=pdf_word)

# ウィジェットの配置
in_path_label.grid(column=0, row=0, pady=10)
in_path_box.grid(column=1, row=0, sticky=tk.EW, padx=5)
out_path_label.grid(column=0, row=1, pady=10)
out_path_box.grid(column=1, row=1, sticky=tk.EW, padx=5)
app_btn.grid(column=1, row=3, padx=10)

# 伸縮設定
main_win.columnconfigure(0, weight=1)
main_win.rowconfigure(0, weight=1)
frame1.columnconfigure(1, weight=1)

main_win.mainloop() 

 

2から3行目でTkinterが使えるようにインポートしています。「ttk」はTkinterの拡張機能でウィジェットの外観がよりきれいに表示されます。「messagebox」はポップアップメッセージを表示させるのに必要です。

 

10行目から53行目は、pdfをwordに変換する処理部分です。

GUIで関係するところは、12行目と13行目で、GUIに入力されたファイルの入力元(in_path_box)と出力先(out_path_box)の値を取り込んでいます。

    in_path = in_path_box.get()
    out_path = out_path_box.get()

 

45行目から47行目では、正常に変換できたときにポップアップを表示し、

      messagebox.showinfo("完了", "ファイルの変換が終了しました。")
      in_path_box.delete(0, tk.END)
      out_path_box.delete(0, tk.END)

 

51行目から53行目で何かしらのエラーで変換に失敗した場合はエラーメッセージを表示するようにしています。

        messagebox.showerror('エラー', 'エラー発生が発生しました。処理を終了します。\r\n入力した値を確認してください。')
        in_path_box.delete(0, tk.END)
        out_path_box.delete(0, tk.END)

 

59行目から93行目がGUIの実装部分です。詳細な説明は長くなるため省きますが、ここで使われているウィジェット「Frame」、「Label」、「Entry」、「Button」について簡単に説明します。

65、66行目では、frame1変数にFrameという箱型のコンテナを設定しています。

レイアウトは行番号(row)、列番号(column)で指定する「grid」を指定しています。

(レイアウトは他に「pack」や「place」などがあります。掘り下げたい方は調べてみてください。)

frame1 = ttk.Frame(main_win)
frame1.grid(column=0, row=0, sticky=tk.NSEW, padx=5, pady=10)

 

Labelは、例えば69行目の

in_path_label = ttk.Label(frame1, text="PDF置き場")

は、”PDF置き場”というテキストデータと、「frame1に紐づくラベルだよ」と設定し、in_path_label変数に持たせています。

 

Entryはいわゆるテキストボックスで、例えば70行目ではframe1に紐づくよう設定し、71行目で初期値を空欄に設定しています。

in_path_box = ttk.Entry(frame1)
in_path_box.insert(tk.END, "")

 

Buttonも設定項目は他のウィジェットと同じですが、commandオプションでボタンを押したときに実行させたい関数を設定できます。79行目では、pdf_word関数を実行するように設定しています。

app_btn = ttk.Button(frame1, text="実行", width=12, command=pdf_word)

 

上記の要領でつくったウィジェットを、82行目から86行目で配置しています。

in_path_label.grid(column=0, row=0, pady=10)
in_path_box.grid(column=1, row=0, sticky=tk.EW, padx=5)
out_path_label.grid(column=0, row=1, pady=10)
out_path_box.grid(column=1, row=1, sticky=tk.EW, padx=5)
app_btn.grid(column=1, row=3, padx=10)

 

89行目から91行目では、画面の伸縮設定を行い、93行目で画面表示の処理を実行しています。

main_win.columnconfigure(0, weight=1)
main_win.rowconfigure(0, weight=1)
frame1.columnconfigure(1, weight=1)

main_win.mainloop() 

 

なんやかんやで完成したのがこちらです。

TKinterで作成したGUI

 

・ソースコードと解説(PySimpleGUI)

全文は以下のとおりです。

from pdf2docx.main import parse
import glob,os,re,time
import PySimpleGUI as sg

#----------------------------------------------------------------
# #PDFファイルからWordファイル変換の処理部分
#----------------------------------------------------------------
def pdf_word():
    #GUIのテキスト入力を代入
    in_path = values["pdf_place"]
    out_path = values["word_place"]
    
    #正常処理
    try:      
      in_file = glob.glob(in_path + "\*.pdf") #PDFファイルのパスごと取得
        
      #PDFファイル名のみを取得
      files = []
      pdflist = []
      pdf_search = re.compile(r'(.pdf)$')

      #指定フォルダ直下の全ファイルをリスト化
      for i in os.listdir(in_path):
        if os.path.isfile(os.path.join(in_path, i)):
          files.append(i)

      #PDFのみリスト化
      for j in files:
        if pdf_search.search(j):
          pdflist.append(j)
        
      filenum = len(pdflist) #PDFファイル数を取得
          
      #PDFをWORDに変換
      for i in range(filenum): 
        pdf_file = in_file[i] #PDFファイル
        docx_file = out_path + "\\" + pdflist[i].replace(".pdf", ".docx") #WORDファイル
        parse(pdf_file, docx_file)      
      
      #正常終了メッセージ  
      time.sleep(2)
      
      sg.popup("ファイルの変換が終了しました。", title="完了" )
      window["pdf_place"].update("")
      window["word_place"].update("")

    #異常終了
    except:
        sg.PopupError('エラー発生が発生しました。処理を終了します。\r\n入力した値を確認してください。',
                      title='エラー', background_color='#f00' )
        window["pdf_place"].update("")
        window["word_place"].update("")


#----------------------------------------------------------------
# GUI設計
#----------------------------------------------------------------
#デザインテーマの選択
sg.theme("DarkBlue")

#縦方向に配置を整列
col1 = [[sg.Text("PDF置き場", size=(10,1), pad=((0,0),(10)))],
        [sg.Text("Word出力先", size=(10,1), pad=((0,0),(10)))]]

col2 = [[sg.InputText(size=(30,1), pad=((5),(10)), key="pdf_place")],
        [sg.InputText(size=(30,1), pad=((5),(10)), key="word_place")]]

#画面レイアウト
layout = [[sg.Column(col1), sg.Column(col2)],          
          [sg.Button("実行", size=(10,1), pad=((130),(10)), key="-change_exe-")]]

#画面全体
window = sg.Window("PDF-Word変換ツール", layout, size=(360,160))  #resizable=False

#画面表示のループ処理
while True:
  event, values = window.read()
  
  print("イベント:",event , ', 値', values)
  if event == sg.WIN_CLOSED :
    break
  
  if event == "-change_exe-":
    pdf_word()    
    
window.close()

 

3行目で、GUI実装に必要なPySimpleGUIをインポートしています。

8行目から52行目が、pdfをwordに変換する処理部分になるのですが、PySimpleGUIだと値の取り込みがこんな感じに。(10、11行目)

    in_path = values["pdf_place"]
    out_path = values["word_place"]

 

変換完了のポップアップとエラーメッセージはこんな感じになります。(43行目から45行目と49行目から52行目)

      sg.popup("ファイルの変換が終了しました。", title="完了" )
      window["pdf_place"].update("")
      window["word_place"].update("")
        sg.PopupError('エラー発生が発生しました。処理を終了します。\r\n入力した値を確認してください。',
                      title='エラー', background_color='#f00' )
        window["pdf_place"].update("")
        window["word_place"].update("")

 

59行目から86行目がGUIの実装部分です。

PySimpleGUIの記述のポイントとして、まずテーマを選択することでデザインが変更できます。

59行目が該当します。

sg.theme("DarkBlue")

 

もうひとつのポイントとしては、感覚的なレイアウトがあります。

リスト内でウイジェットを記述すると、横方向に配置され、リストごとに縦に配置されます。

69、70行目が該当します。

layout = [[sg.Column(col1), sg.Column(col2)],          
          [sg.Button("実行", size=(10,1), pad=((130),(10)), key="-change_exe-")]]

 

画面全体のサイズ指定などは73行目のように記述します。

window = sg.Window("PDF-Word変換ツール", layout, size=(360,160))  #resizable=False

 

画面表示の処理は76行目から86行目のように記述します。

while文を使ってループ処理をしていて、今回の場合だと80行目が画面の×ボタンを押したときの処理。83、84行目が実行ボタンを押したときの処理です。

“-change_exe-“イベントが発生したときにpdf_word関数を実行するように記述しています。

while True:
  event, values = window.read()
  
  print("イベント:",event , ', 値', values)
  if event == sg.WIN_CLOSED :
    break
  
  if event == "-change_exe-":
    pdf_word()    
    
window.close()

 

そうして完成したのがこちらです。

PySimpleGUIで作成したGUI

 

以上、「Tkinter」と「PySimpleGUI」によるGUI実装の解説でした。

ウィジェットの使用数が少ないうちは、Tkinterで十分そうですが、ウィジェットの使用数が多くなりそうならPySimpleGUIのほうがよいかもしれません。

実際、今回のソースコードを比較してみても、大きな違いはないもののPySimpleGUIのほうが10パーセント程度、コード量が少ない結果となりました。

 

3.実行ファイルの作成方法

GUIの実装が終わり、アプリケーションが完成しました。あとは配布するために実行ファイルにするだけです。

そのために、ここでは実行ファイルを作成するための「PyInstaller」と「cx_Freeze」という2つのライブラリを紹介します。それぞれの特徴とやり方を説明しますので、実際にやってみてください。

 

・PyInstaller

PyInstallerは、Pythonのソースファイルを実行ファイル(exeファイル)にするためのライブラリです。次のような特徴があります。

  • コマンド実行で簡単に実行ファイルを作成できる
  • 配布するファイルを実行ファイルだけにすることができる
  • 実行ファイルのファイルサイズが大きくなりがち(故に起動が遅い)
  • 実行ファイルの利用は、実行ファイルを作成したOSに依存する
    (例えば、Windowsで作成した場合、Macで使えない)

 

PyInstallerを使う一番のメリットは、簡単に作成できて実行ファイルをひとつにまとめることができるところでしょう。使う側としても、渡された実行ファイルを自分のパソコンでダブルクリックすればいいので、わかりやすいです。

難点は、作ってる側の開発環境に依存してファイルサイズが肥大化し、実行速度が遅くなることがある点です。肥大化する理由は、実行ファイル作成時に、使用の有無にかかわらず、作り手の開発環境にインストールされているライブラリやパッケージが、すべて組み込まれるためのようです。

軽減する方法は、いくつかあるようですが今回はオプションを使って軽減する方法を一緒にお伝えしたいと思います。

・手順

  1. PyInstallerをインストールしておく。
    pip install pyinstaller
  2. 適当なフォルダを作成し、対象のソースファイルを置く。
  3. コマンドプロンプトを起動する。
  4. コマンドプロンプトで手順2のフォルダに移動する。
  5. 次のようなコマンドを入力し、実行する。
    例)

    pyinstaller (対象のソースファイル).py --onefile --clean --noconsole --exculude pandas

 

手順5の「対象のソースファイル」から右は、Pyinstallerのコマンドオプションです。

詳細を簡単に説明しておきます。よく使うので覚えておくと良いでしょう。

オプション説明
--onefile生成されるファイルを実行ファイルひとつにまとめる。
--noconsole実行ファイルが実行されるときにコンソールが表示されないようにする。
--cleanビルド前にキャッシュや出力ディレクトを削除。
--exculude指定したライブラリを実行ファイルから除外。

上記のように–exculudeでpandasなど容量が大きいライブラリを除外することで、実行ファイルが軽量化する場合があります。(今回、pandasを除外しましたが、違いがありませんでした…)

処理が完了すると、手順2で作成したフォルダ内に次のようなファイルが生成されます。

Pyinstallerで生成されるファイル群

その「dist」フォルダに実行ファイルが格納されています。

実行ファイルが問題なく起動すれば、実行ファイルの作成は成功です。

 

・cx_Freeze

同様に、cx_Freezeも実行ファイルにするためのライブラリです。次のような特徴があります。

  • 作成した実行ファイルは、Windows、Mac、Linuxそれぞれで使える。
    (クロスプラットフォーム対応)
  • 実行ファイルのファイルサイズが小さい(故に起動が早い)
  • ただ、付属のファイル群と一緒じゃないと実行できない
  • 実行ファイル作成用のソースファイルが必要

 

cx_Freezeのメリットに、クロスプラットフォーム対応であることが挙げられます。PyInstallerは作り手のOSに依存するので、cx_Freezeは本当の意味で使う側の環境を配慮しないで済みます。また、実行ファイルは付属のファイル群と一式じゃないと使えないものの、起動は早くストレスフリーなところもよいです。

ただ、実行ファイルを作成するためには、実行ファイル作成用のソースファイルが必要です。その点を踏まえ、手順を説明します。

・手順

  1. cx_Freezeをインストールしておく。
    pip install cx_Freeze
  2. 実行ファイル作成用のソースファイルを作成する。
    例)setup.py

    import sys
    from cx_Freeze import setup, Executable
     
    base = None
     
    if sys.platform == 'win32': base = 'Win32GUI'
    
    # exeにするソースファイルを指定
    exe = Executable(script = "pdf-word.py", base= base)
     
    setup(name = 'pdf-word', #ファイルの名前
        version = '0.1',     #バージョン表記
        description = 'pdfからwordに変換するツール', #アプリケーションの説明
        executables = [exe])  #実行ファイルの形式
    
  3. 適当なフォルダを作成し、対象のソースファイルと手順2のソースファイルを置く。
  4. コマンドプロンプトを起動する。
  5. コマンドプロンプトで手順3のフォルダに移動する。
  6. 次のようなコマンドを入力し、実行する。
    例)

    python setup.py build

 

処理が完了すると、手順3で作成したフォルダ内に次のようなファイルが生成されます。

cx_freezeで生成されるファイル群

コマンドで入力したフォルダ(今回の場合は「build」)に実行ファイルが格納されています。

実行ファイルが問題なく起動すれば、実行ファイルの作成は成功です。

 

4.まとめ

以上、Pythonでデスクトップアプリを作る方法をお伝えしました。

ちょっと長くなったので、おさらいすると

  • デスクトップアプリを作るにはGUIの実装と実行ファイルの作成が必要
  • GUI実装には、手軽にできる「Tkinter」と「PySimpleGUI」がおすすめ
  • 実行ファイルを、ひとつのファイルにするなら「PyInstaller」
  • 実行ファイルが、環境を選ばず使え、起動が速いのは「cx_Freeze」

というお話でした。

Pythonは似た用途の様々なライブラリがあるので迷ってしましますが、手始めに今回紹介したライブラリを使って、この記事を参考にしつつデスクトップアプリを作ってみてください。

ご清聴ありがとうございました。