Pythonの処理部分のコードだけをあげるよりも、画面(GUI)のコードも一緒に付けてあげれば、もらった相手は使い勝手が良くなりなります。特に入出力ファイルをエクスプローラーのような画面で選択できるとかなり便利になります。そこで、今回は簡単な題材を用いて画面(GUI)の作成方法を説明します。イメージとしては、エクセルVBAのユーザーフォームに相当する画面です。

幸いPythonには、標準ライブラリにTkinterというGUIを作成するためのツールキットが組み込まれています。基本的なツールが一通り揃っているので、すぐにプログラミングできます。

Tkinterは最初は分かりにくく感じるかもしれませんが、今回説明するプログラムをベースにカスタマイズすれば、比較的簡単に自分のプログラムに取り込むできますので、ぜひ活用してください。

本記事の目次

作成するプログラムの構成

今回は、以前の記事で紹介したフォルダ内のPDFファイルを1つに結合するプログラム「merge_pdf.py」を操作するための画面(GUI)を作成します。

pdf_files/
  │  ├ 201901hyoshi.pdf
  │  ├ 201902hyoshi.pdf
  │  ├ 201903hyoshi.pdf
  │  └ 201903nankai_gaikyo.pdf
  │
  ├ merge_pdf.py  <- 本体のプログラム
  └ merge_pdf_app.py <- 画面(GUI)のプログラム

画面(GUI)のプログラムは「merge_pdf_app.py」という名前で作成します。このように、本体のプログラム名の末尾に_app付けるなど、命名規則を決めておくと見分けやすくなります。

今回作成するプログラムはサンプルファイルを含めて以下からダウンロードできます。

ソースコードとサンプルPDFファイル(tk_sample.zip)

本体のプログラム

PDFファイルを1つにまとめる本体のプログラム(merge_pdf.py)は、今回の画面プログラムからモジュールとして利用できるように、以下のようにメインの処理を関数しておきます。

# merge_pdf.py
from pathlib import Path
import PyPDF2

def merge_pdf_files(folder_path, file_path, is_reverse):
    # フォルダ内のPDFファイル一覧
    pdf_dir = Path(folder_path)
    pdf_files = sorted(pdf_dir.glob("*.pdf"), reverse=is_reverse)

    # 1つのPDFファイルにまとめる
    pdf_writer = PyPDF2.PdfFileWriter()
    for pdf_file in pdf_files:
        pdf_reader = PyPDF2.PdfFileReader(str(pdf_file))
        for i in range(pdf_reader.getNumPages()):
            pdf_writer.addPage(pdf_reader.getPage(i))

    # 保存
    with open(file_path, "wb") as f:
        pdf_writer.write(f)


if __name__ == "__main__":
    # テスト
    merge_pdf_files("./pdf_files", "test.pdf", False)

このプログラムの詳細については以下のページを参照してください。

no image
今回はフォルダ内にある複数のPDFファイルを1つのPDFファイルに結合するプログラムをPythonで作成します。 通常このような作業は、有料のAcrobat®などを用いますが、毎回ソフト…

画面遷移の設計

最初にどのような順序で画面を操作するかを考えておきます。

今回のプログラムでは、「結合するPDFがあるフォルダ」、「並び順」、「保存するPDFファイル名」を以下の画面操作で指定できるようにします。

(1) merge_pdf_app.pdfを実行すると以下の画面が表示されます。結合するPDFファイルが入っているフォルダを指定するために参照ボタンをクリックします。

GUI Design

(2) 以下のような「フォルダの選択ダイアログ」が表示されるので、フォルダを選択しフォルダの選択ボタンをクリックします。

Ask Folder

(3) すると以下のように、フォルダ指定のテキストボックスにフォルダのパスが入力されます。PDFを結合する並び順を選択して実行ボタンをクリックします。

GUI input

(4) 以下のような「ファイル保存のダイアログ」が表示されるので、結合したPDFファイルを保存するファイル名を指定して保存ボタンをクリックします。

Ask File name

(5) 結合処理が完了すると、以下のような「完了ダイアログ」が表示されるので、OKボタンをクリックします。

finish dialog

画面パーツの作成と配置

画面(GUI)プログラムを完成させるには、各ボタンの動作を定義する必要がありますが、まず画面を構成するパーツの作成方法を先に説明します。

