0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Python初学者】楽天価格チェッカーを作ってみた ⑥  ポップアップUI(popup.py)

0
Last updated at Posted at 2026-04-22

【Python初学者】楽天価格チェッカーを作ってみた ⑥ ― ポップアップUI(popup.py)

はじめに🐰

Python初学者のわたしが覚えるために、学んだことを整理し、理解を深めるために記事を書いています。
私が実際にやってみて、悩んだ部分や過程などを残していきます🐥

ターミナルの文字入力ではなく、GUIのポップアップウィンドウでキーワードを入力できるようにしたファイルです。Pythonに標準で入っている tkinter ライブラリを使っています。


本日のゴール⚽️

  • tkinter で基本的なウィンドウとウィジェット(部品)を作れるようになる
  • @staticmethod の意味を理解する
  • イベント(ボタンクリック・Enterキー)の処理の仕組みを理解する

完成したコード🐣

import tkinter as tk
from tkinter import messagebox


class PopupManager:

    # -----------------------
    # 1つ目のフロー
    # キーワード入力ダイアログを表示する
    # OKを押したら入力文字列を返す
    # キャンセル・×ボタンは ValueError を送出する
    # -----------------------
    @staticmethod
    def ask_keywords() -> str:
        root = tk.Tk()
        root.withdraw()  # メインウィンドウは非表示

        # ダイアログウィンドウを作成
        dialog = tk.Toplevel(root)
        dialog.title("楽天価格チェッカー")
        dialog.resizable(False, False)  # サイズ変更不可
        dialog.grab_set()               # ダイアログが閉じるまで他の操作をブロック

        # ウィンドウを画面中央に配置
        dialog.update_idletasks()
        w, h = 380, 170
        x = (dialog.winfo_screenwidth()  - w) // 2
        y = (dialog.winfo_screenheight() - h) // 2
        dialog.geometry(f"{w}x{h}+{x}+{y}")

        # ラベル(説明文)
        tk.Label(
            dialog,
            text="検索キーワードを入力してください。\nAND検索はカンマ区切りで入力できます。\n例)キーワード1,キーワード2",
            justify="left",
            padx=16,
            pady=10,
        ).pack(anchor="w")

        # 入力欄
        entry_var = tk.StringVar()
        entry = tk.Entry(dialog, textvariable=entry_var, width=42)
        entry.pack(padx=16, pady=(0, 12))
        entry.focus_set()  # 起動直後にカーソルをここに置く

        # OKとキャンセルの結果を保持する辞書
        result: dict = {"value": None, "cancelled": True}

        # OKボタンが押されたときの処理
        def on_ok(event=None):
            value = entry_var.get().strip()
            if not value:
                messagebox.showwarning("入力エラー", "キーワードを入力してください。", parent=dialog)
                return
            result["value"]     = value
            result["cancelled"] = False
            dialog.destroy()

        # キャンセルボタンが押されたときの処理
        def on_cancel():
            dialog.destroy()

        # EnterキーでもOKできる
        entry.bind("<Return>", on_ok)
        # ×ボタンはキャンセルと同じ動作
        dialog.protocol("WM_DELETE_WINDOW", on_cancel)

        # ボタン行
        btn_frame = tk.Frame(dialog)
        btn_frame.pack()
        tk.Button(btn_frame, text="キャンセル", width=10, command=on_cancel).pack(side="left", padx=8)
        tk.Button(btn_frame, text="OK",         width=10, command=on_ok, default="active").pack(side="left", padx=8)

        root.wait_window(dialog)  # ダイアログが閉じるまで待つ
        root.destroy()

        if result["cancelled"]:
            raise ValueError("キーワード入力がキャンセルされました。")

        return result["value"]

    # -----------------------
    # 2つ目のフロー
    # 処理完了のポップアップを表示する
    # -----------------------
    @staticmethod
    def show_complete(filepath: str) -> None:
        root = tk.Tk()
        root.withdraw()
        messagebox.showinfo("完了", f"CSVの保存が完了しました。\n\n{filepath}")
        root.destroy()

    # -----------------------
    # エラーをポップアップで表示する
    # -----------------------
    @staticmethod
    def show_error(message: str) -> None:
        root = tk.Tk()
        root.withdraw()
        messagebox.showerror("エラー", message)
        root.destroy()

コードの詳細解説

@staticmethod とは?

まずは結論から🐣
👉 「インスタンス(self)を使わずに呼び出せるメソッド」

@staticmethod
def ask_keywords() -> str:

通常のメソッドは self(インスタンス)を必要とします!
@staticmethod をつけると、self なしで定義でき、インスタンスを作らなくても直接クラス名から呼べます🧐✨️

# 通常のメソッドの呼び方
popup = PopupManager()
popup.ask_keywords()

# @staticmethod の呼び方(インスタンス不要)
PopupManager.ask_keywords()

PopupManager はウィンドウを表示するだけで、内部に状態(変数)を持たないため @staticmethod が適しています!

🧠 なぜ今回 @staticmethod を使っているのか?
今回のPopupManager

  • 入力ダイアログを表示する
  • 完了メッセージを表示する
  • エラーを表示する

👉 「処理を実行するだけ」で、データを持たない!
だから @staticmethod がピッタリです😊


② tkinter の基本ウィジェット

tkinter はPython標準のGUIライブラリです。画面に表示する部品をウィジェットと呼びます。

ウィジェット 役割
tk.Tk() メインウィンドウ(親)を作る
tk.Toplevel() 子ウィンドウ(ダイアログ)を作る
tk.Label() テキストを表示する
tk.Entry() 1行テキスト入力欄
tk.Button() ボタン
tk.Frame() 複数ウィジェットをまとめるコンテナ
# ウィジェットはこの順番で作る
# 1. 親ウィンドウを作る
root = tk.Tk()

# 2. 親を隠す(ダイアログだけ表示するため)
root.withdraw()

# 3. 子ウィンドウ(ダイアログ)を作る
dialog = tk.Toplevel(root)

③ ウィンドウを画面中央に配置する

ウィンドウを画面の中央に表示するための処理です!

dialog.update_idletasks()
w, h = 380, 170
x = (dialog.winfo_screenwidth()  - w) // 2
y = (dialog.winfo_screenheight() - h) // 2
dialog.geometry(f"{w}x{h}+{x}+{y}")

🔰「元からあるもの」と「自分で決めているもの」

このコードには、tkinterが用意しているメソッドと自分で決めている値が混ざっています!🐣

✔ tkinterに元から用意されているもの

dialog.update_idletasks()
dialog.winfo_screenwidth()
dialog.winfo_screenheight()
dialog.geometry()

これらはすべて、tkinter のウィンドウオブジェクトが最初から持っている機能です。

  • update_idletasks():画面情報を一度更新する
  • winfo_screenwidth():画面の横幅を取得
  • winfo_screenheight():画面の高さを取得
  • geometry():サイズと位置を設定する

👉 自分で定義しなくても使える“組み込みの機能”

✔ 自分で決めているもの

w, h = 380, 170

👉 ウィンドウのサイズ(これは自由に変更できる)

x = (画面の幅 - ウィンドウの幅) // 2
y = (画面の高さ - ウィンドウの高さ) // 2

👉 中央に配置するための計算(自分でロジックを書いている)

winfo_screenwidth()winfo_screenheight() は画面の解像度(幅・高さ)を取得します!
ウィンドウの幅・高さを引いて2で割ると、中央に置くための座標が計算できます。

geometry("幅x高さ+X座標+Y座標") でウィンドウのサイズと位置を指定します。

💡何をしているのか(シンプルに!)
① 画面サイズを取得(tkinterの機能)
② ウィンドウサイズを決める(自分で設定)
③ 中央の位置を計算(自分で計算)
④ 位置とサイズを適用(tkinterの機能)


④ StringVar でテキスト入力欄と連動させる

テキスト入力欄(Entry)と、プログラム側の変数を連動させるための仕組みです!🐣

entry_var = tk.StringVar()
entry = tk.Entry(dialog, textvariable=entry_var, width=42)

tk.StringVar() はtkinterの特別な文字列変数です!
textvariable に渡すと、入力欄の内容が自動的にこの変数と同期されます。

🧠 「元からあるもの」と「自分で決めているもの」
✔ tkinterに元から用意されているもの

tk.StringVar()
tk.Entry()

StringVar():文字列を扱うためのtkinter専用の変数
Entry():1行のテキスト入力欄

👉 GUIで入力を扱うための組み込み機能

💡 何をしているのか(シンプルに)
① 入力を入れるための箱(StringVar)を用意
② 入力欄(Entry)とその箱をつなぐ
③ ユーザーが入力すると、自動で箱に値が入る

🐣 値の取り出し方

# 入力欄の内容を取り出す
value = entry_var.get()  # ユーザーが入力した文字列を取得

👉 入力欄に入力された文字列を取得できる


⑤ 辞書で関数の外に値を渡す

ダイアログ内で入力された値や状態を、外側に渡すための処理です🐣

result: dict = {"value": None, "cancelled": True}

