Python業務自動化 Invoice Maker 第3回
はじめに:CustomTkinterでPythonスクリプトをGUIアプリ化する
こんにちは、VBA・Python業務自動化ラボ(py-vbalab.com)のニンジンです🥕
この記事では、第1回・第2回で作った「Excel請求書を自動でPDF化するPythonスクリプト」を、CustomTkinterを使って、ターミナルなしでマウス操作できるデスクトップGUIアプリに作り替えます。filedialogでのファイル選択、CTkEntryの .get() で開始行・終了行を受け取る方法、class を使ったアプリ設計まで、実際に動くコードで解説します。
これまでの連載で、Excelからのデータ読み取り・PDF化・複数データのループ処理という「裏側のロジック」は完成しました。ただ、現場で事務スタッフに「Pythonのコードを開いて for i in range(15, 50): に書き換えてください」とお願いするのは非現実的です。コードを誤って壊すリスクもあります。そこで最終回は、誰でも直感的に操作できる操作画面(GUI)を作り、社内の誰もが使えるツールに仕上げます。
この記事で分かること
- PythonスクリプトをCustomTkinterでデスクトップGUIアプリに作り替える手順
filedialog.askopenfilename()でExcelファイルを選ばせる方法(パス直打ちのミス防止)CTkEntryの.get()で画面入力(開始行・終了行)をコードに渡す方法class InvoiceApp(ctk.CTk)というクラスを使った本格的なアプリ設計- 完成後に立ちはだかる「他人のPCで動かない」問題と、その対処の方向性
「コードを書く時間がない」「社内のPC全部にPythonを入れるのは無理」という方向けに、本記事で作るツールの完全版(環境構築不要のフルパッケージ版)【Invoice Maker (NINJIN EDITION)】をnoteで頒布しています。
👉 noteでチェックするCustomTkinterによるGUI開発の事前準備
CTk CustomTkinter · 基礎編 CustomTkinterとは?インストールから基本の使い方まで この記事はCustomTkinterでツールをGUI化する実践編です。pipでの導入、CTkLabel・CTkButton・CTkEntry・CTkFrameの配置、pack/grid配置の違いなど、基礎からじっくり知りたい方は、まずこちらの入門記事からどうぞ。 基礎編を読む →準備1:CustomTkinterのインストール
今回は、Python標準のGUIライブラリ「Tkinter」を、フラットデザイン・ダークモード対応に拡張した CustomTkinter を使います。コマンドプロンプト(またはターミナル)で以下を実行します。
pip install customtkinter
準備2:テストに必要なサンプルExcelファイル(受注管理表・請求書雛形)
お手持ちの管理表と書類フォーマットを使うか、下記のサンプルをダウンロードしてください(第1回・第2回と同じファイルです)。
⬇ 受注管理表_Sample.xlsx ⬇ 請求書_Sample.xlsx
準備3:Pythonコードを動かす環境の準備
同一フォルダ内に、次の3ファイルを置きます。
- テストコード(本記事のコード)
- 受注管理表(Excel)
- 請求書雛形(Excel)
次のセクションのPythonコードをVS Codeなどのエディターにコピー&ペーストして実行してください。
GUI版の完成コード:CustomTkinterで請求書自動作成ツールを作る
こちらが完成形のコードです。第2回で作ったPDF化ロジックを、ボタンを押したときに呼ばれる run_process という関数(メソッド)に閉じ込め、画面の入力欄と連動させています。1行ずつコメントを付けてあります。
⬇ テストコード一式をダウンロード(Invoice_Maker_test03.zip)
# invoice_maker_test03.py
# invoice_maker_test02.pyをベースに、GUIで操作できるようにcustomtkinterを使用して拡張したコードです。
# GUIでファイル選択やシート名、行範囲、列・セルの紐付けを入力できるようにしています。
import openpyxl
import win32com.client
import customtkinter as ctk
from tkinter import filedialog
import os
#GUI版のコードは別ファイルで作成します。
class InvoiceApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("NINJIN Invoice Maker TEST")
self.geometry("600x650")
# --- ファイル選択セクション ---
self.label_file = ctk.CTkLabel(self, text="【1】ファイルの選択", font=("BIZ UDゴシック", 16, "bold"))
self.label_file.pack(pady=5)
self.kanri_path = ctk.StringVar(value="未選択")
ctk.CTkButton(self, text="受注管理表を選択", command=self.select_kanri).pack(pady=5)
ctk.CTkLabel(self, textvariable=self.kanri_path).pack()
self.hina_path = ctk.StringVar(value="未選択")
ctk.CTkButton(self, text="請求書雛形を選択", command=self.select_hina).pack(pady=5)
ctk.CTkLabel(self, textvariable=self.hina_path).pack()
# --- 範囲・シート設定セクション ---
self.label_setting = ctk.CTkLabel(self, text="【2】読み込み設定", font=("BIZ UDゴシック", 16, "bold"))
self.label_setting.pack(pady=5)
# シート名と行範囲(横に並べる)
self.frame_labels = ctk.CTkFrame(self)
self.frame_labels.pack(pady=(10, 0)) # 上に余白
# 表題は self.frame_labels の中に入れる
self.lbl_kanri = ctk.CTkLabel(self.frame_labels, text="管理表シート", width=120)
self.lbl_kanri.pack(side="left", padx=5)
self.lbl_hina = ctk.CTkLabel(self.frame_labels, text="請求書シート", width=120)
self.lbl_hina.pack(side="left", padx=5)
self.lbl_start = ctk.CTkLabel(self.frame_labels, text="開始行", width=60)
self.lbl_start.pack(side="left", padx=5)
self.lbl_end = ctk.CTkLabel(self.frame_labels, text="終了行", width=60)
self.lbl_end.pack(side="left", padx=5)
# --- 【2】入力欄(Entry)用のフレーム ---
self.frame_entries = ctk.CTkFrame(self)
self.frame_entries.pack(pady=(0, 10)) # 下に余白
# 入力欄は self.frame_entries の中に入れる
self.entry_kanri_sheet = ctk.CTkEntry(self.frame_entries, width=120)
self.entry_kanri_sheet.insert(0, "Sheet1")
self.entry_kanri_sheet.pack(side="left", padx=5)
self.entry_hina_sheet = ctk.CTkEntry(self.frame_entries, width=120)
self.entry_hina_sheet.insert(0, "Sheet1")
self.entry_hina_sheet.pack(side="left", padx=5)
self.entry_start = ctk.CTkEntry(self.frame_entries, width=60)
self.entry_start.insert(0, "4")
self.entry_start.pack(side="left", padx=5)
self.entry_end = ctk.CTkEntry(self.frame_entries, width=60)
self.entry_end.insert(0, "9")
self.entry_end.pack(side="left", padx=5)
# --- 受注Noの列を入力するセクション ---
self.label_mapping = ctk.CTkLabel(self, text="【3】受注Noの列を入力(作成されるPDFのファイル名に使用)", font=("BIZ UDゴシック", 16, "bold"))
self.label_mapping.pack(pady=10)
self.frame_order = ctk.CTkFrame(self)
self.frame_order.pack( padx=20, fill="x")
self.order_entry = ctk.CTkEntry(self.frame_order, width=60)
self.order_entry.pack(padx=5)
self.order_entry.insert(0, "A") # デフォルトはA列
# --- 列・セル指定セクション ---
self.label_mapping = ctk.CTkLabel(self, text="【4】列・セルの紐付け_左側:管理表の列/右側:請求書雛形のセル", font=("BIZ UDゴシック", 16, "bold"))
self.label_mapping.pack(pady=10)
# グリッド状に配置
self.frame_map = ctk.CTkFrame(self)
self.frame_map.pack(pady=5, padx=20, fill="x")
# 設定項目(項目名, 管理表の列, 雛形のセル)
# 「self.変数名」で受けることで、クラス全体の「持ち物」になる
self.e_col_company, self.e_cell_company = self.create_map_row(self.frame_map, "会社名", "C", "A5", 0)
self.e_col_product, self.e_cell_product = self.create_map_row(self.frame_map, "商品名", "F", "B18", 1)
self.e_col_count, self.e_cell_count = self.create_map_row(self.frame_map, "数量", "G", "J18", 2)
self.e_col_amount, self.e_cell_amount = self.create_map_row(self.frame_map, "単価", "H", "L18", 3)
# --- 実行ボタン ---
self.btn_run = ctk.CTkButton(self, text="PDF作成開始", fg_color="green", hover_color="darkgreen", command=self.run_process)
self.btn_run.pack(pady=30)
def create_map_row(self, master, label_text, col_val, cell_val, row):
ctk.CTkLabel(master, text=label_text).grid(row=row, column=0, padx=10, pady=5)
# 管理表の列用
e_col = ctk.CTkEntry(master, width=50)
e_col.insert(0, col_val)
e_col.grid(row=row, column=1, padx=5, pady=5)
# 雛形のセル用
e_cell = ctk.CTkEntry(master, width=80)
e_cell.insert(0, cell_val)
e_cell.grid(row=row, column=2, padx=5, pady=5)
# 【重要】作った2つのEntryをセットにして外に返す(return)
return e_col, e_cell
def select_kanri(self):
path = filedialog.askopenfilename(filetypes=[("Excel files", "*.xlsx")])
if path: self.kanri_path.set(path)
def select_hina(self):
path = filedialog.askopenfilename(filetypes=[("Excel files", "*.xlsx")])
if path: self.hina_path.set(path)
def run_process(self):
# ここに以前作成したPDF作成ロジックを入れ込む
# 1. 設定
KANRI_PATH = self.kanri_path.get() # GUIで選択された受注管理表のパスを取得
HINAGATA_PATH = self.hina_path.get() # GUIで選択された請求書の雛形となるExcelファイルのパスを取得
kanri_sheet_name = self.entry_kanri_sheet.get() # GUIから受注管理表のシート名を取得
hina_sheet_name = self.entry_hina_sheet.get() # GUIから請求書雛形のシート名を取得
start_row = int(self.entry_start.get()) # GUIから開始行を取得
end_row = int(self.entry_end.get()) # GUIから終了行を取得
col_order=self.order_entry.get() # 受注番号の列を取得
col_company=self.e_col_company.get() # 会社名の列を取得
cell_company=self.e_cell_company.get() # 会社名のセルを取得
col_product=self.e_col_product.get() # 商品名の列を取得
cell_product=self.e_cell_product.get() # 商品名のセルを取得
col_count=self.e_col_count.get() # 数量の列を取得
cell_count=self.e_cell_count.get() # 数量のセルを取得
col_amount=self.e_col_amount.get() # 単価の列を取得
cell_amount=self.e_cell_amount.get() # 単価のセルを取得
# --- 追加:ファイルが選択されているかチェック ---
if KANRI_PATH == "未選択" or HINAGATA_PATH == "未選択":
print("エラー:ファイルを選択してください")
return # ここで関数を終了させる
# ----------------------------------------------
# Excelアプリケーションの起動(ループの外で行うと高速です)
excel = win32com.client.Dispatch("Excel.Application") # DispatchでExcelアプリケーションを起動
excel.Visible = False # Excelのウィンドウを表示しない
excel.DisplayAlerts = False # 警告ダイアログを表示しない
excel.ScreenUpdating = False # 画面更新を停止(より徹底する場合)
try: # エラー発生時もExcelを確実に終了させるためにtryブロックを使用
wb_kanri = openpyxl.load_workbook(KANRI_PATH, data_only=True) # KANRI_PATHを開く data_only=Trueで数式の結果を値で取得
ws_kanri = wb_kanri[kanri_sheet_name] # GUIから受注管理表のシート名を取得してシートを指定
# 2. for文でstart_row行目からend_row行目まで繰り返す
for i in range(start_row, end_row + 1):
try: # for文の中でエラーが起きても次の行に進むように、ここでもtryブロックを使用
# 行番号(i)を使って各データを取得
order_id = ws_kanri[f"{col_order}{i}"].value # 受注番号 .valueでセルの値を取得
company_name = ws_kanri[f"{col_company}{i}"].value # 会社名
s_name = ws_kanri[f"{col_product}{i}"].value # 商品名
count = ws_kanri[f"{col_count}{i}"].value # 数量
amount = ws_kanri[f"{col_amount}{i}"].value # 単価
print(f"--- 処理中({i}行目): {company_name} 様 ---")
# 3. 雛形へ書き込み
wb_hina = openpyxl.load_workbook(HINAGATA_PATH) #HINAGATA_PATHを開く
ws_hina = wb_hina[hina_sheet_name] # シート名を指定してシートを取得
ws_hina[f"{cell_company}"] = company_name #HINAGATA_PATHの指定されたセルに会社名を書き込み
ws_hina[f"{cell_product}"] = s_name #HINAGATA_PATHの指定されたセルに商品名を書き込み
ws_hina[f"{cell_amount}"] = amount #HINAGATA_PATHの指定されたセルに金額を書き込み
ws_hina[f"{cell_count}"] = count #HINAGATA_PATHの指定されたセルに数量を書き込み
# 一時ファイルのパス(会社名を含めて重複を避ける)
temp_excel = os.path.abspath(f"temp_{order_id}.xlsx") #abspathで絶対パスを取得 order_idを使って一時ファイルの名前を個別化する
wb_hina.save(temp_excel) # 保存してからPDF化する必要があるため、一時的なExcelファイルを作成
# 4. PDF化処理
output_pdf = os.path.abspath(f"請求書_{order_id}_{company_name}.pdf") # 出力するPDFのパスを作成(会社名と受注番号を含めて重複を避ける)
doc = excel.Workbooks.Open(temp_excel) # Openで先ほど保存した一時的なExcelファイル(temp.xlsx)を開く
doc.ExportAsFixedFormat(0, output_pdf) # ExportAsFixedFormatでPDF形式で保存。第1引数は0でPDF、第2引数は保存先のパス
doc.Close(False) # Closeでtemp.xlsxを閉じる。引数は変更を保存するかどうか(Falseで保存しない)
# 一時ファイルの削除
os.remove(temp_excel) # 一時的なExcelファイルを削除
print(f"成功: {output_pdf}")
except Exception as e: # エラーが発生しても次の行に進むための例外処理
print(f"【失敗】 {i}行目の処理でエラー: {e}")
finally:
# ループ内での後片付け(必要に応じて)
pass # 今回は特に後片付けはないですが、ここでExcelの設定をリセットすることもできます
finally:
# 5. Excelを完全に終了
excel.Quit()
print("すべての処理が完了しました。")
if __name__ == "__main__":
app = InvoiceApp()
app.mainloop()画面の操作方法とテスト結果の確認
- コードを実行し、GUIウィンドウが立ち上がれば成功です
- 「受注管理表を選択」「請求書雛形を選択」でExcelファイルを選びます
- シート名・開始行・終了行・列/セルの対応を入力します
- 「PDF作成開始」ボタンを押します
- VS Codeのターミナルに「6件分の処理完了」が表示され、同じフォルダに6件分のPDFができていれば成功です
現場で役立つGUIアプリ(デスクトップアプリ)開発 3つのコツ
① filedialogによる直感的なファイル選択(パス入力ミスの防止)
パスを直接キーボードで打つのはミスのもとです。from tkinter import filedialog と書き、filedialog.askopenfilename() を使うと、Windows標準の「ファイルを開く」ダイアログを呼び出せます。選ばれたパスは StringVar を通じて画面のラベルに即座に反映させます。filedialog の使い方(前回開いたフォルダを記憶する実装など)は、tkinterのfiledialogでExcelファイルを選択する方法で詳しく解説しています。
② .get() を使った画面入力値(開始行・終了行)のプログラムへの引き渡し
GUIを作る最大のメリットがここです。ユーザーが入力欄(CTkEntry)に入れた「開始行」「終了行」を self.entry_start.get() で取得し(文字列で返るので int() で数値化)、そのまま for 文の range() に渡します。コードを一切書き換えずに、処理する範囲を自由に変更できるようになります。
③ class(クラス)を使った本格的なアプリケーション設計
変数がごちゃごちゃにならないよう、class InvoiceApp(ctk.CTk): というクラスでアプリ全体を設計しています。self.entry_start のようにウィジェットをクラスの持ち物にしておくと、どのメソッドからでも参照でき、コードが整理されます。クラスの書き方に不安があれば、先に基礎編(CustomTkinter入門)を読んでおくとスムーズです。
まとめ:ツールは完成、しかし「他人のPCで動かない」最後の壁
全3回の「Invoice Maker」制作記、Excelの裏側操作から始まり、最後はデスクトップアプリまで到達しました。これで請求書のPDF化は、マウス操作数回で終わります。
ただし、このツールを自分以外のPC(他の社員のパソコン)で動かそうとすると、最後の壁が立ちはだかります。相手のPCにもPythonとライブラリをインストールしないといけないことです。非エンジニアに環境構築を頼むのは骨が折れます。
この壁を越える方法としては、PyInstallerなどでコードを .exe にまとめて「ダブルクリックで起動する普通のソフト」にする、というアプローチがあります(本記事では詳しくは触れていません)。
PR
Pythonがインストールされていないパソコンでも、ダブルクリックで起動できる実行ファイル(.exe)化済みの完全版パッケージ「NINJIN Invoice Maker」をnoteで頒布しています。すぐに使える設定項目つき、ソースコードなしで動きます。
👉 noteで確認する次に読む記事
よくある質問(CustomTkinter GUI化のつまずき)
QCustomTkinterのウィンドウは出るのに「PDF作成開始」を押しても何も起きません
AまずVS Codeのターミナルにエラーが出ていないか確認します。「エラー:ファイルを選択してください」なら、受注管理表と請求書雛形を両方選んでください。ModuleNotFoundError: No module named ‘win32com’ なら pip install pywin32 を実行します。win32comはExcel本体を操作するため、Excelがインストールされていないパソコンでは動きません。
Qfiledialogをimportしてもファイル選択ダイアログが出ません
Aimport文は from tkinter import filedialog と書きます(customtkinter側にはfiledialogはありません)。filedialog.askopenfilename() の戻り値(パス文字列)を変数で受けて使います。ダイアログでキャンセルすると空文字が返るので、その分岐(if path: …)も入れておくと安全です。
QEntryに入れた開始行・終了行が反映されません
Aself.entry_start.get() は文字列を返すので、int() で数値に変換してから range() に渡します。この記事のコードでは start_row = int(self.entry_start.get()) としています。入力欄が空だと int() が ValueError になるため、デフォルト値を insert(0, “4”) のように入れておきます。
Qclass InvoiceApp(ctk.CTk): のクラス設計が難しいです。関数だけで作れませんか?
A小さなツールなら関数だけでも作れます。ただしウィジェットが増えると、関数間での変数の受け渡しが煩雑になります。クラスにして self.entry_start のように持たせると、どのメソッドからでも参照でき、コードが整理されます。CTkの基本に不安があれば、先に基礎編(CustomTkinter入門)を読むと理解しやすいです。
Q作ったツールを他の人のパソコンで使ってもらうにはどうすればいいですか?
A相手のパソコンにPythonとライブラリ(openpyxl・pywin32・customtkinter)をインストールする必要があります。それが難しい場合は、PyInstallerなどでコードを .exe にまとめる方法があります。環境構築不要の配布パッケージは著者のnoteで頒布しています。
Follow me on X
本ブログの中の人「ニンジン🥕」はXやってます。新着記事はいち早くポストでお知らせしているので、見逃したくない方は @ninjin_py_vba をフォローしてみてください🥕
@ninjin_py_vba をフォローシリーズ記事一覧
電子工作・自動化ツールの製作記録をシリーズごとにまとめています。気になるテーマからお読みください。
-
FILE.01 — IoT
Raspberry Pi Pico W × GASラズパイPico W×GASで自作!「LINEで動く温湿度&スマートリモコン」完全ロードマップ
「外出中のペットの室温が心配…」そんな思いからスタートしたIoT自作プロジェクトの総集編。温湿度監視からエアコン遠隔操作まで、仕組みをゼロから作りたい方のための全5回の開発記録です。
シリーズを読む -
FILE.02 — CNC
Arduino × レーザー刻印CNCCNC自作シリーズ
高精度な加工を目指し、本格的な自作CNC製作に挑戦中。現在は基幹パーツであるオリエンタルモーターの納品を待つ「設計・準備編」を公開。ハードとソフトの両面から、理想のマシンを形にする過程をリアルタイムにお届けします。
シリーズを読む -
FILE.03 — XY軸制御
Raspberry Pi × ステッピングモーターXYテーブルシリーズ
Raspberry Piとステッパモーターを使い、ゼロから2軸制御に挑む記録。OS設定から回路設計、多軸制御のPythonコードまで、躓きやすいポイントを徹底図解。電子工作初心者が「動く感動」を味わうための実戦ガイドです。
シリーズを読む -
FILE.04 — 業務自動化
Python × Excel × CustomTkinter【Python開発記】NINJIN Mail制作記
PythonとExcelを連携させ、実務で即戦力となるメール送信ツールを開発。CustomTkinterによるUI構築や、ミスを防ぐテンプレート活用術など、現場の「痒い所に手が届く」自動化ノウハウを細部まで丁寧に解説します。
シリーズを読む -
FILE.05 — 業務自動化
Python × Excel × GUIアプリ化Invoice Maker(請求書・見積書自動作成)
手作業の請求書作成から卒業!PythonでExcelデータを読み込み一括PDF化する「Invoice_Maker」の作り方を全3回で解説。基本ロジックからGUIアプリ化まで、実務で役立つ自動化ノウハウが満載です。
シリーズを読む





コメント