はじめに
こんにちは、VBA・Python業務自動化ラボ(py-vbalab.com)のニンジンです。
全3回でお届けしている「請求書自動作成ツール」制作記。これまでの連載で、Excelからのデータ読み取り、PDF化、そして複数データのループ処理という「裏側のロジック」は完璧に完成しました。
しかし、現場で実際に使うとなるとどうでしょう? 「今月は開始行を15行目に変えたい」という時、事務のスタッフに「Pythonのプログラムを開いて、for i in range(15, 50): に書き換えてください」とお願いするのは非現実的ですよね。間違えてコードを消してしまうリスクもあります。
そこで最終回となる今回は、**誰でも直感的に操作できる「操作画面(GUI)」**を作成します! 真っ黒なターミナル画面とはお別れです。モダンで美しい画面を作り上げ、社内の誰もが使える「プロ仕様のツール」を完成させましょう。
【PR:Python環境不要!今すぐ完成版ツールを使いたい方へ】

「コードを書く時間がない」 「社内のPCすべてにPythonをインストールするのは無理!」
という方に向けて、本記事で作成したツールの完全版(面倒な環境構築が一切不要なフルパッケージ版)である**【Invoice Maker (NINJIN EDITION)】**をnoteにて頒布しています。 ダウンロードしてすぐに、今の業務でお使いいただけます!
👉 Invoice Maker (NINJIN EDITION) をnoteでチェックする
準備1:CustomTkinterのインストール
今回は、Pythonの標準GUIライブラリ「Tkinter」を、今風のフラットデザイン(ダークモード対応など)に進化させた**「CustomTkinter」**を使用します。
コマンドプロンプト(またはターミナル)で以下を実行してインストールします。
準備2:テストに必要なExcelファイルを準備
お手持ちの管理表と書類フォーマットを準備いただくか、下記のサンプルをご使用下さい。
※第1回、第2回の記事と同じExcelファイルです。
準備3:Pythonコードを動かす環境の準備
ツール完成!GUI版Pythonスクリプト
こちらが今回の完成形となるコードの骨格です。 第2回で作ったPDF化のロジックを、ボタンを押したときに呼び出す run_process という関数の中に閉じ込め、画面の入力欄と連動させています。
初心者でもコードの意味が分かるように、1行ずつ説明文を追加してあります。
# 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アプリ開発のコツ
① 直感的なファイル選択 (filedialog)
パスを直接キーボードで打ち込むのはミスのもとです。filedialog.askopenfilename() を使うことで、Windows標準の「ファイルを開く」画面を呼び出すことができます。選んだファイルのパスは StringVar を使って画面のラベルに即座に反映させます。
② 画面の入力値をプログラムに渡す (.get())
GUIを作った最大のメリットがここです。 ユーザーが画面の入力欄(Entry)に入力した「開始行」や「終了行」の数字を、self.entry_start.get() で取得します。これをそのまま for 文の range() に渡すことで、コードを一切書き換えることなく、処理する範囲を自由に変更できるようになりました。
③ Class(クラス)を使った本格的な設計
変数がごちゃごちゃにならないよう、class InvoiceApp(ctk.CTk): という「クラス」を使ってアプリ全体を設計しています。この書き方をマスターすれば、これからのPythonツール開発が一気にプロレベルへと引き上がります。
まとめ:ついに完成!しかし、最後の壁が…
全3回にわたる「Invoice Maker」の制作記、いかがだったでしょうか。 Excelの裏側操作から始まり、最後はこんなに立派なデスクトップアプリが完成しました。これで、面倒な請求書のPDF化作業は、マウスのクリック数回で終わるようになります。
しかし、このツールを**「自分以外のPC(他の社員のパソコン)」で動かそうとした時、最後の壁が立ちはだかります。 それは「相手のPCにもPythonとライブラリをインストールしなければならない」**ということです。非エンジニアのスタッフに環境構築をお願いするのは、非常に骨が折れる作業です。
「せっかく作ったのに、自分のパソコンでしか使えないの…?」
ご安心ください! そんなお悩みを解決するため、Pythonがインストールされていないパソコンでも、ダブルクリックするだけで普通のソフトと同じように起動できる**【実行ファイル(.exe)化済み】の完全版パッケージ**をご用意しました!
💡 社内ですぐにツールを展開したい方へ
「NINJIN Invoice Maker」のフル機能(実行ファイル、すぐに使える設定項目、ソースコードなしで動くパッケージ)をnoteにて頒布しています。 業務のDX化を「今すぐ」実現したい方は、ぜひ以下のリンクからチェックしてみてください!
👉 Invoice Maker (NINJIN EDITION) をnoteで確認する
最後までお読みいただき、ありがとうございました。py-vbalab.comでは、これからも皆様の業務を劇的に楽にするVBA&Pythonの技術を発信していきます!











コメント