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)
このプログラムの詳細については以下のページを参照してください。
画面遷移の設計
最初にどのような順序で画面を操作するかを考えておきます。
今回のプログラムでは、「結合するPDFがあるフォルダ」、「並び順」、「保存するPDFファイル名」を以下の画面操作で指定できるようにします。
(1) merge_pdf_app.pdf
を実行すると以下の画面が表示されます。結合するPDFファイルが入っているフォルダを指定するために参照ボタンをクリックします。
(2) 以下のような「フォルダの選択ダイアログ」が表示されるので、フォルダを選択しフォルダの選択ボタンをクリックします。
(3) すると以下のように、フォルダ指定のテキストボックスにフォルダのパスが入力されます。PDFを結合する並び順を選択して実行ボタンをクリックします。
(4) 以下のような「ファイル保存のダイアログ」が表示されるので、結合したPDFファイルを保存するファイル名を指定して保存ボタンをクリックします。
(5) 結合処理が完了すると、以下のような「完了ダイアログ」が表示されるので、OKボタンをクリックします。
画面パーツの作成と配置
画面(GUI)プログラムを完成させるには、各ボタンの動作を定義する必要がありますが、まず画面を構成するパーツの作成方法を先に説明します。
画面は以下のように、「メインウィンドウ」、「メインフレーム」、「ウィジェット」の3種類のパーツで構成されます。
メインウィンドウの作成には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
)で位置を指定します。
位置揃えはsticky
を用いて「N=上、S=下、E=右、W=左」で指定します。
例えば、コンボボックス(order_comb
)は、sticky=tkinter.W
で左に寄せています。また、テキストボックス(folder_box
)のように、sticky=tkinter.EW
と左右両方を指定すると水平方向に引き延ばすことができます。
余白はpadx
とpady
で指定します。padx
は左右、pady
は上下の余白になります。
その他のレイアウト方法
Tkinterでウィジェットをレイアウトするには、pack、grid、placeの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は標準ライブラリなので、インストール不要ですぐ使えます。ぜひ本記事を参考に有効活用してください。