画面は以下のように、「メインウィンドウ」、「メインフレーム」、「ウィジェット」の3種類のパーツで構成されます。

GUI Design Frame

メインウィンドウの作成にはtkinterモジュール、メインフレームウィジェットの作成にはtkinter.ttkモジュールを用います。

公式ドキュメント

tkinterモジュール
tkinter.ttkモジュール

各パーツを作成し配置するコードを以下のようになります。まだ、ボタンは機能しませんが、実行すれば画面を表示できます。

# merge_pdf_app.py (途中:画面パーツの配置のみ)
import tkinter
from tkinter import ttk

# メインウィンドウ
main_win = tkinter.Tk()
main_win.title("PDFを結合する")
main_win.geometry("500x120")

# メインフレーム
main_frm = ttk.Frame(main_win)
main_frm.grid(column=0, row=0, sticky=tkinter.NSEW, padx=5, pady=10)

# ウィジェット作成(フォルダパス)
folder_label = ttk.Label(main_frm, text="フォルダ指定")
folder_box = ttk.Entry(main_frm)
folder_btn = ttk.Button(main_frm, text="参照")

# ウィジェット作成(並び順)
order_label = ttk.Label(main_frm, text="並び順")
order_comb = ttk.Combobox(main_frm, values=["昇順", "降順"], width=10)
order_comb.current(0)

# ウィジェット作成(実行ボタン)
app_btn = ttk.Button(main_frm, text="実行")

# ウィジェットの配置
folder_label.grid(column=0, row=0, pady=10)
folder_box.grid(column=1, row=0, sticky=tkinter.EW, padx=5)
folder_btn.grid(column=2, row=0)
order_label.grid(column=0, row=1)
order_comb.grid(column=1, row=1, sticky=tkinter.W, padx=5)
app_btn.grid(column=1, row=2)

# 配置設定
main_win.columnconfigure(0, weight=1)
main_win.rowconfigure(0, weight=1)
main_frm.columnconfigure(1, weight=1)

main_win.mainloop()

# メインウィンドウの作成

メインウィンドウはtkinter.Tk()で作成し、タイトルtitle()サイズgeometry()の各メソッドで指定します。今回のように"500x120"を指定すると、「幅500px、高さ120px」のウィンドウが作成されます。

# メインフレームとウィンドウの作成

メインフレームとウィジェットは、tkinter.ttkからFrame()Button()を呼び出して作成します。この時、かっこ内の引数には親に相当するパーツを指定します。つまり、メインフレームフレームならばメインウィンドウ、ウィジェットならばメインフレームを指定します。

ラベルLabel)やボタンButton)では、text=で表示するテキストを指定できます。

コンボボックスCombobox)では、項目はvalues=リストで指定します。初期値はcurrent()メソッドで、項目のインデックス番号で指定します。

# メインフレームとウィンドウの配置

メインフレームとウィジェットの配置は、grid()メソッドで行います。以下のように、左上を起点に、0番から列(column)と行(row)で位置を指定します。

ttk grid system

位置揃えstickyを用いて「N=上、S=下、E=右、W=左」で指定します。

例えば、コンボボックス(order_comb)は、sticky=tkinter.Wで左に寄せています。また、テキストボックス(folder_box)のように、sticky=tkinter.EWと左右両方を指定すると水平方向に引き延ばすことができます。

余白padxpadyで指定します。padxは左右、padyは上下の余白になります。

その他のレイアウト方法

Tkinterでウィジェットをレイアウトするには、packgridplaceの3つのメソッドを利用できます(これらはGeometry managersと呼ばれます)。左上から順に詰めて行くpackもよく用いられますが、多少複雑な配置になるとgridの方がレイアウトしやすいです。そのため、本記事ではgridの方を採用しています。

# 配置設定(引き伸ばし設定)

メインフレームがメインウィンドウの内側にビッタリと張り付くように、メインウィンドウのcolumnconfigure(0, weight=1)main_win.rowconfigure(0, weight=1)を設定します。

また、フォルダ指定のテキストバックスが水平方向に引き伸ばされるように、メインフレームのcolumnconfigure(1, weight=1)を設定して真ん中の列(column=1)が延びるようにします。

ボタン動作定義の追加

上記で途中まで作成したmerge_pdf_app.pyにボタン動作を加えてプログラムを完成させます。