def on_ok(event=None):
    result["value"]     = value
    result["cancelled"] = False
    dialog.destroy()

on_ok() はダイアログの内側で定義された関数ですが、外側の result 辞書を書き換えることができます(クロージャという仕組みです)。

tkinterでは、ボタンが押されたときの処理は別の関数(on_ok)として実行されます。
そのため👇

value = None

def on_ok():
    value = "入力値"

👉 これは外側の value は変わりません(別の変数として扱われるため)

一方、辞書を使うと👇

result["value"] = "入力値"

👉 同じオブジェクトの中身を書き換えるため、外側にも反映されます!!

クロージャとは、関数の中から外側の変数を使える仕組みです!
内側の関数でも、外側で用意したデータを共有して扱うことができます😊

ダイアログが閉じた後、result を参照することで「OKが押されたかキャンセルされたか」と「入力値」を取り出せます。

なぜ return しないのか?
def on_ok():
return value

と書きたくなりますが、これは使えません😱

👉 on_ok() は「ボタンが押されたときに自動で呼ばれる関数」であり、
戻り値を受け取る場所がないためです⚡️

🐣 ダイアログ終了後の処理

if result["cancelled"]:
    raise ValueError("キャンセルされました")  # エラーとして処理
return result["value"]                        # 入力値を返す

👉 ダイアログが閉じた後に、結果をまとめて取り出します


⑥ イベントのバインド

# Enterキーを押したら on_ok を実行
entry.bind("<Return>", on_ok)

# ×ボタンを押したら on_cancel を実行
dialog.protocol("WM_DELETE_WINDOW", on_cancel)

ここでは、「ユーザーの操作」と「実行する処理」を結びつけています。

たとえば、

  • Enterキーを押したとき
  • ウィンドウの×ボタンを押したとき

などの“きっかけ(イベント)”に対して、「この関数を動かす」と設定しています!

🔶.bind() とは?

.bind()は「この操作が起きたら、この関数を実行してね」
という設定をするものです🐣

entry.bind("<Return>", on_ok)

👉「入力欄でEnterキーが押されたら、on_ok を実行する」という意味になります。

"<Return>" は Enterキー を表す決まった書き方です🐣

🔶.protocol() とは?

dialog.protocol("WM_DELETE_WINDOW", on_cancel)

これは少し特殊です!
👉「ウィンドウの×ボタンが押されたときの処理」を設定しています!

つまり、「×ボタンが押されたら on_cancel を実行する」という意味です!

■ なぜこれが必要?
これを設定していないと、
×ボタンを押しても処理が中途半端に終わるプログラム側の状態(値など)が更新されないといった問題が起きることがあります😱


⑦ messagebox で簡単なポップアップ

from tkinter import messagebox

# 情報ダイアログ
messagebox.showinfo("タイトル", "メッセージ")

# 警告ダイアログ
messagebox.showwarning("タイトル", "メッセージ")

# エラーダイアログ
messagebox.showerror("タイトル", "メッセージ")

messagebox は1行で呼べる便利なダイアログです。完了通知やエラー表示など、シンプルなポップアップはこれで対応ができます😊


⑧ root.wait_window() で処理を待つ

root.wait_window(dialog)  # ダイアログが閉じるまでここで待つ
root.destroy()

wait_window() を使うことで、ダイアログが閉じられるまで次の処理に進まないようにしています!
ダイアログが閉じたら root.destroy() でメインウィンドウも破棄します!


まとめ

学んだこと ポイント
@staticmethod インスタンス不要で呼べるメソッド
tkinter の基本構造 Tk()Toplevel() → ウィジェット → .pack()
StringVar 入力欄とプログラムを連動させる変数
イベントのバインド .bind() でキーやクリックに処理を紐づける
protocol("WM_DELETE_WINDOW") ×ボタンの動作をカスタマイズする
クロージャ(辞書経由) 内側の関数から外側の変数を書き換えるテクニック
messagebox 1行で呼べるシンプルなポップアップ

次回は utils/path_helper.pyconfig.py(パス管理と設定値)を解説します!


🐣シリーズ一覧🐣

  • ① プロジェクト全体像とフォルダ構成
  • ② 楽天APIリクエスト(rakuten_api.py)
  • ③ フィルタリング・並び替え(price_list_builder.py)
  • ④ 統計計算(price_stats.py)
  • ⑤ CSV保存(csv_saver.py)
  • ⑥ ポップアップUI(popup.py) ← 今回
  • ⑦ パス管理・設定(path_helper.py & config.py)
  • ⑧ 全体統括(main_flow.py)
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?