ボタン動作は、フォルダの参照ボタンfolder_btn)と実行ボタンapp_btn)をクリックした時の動作を定義します。

ボタンをクリックした時の動作は関数で定義し、command=でその関数名を指定します。ここで、関数名を指定する時にかっこは不要です(app()ならばappと指定)。

# merge_pdf_app.py(ボタン動作を追加した最終版)
import tkinter
from tkinter import ttk
# インポートは以下の3行を追加
from tkinter import filedialog
from tkinter import messagebox
import merge_pdf


def ask_folder():
    """ 参照ボタンの動作
    """
    path = filedialog.askdirectory()
    folder_path.set(path)


def app():
    """ 実行ボタンの動作
    """
    is_reverse = order_comb.get() == "降順"
    input_dir = folder_path.get()
    # 保存するPDFファイルを指定
    output_file = filedialog.asksaveasfilename(
        filetypes=[("PDF files", "*.pdf")], defaultextension=".pdf"
    )
    if not input_dir or not output_file:
        return
    # 結合実行
    merge_pdf.merge_pdf_files(input_dir, output_file, is_reverse)
    # メッセージボックス
    messagebox.showinfo("完了", "完了しました。")


# メインウィンドウ
main_win = tkinter.Tk()
main_win.title("PDFを結合する")
main_win.geometry("500x120")

# メインフレーム
main_frm = ttk.Frame(main_win)
main_frm.grid(column=0, row=0, sticky=tkinter.NSEW, padx=5, pady=10)

# パラメータ
folder_path = tkinter.StringVar()

# ウィジェット(フォルダ名)
folder_label = ttk.Label(main_frm, text="フォルダ指定")
folder_box = ttk.Entry(main_frm, textvariable=folder_path)
folder_btn = ttk.Button(main_frm, text="参照", command=ask_folder)

# ウィジェット(並び順)
order_label = ttk.Label(main_frm, text="並び順")
order_comb = ttk.Combobox(main_frm, values=["昇順", "降順"], width=10)
order_comb.current(0)

# ウィジェット(実行ボタン)
app_btn = ttk.Button(main_frm, text="実行", command=app)

# ウィジェットの配置
folder_label.grid(column=0, row=0, pady=10)
folder_box.grid(column=1, row=0, sticky=tkinter.EW, padx=5)
folder_btn.grid(column=2, row=0)
order_label.grid(column=0, row=1)
order_comb.grid(column=1, row=1, sticky=tkinter.W, padx=5)
app_btn.grid(column=1, row=2)

# 配置設定
main_win.columnconfigure(0, weight=1)
main_win.rowconfigure(0, weight=1)
main_frm.columnconfigure(1, weight=1)

main_win.mainloop()

# フォルダ指定ダイアログの表示

フォルダ指定のダイアログを開くには、tkinter.filedialogモジュールからaskdirectory()を呼び出します。ダイアログでフォルダを指定するとパスを文字列で返します。

# 設定した値をウィジェットに反映させる方法

フォルダダイアログで選択したフォルダのパスを、テキストボックス(folder_box)に反映させるために、textvariable=tkinter.StringVar()の変数(folder_path)を設定しておきます。

これにより、folder_pathの値をset()メソッドで書き換えれば、テキストボックスのテキストにすぐに反映されます。また、get()メソッドで値を取得することもできます。

# ファイル保存ダイアログの表示

ファイル保存のダイアログを開くには、tkinter.filedialogモジュールからasksaveasfilenameを呼び出します。ダイアログでファイル名を指定するとパスを文字列で返します。ファイルの種類はfiletypes=、規定の拡張子はdefaultextension=で指定します。

なお、ファイルを開く時はaskopenfilename()を呼び出します。

# メッセージボックスの表示

ユーザーにメッセージを表示するには、tkinter.messageboxモジュールからshowinfo()を呼び出します。1つ目の引数がダイアログのタイトル、2つめがメッセージになります。

最後に

Tkinterによる画面(GUI)作成は少し分かりづらいところもありますが、仕組みは比較的単純です。良く使うパターンでひな形を作っておけば、呼び出し本体のプログラムを差し替えるだけで、簡単に画面を付け加えることができます。

Tkinterは標準ライブラリなので、インストール不要ですぐ使えます。ぜひ本記事を参考に有効活用してください。