QRコード生成器
テキスト・URL・WiFi設定をQRコードに変換しPNG保存・プレビュー表示するジェネレーター。qrcode活用。
1. アプリ概要
テキスト・URL・WiFi設定をQRコードに変換しPNG保存・プレビュー表示するジェネレーター。qrcode活用。
このアプリはmediaカテゴリの実践的なPythonアプリです。使用ライブラリは tkinter(標準ライブラリ)・qrcode・Pillow、難易度は ★★☆ です。
Pythonの豊富なライブラリを活用することで、実用的なアプリを短いコードで実装できます。ソースコードをコピーして実行し、仕組みを理解したうえでカスタマイズに挑戦してみてください。
GUIアプリ開発はプログラミングの楽しさを実感できる最も効果的な学習方法のひとつです。変数・関数・クラス・イベント処理などの重要な概念が自然と身につきます。
2. 機能一覧
- QRコード生成器のメイン機能
- 直感的なGUIインターフェース
- 入力値のバリデーション
- エラーハンドリング
- 結果の見やすい表示
- クリア機能付き
3. 事前準備・環境
Python 3.10 以上 / Windows・Mac・Linux すべて対応
以下の環境で動作確認しています。
- Python 3.10 以上
- OS: Windows 10/11・macOS 12+・Ubuntu 20.04+
インストールが必要なライブラリ
pip install qrcode[pil] pillow
4. 完全なソースコード
右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import io
try:
import qrcode
from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q, ERROR_CORRECT_H
QRCODE_AVAILABLE = True
except ImportError:
QRCODE_AVAILABLE = False
try:
from PIL import Image, ImageTk
PIL_AVAILABLE = True
except ImportError:
PIL_AVAILABLE = False
class App073:
"""QRコード生成器"""
EC_LEVELS = {
"L (7%)": None,
"M (15%)": None,
"Q (25%)": None,
"H (30%)": None,
}
def __init__(self, root):
self.root = root
self.root.title("QRコード生成器")
self.root.geometry("680x560")
self.root.configure(bg="#1e1e1e")
self._qr_img = None # PIL Image
self._tk_img = None # ImageTk.PhotoImage
if QRCODE_AVAILABLE:
self.EC_LEVELS = {
"L (7%)": ERROR_CORRECT_L,
"M (15%)": ERROR_CORRECT_M,
"Q (25%)": ERROR_CORRECT_Q,
"H (30%)": ERROR_CORRECT_H,
}
self._build_ui()
def _build_ui(self):
header = tk.Frame(self.root, bg="#252526", pady=6)
header.pack(fill=tk.X)
tk.Label(header, text="◼ QRコード生成器",
font=("Noto Sans JP", 12, "bold"),
bg="#252526", fg="#4fc3f7").pack(side=tk.LEFT, padx=12)
if not QRCODE_AVAILABLE:
tk.Label(self.root,
text="⚠ qrcode が未インストールです (pip install qrcode[pil])",
bg="#fff3cd", fg="#856404", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X)
# 入力エリア
inp_f = tk.LabelFrame(self.root, text="入力テキスト / URL",
bg="#252526", fg="#ccc", font=("Arial", 9),
padx=8, pady=6)
inp_f.pack(fill=tk.X, padx=8, pady=6)
self.input_text = tk.Text(inp_f, height=4, bg="#0d1117", fg="#c9d1d9",
font=("Arial", 11), relief=tk.FLAT,
insertbackground="white")
self.input_text.pack(fill=tk.X)
self.input_text.insert("1.0", "https://pythonland.tech/")
# 設定パネル
cfg_f = tk.Frame(self.root, bg="#1e1e1e")
cfg_f.pack(fill=tk.X, padx=8, pady=2)
# 誤り訂正レベル
tk.Label(cfg_f, text="誤り訂正:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=0, sticky="w")
self.ec_var = tk.StringVar(value="M (15%)")
ttk.Combobox(cfg_f, textvariable=self.ec_var,
values=list(self.EC_LEVELS.keys()),
state="readonly", width=12).grid(row=0, column=1, padx=4)
# ボックスサイズ
tk.Label(cfg_f, text="ボックスサイズ:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=2, sticky="w", padx=(12, 0))
self.box_var = tk.IntVar(value=10)
ttk.Spinbox(cfg_f, from_=2, to=20, textvariable=self.box_var,
width=5).grid(row=0, column=3, padx=4)
# ボーダー
tk.Label(cfg_f, text="ボーダー:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=4, sticky="w", padx=(12, 0))
self.border_var = tk.IntVar(value=4)
ttk.Spinbox(cfg_f, from_=0, to=10, textvariable=self.border_var,
width=5).grid(row=0, column=5, padx=4)
# 前景色 / 背景色
tk.Label(cfg_f, text="前景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=0, sticky="w", pady=4)
self.fg_var = tk.StringVar(value="#000000")
ttk.Entry(cfg_f, textvariable=self.fg_var, width=9).grid(row=1, column=1, padx=4)
tk.Label(cfg_f, text="背景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=2, sticky="w", padx=(12, 0))
self.bg_var = tk.StringVar(value="#ffffff")
ttk.Entry(cfg_f, textvariable=self.bg_var, width=9).grid(row=1, column=3, padx=4)
# ボタン行
btn_f = tk.Frame(self.root, bg="#1e1e1e")
btn_f.pack(pady=6)
tk.Button(btn_f, text="▶ 生成", command=self._generate,
bg="#1565c0", fg="white", relief=tk.FLAT,
font=("Arial", 12, "bold"), padx=20, pady=6,
activebackground="#0d47a1", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="💾 PNG保存", command=self._save,
bg="#2e7d32", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#1b5e20", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="📋 クリップボード", command=self._copy_clipboard,
bg="#3c3c3c", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#505050", bd=0).pack(side=tk.LEFT, padx=6)
# QRプレビューキャンバス
self.canvas = tk.Canvas(self.root, bg="#2d2d2d", height=300,
highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)
self.canvas.create_text(
300, 150, text="「生成」ボタンを押してQRコードを作成", fill="#555",
font=("Arial", 11))
self.status_var = tk.StringVar(value="テキストを入力して生成してください")
tk.Label(self.root, textvariable=self.status_var,
bg="#252526", fg="#858585", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)
# Enterキーでも生成
self.root.bind("<Return>", lambda e: self._generate())
def _generate(self):
if not QRCODE_AVAILABLE or not PIL_AVAILABLE:
messagebox.showerror("エラー",
"pip install qrcode[pil] Pillow")
return
data = self.input_text.get("1.0", tk.END).strip()
if not data:
messagebox.showwarning("警告", "テキストを入力してください")
return
try:
ec = self.EC_LEVELS.get(self.ec_var.get(), ERROR_CORRECT_M)
qr = qrcode.QRCode(
error_correction=ec,
box_size=self.box_var.get(),
border=self.border_var.get())
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(
fill_color=self.fg_var.get(),
back_color=self.bg_var.get())
self._qr_img = img.get_image() if hasattr(img, "get_image") else img._img
except Exception as e:
messagebox.showerror("エラー", str(e))
return
self._show_qr()
size = self._qr_img.size
self.status_var.set(
f"生成完了: {size[0]}×{size[1]}px | バージョン: {qr.version} | "
f"文字数: {len(data)}")
def _show_qr(self):
if not self._qr_img:
return
self.canvas.update_idletasks()
cw = max(100, self.canvas.winfo_width())
ch = max(100, self.canvas.winfo_height())
img = self._qr_img.copy()
img.thumbnail((cw - 20, ch - 20), Image.LANCZOS if hasattr(Image, "LANCZOS") else Image.ANTIALIAS)
self._tk_img = ImageTk.PhotoImage(img)
self.canvas.delete("all")
self.canvas.create_image(cw // 2, ch // 2, image=self._tk_img, anchor="center")
def _save(self):
if not self._qr_img:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
path = filedialog.asksaveasfilename(
initialfile="qrcode.png",
defaultextension=".png",
filetypes=[("PNG", "*.png"), ("すべて", "*.*")])
if not path:
return
try:
self._qr_img.save(path)
self.status_var.set(f"保存完了: {path}")
except Exception as e:
messagebox.showerror("エラー", str(e))
def _copy_clipboard(self):
if not self._qr_img or not PIL_AVAILABLE:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
try:
# BMP経由でクリップボードにコピー (Windows専用)
import subprocess, sys
if sys.platform != "win32":
messagebox.showinfo("情報", "クリップボードコピーはWindows専用です")
return
buf = io.BytesIO()
self._qr_img.convert("RGB").save(buf, format="BMP")
data = buf.getvalue()[14:] # BMP file header を除去
import ctypes
ctypes.windll.user32.OpenClipboard(0)
ctypes.windll.user32.EmptyClipboard()
h = ctypes.windll.kernel32.GlobalAlloc(0x0042, len(data))
p = ctypes.windll.kernel32.GlobalLock(h)
ctypes.memmove(p, data, len(data))
ctypes.windll.kernel32.GlobalUnlock(h)
ctypes.windll.user32.SetClipboardData(8, h) # CF_DIB = 8
ctypes.windll.user32.CloseClipboard()
self.status_var.set("クリップボードにコピーしました")
except Exception as e:
messagebox.showerror("エラー", str(e))
if __name__ == "__main__":
root = tk.Tk()
app = App073(root)
root.mainloop()
5. コード解説
QRコード生成器のコードを詳しく解説します。クラスベースの設計で各機能を整理して実装しています。
クラス設計とコンストラクタ
App073クラスにアプリの全機能をまとめています。__init__でウィンドウ設定、_build_ui()でUI構築、process()でメイン処理を担当します。責任の分離により、コードが読みやすくなります。
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import io
try:
import qrcode
from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q, ERROR_CORRECT_H
QRCODE_AVAILABLE = True
except ImportError:
QRCODE_AVAILABLE = False
try:
from PIL import Image, ImageTk
PIL_AVAILABLE = True
except ImportError:
PIL_AVAILABLE = False
class App073:
"""QRコード生成器"""
EC_LEVELS = {
"L (7%)": None,
"M (15%)": None,
"Q (25%)": None,
"H (30%)": None,
}
def __init__(self, root):
self.root = root
self.root.title("QRコード生成器")
self.root.geometry("680x560")
self.root.configure(bg="#1e1e1e")
self._qr_img = None # PIL Image
self._tk_img = None # ImageTk.PhotoImage
if QRCODE_AVAILABLE:
self.EC_LEVELS = {
"L (7%)": ERROR_CORRECT_L,
"M (15%)": ERROR_CORRECT_M,
"Q (25%)": ERROR_CORRECT_Q,
"H (30%)": ERROR_CORRECT_H,
}
self._build_ui()
def _build_ui(self):
header = tk.Frame(self.root, bg="#252526", pady=6)
header.pack(fill=tk.X)
tk.Label(header, text="◼ QRコード生成器",
font=("Noto Sans JP", 12, "bold"),
bg="#252526", fg="#4fc3f7").pack(side=tk.LEFT, padx=12)
if not QRCODE_AVAILABLE:
tk.Label(self.root,
text="⚠ qrcode が未インストールです (pip install qrcode[pil])",
bg="#fff3cd", fg="#856404", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X)
# 入力エリア
inp_f = tk.LabelFrame(self.root, text="入力テキスト / URL",
bg="#252526", fg="#ccc", font=("Arial", 9),
padx=8, pady=6)
inp_f.pack(fill=tk.X, padx=8, pady=6)
self.input_text = tk.Text(inp_f, height=4, bg="#0d1117", fg="#c9d1d9",
font=("Arial", 11), relief=tk.FLAT,
insertbackground="white")
self.input_text.pack(fill=tk.X)
self.input_text.insert("1.0", "https://pythonland.tech/")
# 設定パネル
cfg_f = tk.Frame(self.root, bg="#1e1e1e")
cfg_f.pack(fill=tk.X, padx=8, pady=2)
# 誤り訂正レベル
tk.Label(cfg_f, text="誤り訂正:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=0, sticky="w")
self.ec_var = tk.StringVar(value="M (15%)")
ttk.Combobox(cfg_f, textvariable=self.ec_var,
values=list(self.EC_LEVELS.keys()),
state="readonly", width=12).grid(row=0, column=1, padx=4)
# ボックスサイズ
tk.Label(cfg_f, text="ボックスサイズ:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=2, sticky="w", padx=(12, 0))
self.box_var = tk.IntVar(value=10)
ttk.Spinbox(cfg_f, from_=2, to=20, textvariable=self.box_var,
width=5).grid(row=0, column=3, padx=4)
# ボーダー
tk.Label(cfg_f, text="ボーダー:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=4, sticky="w", padx=(12, 0))
self.border_var = tk.IntVar(value=4)
ttk.Spinbox(cfg_f, from_=0, to=10, textvariable=self.border_var,
width=5).grid(row=0, column=5, padx=4)
# 前景色 / 背景色
tk.Label(cfg_f, text="前景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=0, sticky="w", pady=4)
self.fg_var = tk.StringVar(value="#000000")
ttk.Entry(cfg_f, textvariable=self.fg_var, width=9).grid(row=1, column=1, padx=4)
tk.Label(cfg_f, text="背景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=2, sticky="w", padx=(12, 0))
self.bg_var = tk.StringVar(value="#ffffff")
ttk.Entry(cfg_f, textvariable=self.bg_var, width=9).grid(row=1, column=3, padx=4)
# ボタン行
btn_f = tk.Frame(self.root, bg="#1e1e1e")
btn_f.pack(pady=6)
tk.Button(btn_f, text="▶ 生成", command=self._generate,
bg="#1565c0", fg="white", relief=tk.FLAT,
font=("Arial", 12, "bold"), padx=20, pady=6,
activebackground="#0d47a1", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="💾 PNG保存", command=self._save,
bg="#2e7d32", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#1b5e20", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="📋 クリップボード", command=self._copy_clipboard,
bg="#3c3c3c", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#505050", bd=0).pack(side=tk.LEFT, padx=6)
# QRプレビューキャンバス
self.canvas = tk.Canvas(self.root, bg="#2d2d2d", height=300,
highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)
self.canvas.create_text(
300, 150, text="「生成」ボタンを押してQRコードを作成", fill="#555",
font=("Arial", 11))
self.status_var = tk.StringVar(value="テキストを入力して生成してください")
tk.Label(self.root, textvariable=self.status_var,
bg="#252526", fg="#858585", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)
# Enterキーでも生成
self.root.bind("<Return>", lambda e: self._generate())
def _generate(self):
if not QRCODE_AVAILABLE or not PIL_AVAILABLE:
messagebox.showerror("エラー",
"pip install qrcode[pil] Pillow")
return
data = self.input_text.get("1.0", tk.END).strip()
if not data:
messagebox.showwarning("警告", "テキストを入力してください")
return
try:
ec = self.EC_LEVELS.get(self.ec_var.get(), ERROR_CORRECT_M)
qr = qrcode.QRCode(
error_correction=ec,
box_size=self.box_var.get(),
border=self.border_var.get())
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(
fill_color=self.fg_var.get(),
back_color=self.bg_var.get())
self._qr_img = img.get_image() if hasattr(img, "get_image") else img._img
except Exception as e:
messagebox.showerror("エラー", str(e))
return
self._show_qr()
size = self._qr_img.size
self.status_var.set(
f"生成完了: {size[0]}×{size[1]}px | バージョン: {qr.version} | "
f"文字数: {len(data)}")
def _show_qr(self):
if not self._qr_img:
return
self.canvas.update_idletasks()
cw = max(100, self.canvas.winfo_width())
ch = max(100, self.canvas.winfo_height())
img = self._qr_img.copy()
img.thumbnail((cw - 20, ch - 20), Image.LANCZOS if hasattr(Image, "LANCZOS") else Image.ANTIALIAS)
self._tk_img = ImageTk.PhotoImage(img)
self.canvas.delete("all")
self.canvas.create_image(cw // 2, ch // 2, image=self._tk_img, anchor="center")
def _save(self):
if not self._qr_img:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
path = filedialog.asksaveasfilename(
initialfile="qrcode.png",
defaultextension=".png",
filetypes=[("PNG", "*.png"), ("すべて", "*.*")])
if not path:
return
try:
self._qr_img.save(path)
self.status_var.set(f"保存完了: {path}")
except Exception as e:
messagebox.showerror("エラー", str(e))
def _copy_clipboard(self):
if not self._qr_img or not PIL_AVAILABLE:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
try:
# BMP経由でクリップボードにコピー (Windows専用)
import subprocess, sys
if sys.platform != "win32":
messagebox.showinfo("情報", "クリップボードコピーはWindows専用です")
return
buf = io.BytesIO()
self._qr_img.convert("RGB").save(buf, format="BMP")
data = buf.getvalue()[14:] # BMP file header を除去
import ctypes
ctypes.windll.user32.OpenClipboard(0)
ctypes.windll.user32.EmptyClipboard()
h = ctypes.windll.kernel32.GlobalAlloc(0x0042, len(data))
p = ctypes.windll.kernel32.GlobalLock(h)
ctypes.memmove(p, data, len(data))
ctypes.windll.kernel32.GlobalUnlock(h)
ctypes.windll.user32.SetClipboardData(8, h) # CF_DIB = 8
ctypes.windll.user32.CloseClipboard()
self.status_var.set("クリップボードにコピーしました")
except Exception as e:
messagebox.showerror("エラー", str(e))
if __name__ == "__main__":
root = tk.Tk()
app = App073(root)
root.mainloop()
UIレイアウトの構築
LabelFrameで入力エリアと結果エリアを視覚的に分けています。pack()で縦に並べ、expand=Trueで結果エリアが画面いっぱいに広がるよう設定しています。
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import io
try:
import qrcode
from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q, ERROR_CORRECT_H
QRCODE_AVAILABLE = True
except ImportError:
QRCODE_AVAILABLE = False
try:
from PIL import Image, ImageTk
PIL_AVAILABLE = True
except ImportError:
PIL_AVAILABLE = False
class App073:
"""QRコード生成器"""
EC_LEVELS = {
"L (7%)": None,
"M (15%)": None,
"Q (25%)": None,
"H (30%)": None,
}
def __init__(self, root):
self.root = root
self.root.title("QRコード生成器")
self.root.geometry("680x560")
self.root.configure(bg="#1e1e1e")
self._qr_img = None # PIL Image
self._tk_img = None # ImageTk.PhotoImage
if QRCODE_AVAILABLE:
self.EC_LEVELS = {
"L (7%)": ERROR_CORRECT_L,
"M (15%)": ERROR_CORRECT_M,
"Q (25%)": ERROR_CORRECT_Q,
"H (30%)": ERROR_CORRECT_H,
}
self._build_ui()
def _build_ui(self):
header = tk.Frame(self.root, bg="#252526", pady=6)
header.pack(fill=tk.X)
tk.Label(header, text="◼ QRコード生成器",
font=("Noto Sans JP", 12, "bold"),
bg="#252526", fg="#4fc3f7").pack(side=tk.LEFT, padx=12)
if not QRCODE_AVAILABLE:
tk.Label(self.root,
text="⚠ qrcode が未インストールです (pip install qrcode[pil])",
bg="#fff3cd", fg="#856404", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X)
# 入力エリア
inp_f = tk.LabelFrame(self.root, text="入力テキスト / URL",
bg="#252526", fg="#ccc", font=("Arial", 9),
padx=8, pady=6)
inp_f.pack(fill=tk.X, padx=8, pady=6)
self.input_text = tk.Text(inp_f, height=4, bg="#0d1117", fg="#c9d1d9",
font=("Arial", 11), relief=tk.FLAT,
insertbackground="white")
self.input_text.pack(fill=tk.X)
self.input_text.insert("1.0", "https://pythonland.tech/")
# 設定パネル
cfg_f = tk.Frame(self.root, bg="#1e1e1e")
cfg_f.pack(fill=tk.X, padx=8, pady=2)
# 誤り訂正レベル
tk.Label(cfg_f, text="誤り訂正:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=0, sticky="w")
self.ec_var = tk.StringVar(value="M (15%)")
ttk.Combobox(cfg_f, textvariable=self.ec_var,
values=list(self.EC_LEVELS.keys()),
state="readonly", width=12).grid(row=0, column=1, padx=4)
# ボックスサイズ
tk.Label(cfg_f, text="ボックスサイズ:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=2, sticky="w", padx=(12, 0))
self.box_var = tk.IntVar(value=10)
ttk.Spinbox(cfg_f, from_=2, to=20, textvariable=self.box_var,
width=5).grid(row=0, column=3, padx=4)
# ボーダー
tk.Label(cfg_f, text="ボーダー:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=4, sticky="w", padx=(12, 0))
self.border_var = tk.IntVar(value=4)
ttk.Spinbox(cfg_f, from_=0, to=10, textvariable=self.border_var,
width=5).grid(row=0, column=5, padx=4)
# 前景色 / 背景色
tk.Label(cfg_f, text="前景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=0, sticky="w", pady=4)
self.fg_var = tk.StringVar(value="#000000")
ttk.Entry(cfg_f, textvariable=self.fg_var, width=9).grid(row=1, column=1, padx=4)
tk.Label(cfg_f, text="背景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=2, sticky="w", padx=(12, 0))
self.bg_var = tk.StringVar(value="#ffffff")
ttk.Entry(cfg_f, textvariable=self.bg_var, width=9).grid(row=1, column=3, padx=4)
# ボタン行
btn_f = tk.Frame(self.root, bg="#1e1e1e")
btn_f.pack(pady=6)
tk.Button(btn_f, text="▶ 生成", command=self._generate,
bg="#1565c0", fg="white", relief=tk.FLAT,
font=("Arial", 12, "bold"), padx=20, pady=6,
activebackground="#0d47a1", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="💾 PNG保存", command=self._save,
bg="#2e7d32", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#1b5e20", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="📋 クリップボード", command=self._copy_clipboard,
bg="#3c3c3c", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#505050", bd=0).pack(side=tk.LEFT, padx=6)
# QRプレビューキャンバス
self.canvas = tk.Canvas(self.root, bg="#2d2d2d", height=300,
highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)
self.canvas.create_text(
300, 150, text="「生成」ボタンを押してQRコードを作成", fill="#555",
font=("Arial", 11))
self.status_var = tk.StringVar(value="テキストを入力して生成してください")
tk.Label(self.root, textvariable=self.status_var,
bg="#252526", fg="#858585", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)
# Enterキーでも生成
self.root.bind("<Return>", lambda e: self._generate())
def _generate(self):
if not QRCODE_AVAILABLE or not PIL_AVAILABLE:
messagebox.showerror("エラー",
"pip install qrcode[pil] Pillow")
return
data = self.input_text.get("1.0", tk.END).strip()
if not data:
messagebox.showwarning("警告", "テキストを入力してください")
return
try:
ec = self.EC_LEVELS.get(self.ec_var.get(), ERROR_CORRECT_M)
qr = qrcode.QRCode(
error_correction=ec,
box_size=self.box_var.get(),
border=self.border_var.get())
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(
fill_color=self.fg_var.get(),
back_color=self.bg_var.get())
self._qr_img = img.get_image() if hasattr(img, "get_image") else img._img
except Exception as e:
messagebox.showerror("エラー", str(e))
return
self._show_qr()
size = self._qr_img.size
self.status_var.set(
f"生成完了: {size[0]}×{size[1]}px | バージョン: {qr.version} | "
f"文字数: {len(data)}")
def _show_qr(self):
if not self._qr_img:
return
self.canvas.update_idletasks()
cw = max(100, self.canvas.winfo_width())
ch = max(100, self.canvas.winfo_height())
img = self._qr_img.copy()
img.thumbnail((cw - 20, ch - 20), Image.LANCZOS if hasattr(Image, "LANCZOS") else Image.ANTIALIAS)
self._tk_img = ImageTk.PhotoImage(img)
self.canvas.delete("all")
self.canvas.create_image(cw // 2, ch // 2, image=self._tk_img, anchor="center")
def _save(self):
if not self._qr_img:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
path = filedialog.asksaveasfilename(
initialfile="qrcode.png",
defaultextension=".png",
filetypes=[("PNG", "*.png"), ("すべて", "*.*")])
if not path:
return
try:
self._qr_img.save(path)
self.status_var.set(f"保存完了: {path}")
except Exception as e:
messagebox.showerror("エラー", str(e))
def _copy_clipboard(self):
if not self._qr_img or not PIL_AVAILABLE:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
try:
# BMP経由でクリップボードにコピー (Windows専用)
import subprocess, sys
if sys.platform != "win32":
messagebox.showinfo("情報", "クリップボードコピーはWindows専用です")
return
buf = io.BytesIO()
self._qr_img.convert("RGB").save(buf, format="BMP")
data = buf.getvalue()[14:] # BMP file header を除去
import ctypes
ctypes.windll.user32.OpenClipboard(0)
ctypes.windll.user32.EmptyClipboard()
h = ctypes.windll.kernel32.GlobalAlloc(0x0042, len(data))
p = ctypes.windll.kernel32.GlobalLock(h)
ctypes.memmove(p, data, len(data))
ctypes.windll.kernel32.GlobalUnlock(h)
ctypes.windll.user32.SetClipboardData(8, h) # CF_DIB = 8
ctypes.windll.user32.CloseClipboard()
self.status_var.set("クリップボードにコピーしました")
except Exception as e:
messagebox.showerror("エラー", str(e))
if __name__ == "__main__":
root = tk.Tk()
app = App073(root)
root.mainloop()
イベント処理
ボタンのcommand引数でクリックイベントを、bind('
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import io
try:
import qrcode
from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q, ERROR_CORRECT_H
QRCODE_AVAILABLE = True
except ImportError:
QRCODE_AVAILABLE = False
try:
from PIL import Image, ImageTk
PIL_AVAILABLE = True
except ImportError:
PIL_AVAILABLE = False
class App073:
"""QRコード生成器"""
EC_LEVELS = {
"L (7%)": None,
"M (15%)": None,
"Q (25%)": None,
"H (30%)": None,
}
def __init__(self, root):
self.root = root
self.root.title("QRコード生成器")
self.root.geometry("680x560")
self.root.configure(bg="#1e1e1e")
self._qr_img = None # PIL Image
self._tk_img = None # ImageTk.PhotoImage
if QRCODE_AVAILABLE:
self.EC_LEVELS = {
"L (7%)": ERROR_CORRECT_L,
"M (15%)": ERROR_CORRECT_M,
"Q (25%)": ERROR_CORRECT_Q,
"H (30%)": ERROR_CORRECT_H,
}
self._build_ui()
def _build_ui(self):
header = tk.Frame(self.root, bg="#252526", pady=6)
header.pack(fill=tk.X)
tk.Label(header, text="◼ QRコード生成器",
font=("Noto Sans JP", 12, "bold"),
bg="#252526", fg="#4fc3f7").pack(side=tk.LEFT, padx=12)
if not QRCODE_AVAILABLE:
tk.Label(self.root,
text="⚠ qrcode が未インストールです (pip install qrcode[pil])",
bg="#fff3cd", fg="#856404", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X)
# 入力エリア
inp_f = tk.LabelFrame(self.root, text="入力テキスト / URL",
bg="#252526", fg="#ccc", font=("Arial", 9),
padx=8, pady=6)
inp_f.pack(fill=tk.X, padx=8, pady=6)
self.input_text = tk.Text(inp_f, height=4, bg="#0d1117", fg="#c9d1d9",
font=("Arial", 11), relief=tk.FLAT,
insertbackground="white")
self.input_text.pack(fill=tk.X)
self.input_text.insert("1.0", "https://pythonland.tech/")
# 設定パネル
cfg_f = tk.Frame(self.root, bg="#1e1e1e")
cfg_f.pack(fill=tk.X, padx=8, pady=2)
# 誤り訂正レベル
tk.Label(cfg_f, text="誤り訂正:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=0, sticky="w")
self.ec_var = tk.StringVar(value="M (15%)")
ttk.Combobox(cfg_f, textvariable=self.ec_var,
values=list(self.EC_LEVELS.keys()),
state="readonly", width=12).grid(row=0, column=1, padx=4)
# ボックスサイズ
tk.Label(cfg_f, text="ボックスサイズ:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=2, sticky="w", padx=(12, 0))
self.box_var = tk.IntVar(value=10)
ttk.Spinbox(cfg_f, from_=2, to=20, textvariable=self.box_var,
width=5).grid(row=0, column=3, padx=4)
# ボーダー
tk.Label(cfg_f, text="ボーダー:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=4, sticky="w", padx=(12, 0))
self.border_var = tk.IntVar(value=4)
ttk.Spinbox(cfg_f, from_=0, to=10, textvariable=self.border_var,
width=5).grid(row=0, column=5, padx=4)
# 前景色 / 背景色
tk.Label(cfg_f, text="前景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=0, sticky="w", pady=4)
self.fg_var = tk.StringVar(value="#000000")
ttk.Entry(cfg_f, textvariable=self.fg_var, width=9).grid(row=1, column=1, padx=4)
tk.Label(cfg_f, text="背景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=2, sticky="w", padx=(12, 0))
self.bg_var = tk.StringVar(value="#ffffff")
ttk.Entry(cfg_f, textvariable=self.bg_var, width=9).grid(row=1, column=3, padx=4)
# ボタン行
btn_f = tk.Frame(self.root, bg="#1e1e1e")
btn_f.pack(pady=6)
tk.Button(btn_f, text="▶ 生成", command=self._generate,
bg="#1565c0", fg="white", relief=tk.FLAT,
font=("Arial", 12, "bold"), padx=20, pady=6,
activebackground="#0d47a1", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="💾 PNG保存", command=self._save,
bg="#2e7d32", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#1b5e20", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="📋 クリップボード", command=self._copy_clipboard,
bg="#3c3c3c", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#505050", bd=0).pack(side=tk.LEFT, padx=6)
# QRプレビューキャンバス
self.canvas = tk.Canvas(self.root, bg="#2d2d2d", height=300,
highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)
self.canvas.create_text(
300, 150, text="「生成」ボタンを押してQRコードを作成", fill="#555",
font=("Arial", 11))
self.status_var = tk.StringVar(value="テキストを入力して生成してください")
tk.Label(self.root, textvariable=self.status_var,
bg="#252526", fg="#858585", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)
# Enterキーでも生成
self.root.bind("<Return>", lambda e: self._generate())
def _generate(self):
if not QRCODE_AVAILABLE or not PIL_AVAILABLE:
messagebox.showerror("エラー",
"pip install qrcode[pil] Pillow")
return
data = self.input_text.get("1.0", tk.END).strip()
if not data:
messagebox.showwarning("警告", "テキストを入力してください")
return
try:
ec = self.EC_LEVELS.get(self.ec_var.get(), ERROR_CORRECT_M)
qr = qrcode.QRCode(
error_correction=ec,
box_size=self.box_var.get(),
border=self.border_var.get())
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(
fill_color=self.fg_var.get(),
back_color=self.bg_var.get())
self._qr_img = img.get_image() if hasattr(img, "get_image") else img._img
except Exception as e:
messagebox.showerror("エラー", str(e))
return
self._show_qr()
size = self._qr_img.size
self.status_var.set(
f"生成完了: {size[0]}×{size[1]}px | バージョン: {qr.version} | "
f"文字数: {len(data)}")
def _show_qr(self):
if not self._qr_img:
return
self.canvas.update_idletasks()
cw = max(100, self.canvas.winfo_width())
ch = max(100, self.canvas.winfo_height())
img = self._qr_img.copy()
img.thumbnail((cw - 20, ch - 20), Image.LANCZOS if hasattr(Image, "LANCZOS") else Image.ANTIALIAS)
self._tk_img = ImageTk.PhotoImage(img)
self.canvas.delete("all")
self.canvas.create_image(cw // 2, ch // 2, image=self._tk_img, anchor="center")
def _save(self):
if not self._qr_img:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
path = filedialog.asksaveasfilename(
initialfile="qrcode.png",
defaultextension=".png",
filetypes=[("PNG", "*.png"), ("すべて", "*.*")])
if not path:
return
try:
self._qr_img.save(path)
self.status_var.set(f"保存完了: {path}")
except Exception as e:
messagebox.showerror("エラー", str(e))
def _copy_clipboard(self):
if not self._qr_img or not PIL_AVAILABLE:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
try:
# BMP経由でクリップボードにコピー (Windows専用)
import subprocess, sys
if sys.platform != "win32":
messagebox.showinfo("情報", "クリップボードコピーはWindows専用です")
return
buf = io.BytesIO()
self._qr_img.convert("RGB").save(buf, format="BMP")
data = buf.getvalue()[14:] # BMP file header を除去
import ctypes
ctypes.windll.user32.OpenClipboard(0)
ctypes.windll.user32.EmptyClipboard()
h = ctypes.windll.kernel32.GlobalAlloc(0x0042, len(data))
p = ctypes.windll.kernel32.GlobalLock(h)
ctypes.memmove(p, data, len(data))
ctypes.windll.kernel32.GlobalUnlock(h)
ctypes.windll.user32.SetClipboardData(8, h) # CF_DIB = 8
ctypes.windll.user32.CloseClipboard()
self.status_var.set("クリップボードにコピーしました")
except Exception as e:
messagebox.showerror("エラー", str(e))
if __name__ == "__main__":
root = tk.Tk()
app = App073(root)
root.mainloop()
Textウィジェットでの結果表示
tk.Textウィジェットをstate=DISABLED(読み取り専用)で作成し、更新時はNORMALに変更してinsert()で内容を書き込み、再びDISABLEDに戻します。
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import io
try:
import qrcode
from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q, ERROR_CORRECT_H
QRCODE_AVAILABLE = True
except ImportError:
QRCODE_AVAILABLE = False
try:
from PIL import Image, ImageTk
PIL_AVAILABLE = True
except ImportError:
PIL_AVAILABLE = False
class App073:
"""QRコード生成器"""
EC_LEVELS = {
"L (7%)": None,
"M (15%)": None,
"Q (25%)": None,
"H (30%)": None,
}
def __init__(self, root):
self.root = root
self.root.title("QRコード生成器")
self.root.geometry("680x560")
self.root.configure(bg="#1e1e1e")
self._qr_img = None # PIL Image
self._tk_img = None # ImageTk.PhotoImage
if QRCODE_AVAILABLE:
self.EC_LEVELS = {
"L (7%)": ERROR_CORRECT_L,
"M (15%)": ERROR_CORRECT_M,
"Q (25%)": ERROR_CORRECT_Q,
"H (30%)": ERROR_CORRECT_H,
}
self._build_ui()
def _build_ui(self):
header = tk.Frame(self.root, bg="#252526", pady=6)
header.pack(fill=tk.X)
tk.Label(header, text="◼ QRコード生成器",
font=("Noto Sans JP", 12, "bold"),
bg="#252526", fg="#4fc3f7").pack(side=tk.LEFT, padx=12)
if not QRCODE_AVAILABLE:
tk.Label(self.root,
text="⚠ qrcode が未インストールです (pip install qrcode[pil])",
bg="#fff3cd", fg="#856404", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X)
# 入力エリア
inp_f = tk.LabelFrame(self.root, text="入力テキスト / URL",
bg="#252526", fg="#ccc", font=("Arial", 9),
padx=8, pady=6)
inp_f.pack(fill=tk.X, padx=8, pady=6)
self.input_text = tk.Text(inp_f, height=4, bg="#0d1117", fg="#c9d1d9",
font=("Arial", 11), relief=tk.FLAT,
insertbackground="white")
self.input_text.pack(fill=tk.X)
self.input_text.insert("1.0", "https://pythonland.tech/")
# 設定パネル
cfg_f = tk.Frame(self.root, bg="#1e1e1e")
cfg_f.pack(fill=tk.X, padx=8, pady=2)
# 誤り訂正レベル
tk.Label(cfg_f, text="誤り訂正:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=0, sticky="w")
self.ec_var = tk.StringVar(value="M (15%)")
ttk.Combobox(cfg_f, textvariable=self.ec_var,
values=list(self.EC_LEVELS.keys()),
state="readonly", width=12).grid(row=0, column=1, padx=4)
# ボックスサイズ
tk.Label(cfg_f, text="ボックスサイズ:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=2, sticky="w", padx=(12, 0))
self.box_var = tk.IntVar(value=10)
ttk.Spinbox(cfg_f, from_=2, to=20, textvariable=self.box_var,
width=5).grid(row=0, column=3, padx=4)
# ボーダー
tk.Label(cfg_f, text="ボーダー:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=4, sticky="w", padx=(12, 0))
self.border_var = tk.IntVar(value=4)
ttk.Spinbox(cfg_f, from_=0, to=10, textvariable=self.border_var,
width=5).grid(row=0, column=5, padx=4)
# 前景色 / 背景色
tk.Label(cfg_f, text="前景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=0, sticky="w", pady=4)
self.fg_var = tk.StringVar(value="#000000")
ttk.Entry(cfg_f, textvariable=self.fg_var, width=9).grid(row=1, column=1, padx=4)
tk.Label(cfg_f, text="背景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=2, sticky="w", padx=(12, 0))
self.bg_var = tk.StringVar(value="#ffffff")
ttk.Entry(cfg_f, textvariable=self.bg_var, width=9).grid(row=1, column=3, padx=4)
# ボタン行
btn_f = tk.Frame(self.root, bg="#1e1e1e")
btn_f.pack(pady=6)
tk.Button(btn_f, text="▶ 生成", command=self._generate,
bg="#1565c0", fg="white", relief=tk.FLAT,
font=("Arial", 12, "bold"), padx=20, pady=6,
activebackground="#0d47a1", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="💾 PNG保存", command=self._save,
bg="#2e7d32", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#1b5e20", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="📋 クリップボード", command=self._copy_clipboard,
bg="#3c3c3c", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#505050", bd=0).pack(side=tk.LEFT, padx=6)
# QRプレビューキャンバス
self.canvas = tk.Canvas(self.root, bg="#2d2d2d", height=300,
highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)
self.canvas.create_text(
300, 150, text="「生成」ボタンを押してQRコードを作成", fill="#555",
font=("Arial", 11))
self.status_var = tk.StringVar(value="テキストを入力して生成してください")
tk.Label(self.root, textvariable=self.status_var,
bg="#252526", fg="#858585", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)
# Enterキーでも生成
self.root.bind("<Return>", lambda e: self._generate())
def _generate(self):
if not QRCODE_AVAILABLE or not PIL_AVAILABLE:
messagebox.showerror("エラー",
"pip install qrcode[pil] Pillow")
return
data = self.input_text.get("1.0", tk.END).strip()
if not data:
messagebox.showwarning("警告", "テキストを入力してください")
return
try:
ec = self.EC_LEVELS.get(self.ec_var.get(), ERROR_CORRECT_M)
qr = qrcode.QRCode(
error_correction=ec,
box_size=self.box_var.get(),
border=self.border_var.get())
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(
fill_color=self.fg_var.get(),
back_color=self.bg_var.get())
self._qr_img = img.get_image() if hasattr(img, "get_image") else img._img
except Exception as e:
messagebox.showerror("エラー", str(e))
return
self._show_qr()
size = self._qr_img.size
self.status_var.set(
f"生成完了: {size[0]}×{size[1]}px | バージョン: {qr.version} | "
f"文字数: {len(data)}")
def _show_qr(self):
if not self._qr_img:
return
self.canvas.update_idletasks()
cw = max(100, self.canvas.winfo_width())
ch = max(100, self.canvas.winfo_height())
img = self._qr_img.copy()
img.thumbnail((cw - 20, ch - 20), Image.LANCZOS if hasattr(Image, "LANCZOS") else Image.ANTIALIAS)
self._tk_img = ImageTk.PhotoImage(img)
self.canvas.delete("all")
self.canvas.create_image(cw // 2, ch // 2, image=self._tk_img, anchor="center")
def _save(self):
if not self._qr_img:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
path = filedialog.asksaveasfilename(
initialfile="qrcode.png",
defaultextension=".png",
filetypes=[("PNG", "*.png"), ("すべて", "*.*")])
if not path:
return
try:
self._qr_img.save(path)
self.status_var.set(f"保存完了: {path}")
except Exception as e:
messagebox.showerror("エラー", str(e))
def _copy_clipboard(self):
if not self._qr_img or not PIL_AVAILABLE:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
try:
# BMP経由でクリップボードにコピー (Windows専用)
import subprocess, sys
if sys.platform != "win32":
messagebox.showinfo("情報", "クリップボードコピーはWindows専用です")
return
buf = io.BytesIO()
self._qr_img.convert("RGB").save(buf, format="BMP")
data = buf.getvalue()[14:] # BMP file header を除去
import ctypes
ctypes.windll.user32.OpenClipboard(0)
ctypes.windll.user32.EmptyClipboard()
h = ctypes.windll.kernel32.GlobalAlloc(0x0042, len(data))
p = ctypes.windll.kernel32.GlobalLock(h)
ctypes.memmove(p, data, len(data))
ctypes.windll.kernel32.GlobalUnlock(h)
ctypes.windll.user32.SetClipboardData(8, h) # CF_DIB = 8
ctypes.windll.user32.CloseClipboard()
self.status_var.set("クリップボードにコピーしました")
except Exception as e:
messagebox.showerror("エラー", str(e))
if __name__ == "__main__":
root = tk.Tk()
app = App073(root)
root.mainloop()
例外処理とエラーハンドリング
try-exceptでValueErrorとExceptionを捕捉し、messagebox.showerror()でエラーメッセージを表示します。予期しないエラーも処理することで、アプリの堅牢性が向上します。
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import io
try:
import qrcode
from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q, ERROR_CORRECT_H
QRCODE_AVAILABLE = True
except ImportError:
QRCODE_AVAILABLE = False
try:
from PIL import Image, ImageTk
PIL_AVAILABLE = True
except ImportError:
PIL_AVAILABLE = False
class App073:
"""QRコード生成器"""
EC_LEVELS = {
"L (7%)": None,
"M (15%)": None,
"Q (25%)": None,
"H (30%)": None,
}
def __init__(self, root):
self.root = root
self.root.title("QRコード生成器")
self.root.geometry("680x560")
self.root.configure(bg="#1e1e1e")
self._qr_img = None # PIL Image
self._tk_img = None # ImageTk.PhotoImage
if QRCODE_AVAILABLE:
self.EC_LEVELS = {
"L (7%)": ERROR_CORRECT_L,
"M (15%)": ERROR_CORRECT_M,
"Q (25%)": ERROR_CORRECT_Q,
"H (30%)": ERROR_CORRECT_H,
}
self._build_ui()
def _build_ui(self):
header = tk.Frame(self.root, bg="#252526", pady=6)
header.pack(fill=tk.X)
tk.Label(header, text="◼ QRコード生成器",
font=("Noto Sans JP", 12, "bold"),
bg="#252526", fg="#4fc3f7").pack(side=tk.LEFT, padx=12)
if not QRCODE_AVAILABLE:
tk.Label(self.root,
text="⚠ qrcode が未インストールです (pip install qrcode[pil])",
bg="#fff3cd", fg="#856404", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X)
# 入力エリア
inp_f = tk.LabelFrame(self.root, text="入力テキスト / URL",
bg="#252526", fg="#ccc", font=("Arial", 9),
padx=8, pady=6)
inp_f.pack(fill=tk.X, padx=8, pady=6)
self.input_text = tk.Text(inp_f, height=4, bg="#0d1117", fg="#c9d1d9",
font=("Arial", 11), relief=tk.FLAT,
insertbackground="white")
self.input_text.pack(fill=tk.X)
self.input_text.insert("1.0", "https://pythonland.tech/")
# 設定パネル
cfg_f = tk.Frame(self.root, bg="#1e1e1e")
cfg_f.pack(fill=tk.X, padx=8, pady=2)
# 誤り訂正レベル
tk.Label(cfg_f, text="誤り訂正:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=0, sticky="w")
self.ec_var = tk.StringVar(value="M (15%)")
ttk.Combobox(cfg_f, textvariable=self.ec_var,
values=list(self.EC_LEVELS.keys()),
state="readonly", width=12).grid(row=0, column=1, padx=4)
# ボックスサイズ
tk.Label(cfg_f, text="ボックスサイズ:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=2, sticky="w", padx=(12, 0))
self.box_var = tk.IntVar(value=10)
ttk.Spinbox(cfg_f, from_=2, to=20, textvariable=self.box_var,
width=5).grid(row=0, column=3, padx=4)
# ボーダー
tk.Label(cfg_f, text="ボーダー:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=0, column=4, sticky="w", padx=(12, 0))
self.border_var = tk.IntVar(value=4)
ttk.Spinbox(cfg_f, from_=0, to=10, textvariable=self.border_var,
width=5).grid(row=0, column=5, padx=4)
# 前景色 / 背景色
tk.Label(cfg_f, text="前景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=0, sticky="w", pady=4)
self.fg_var = tk.StringVar(value="#000000")
ttk.Entry(cfg_f, textvariable=self.fg_var, width=9).grid(row=1, column=1, padx=4)
tk.Label(cfg_f, text="背景色:", bg="#1e1e1e", fg="#ccc",
font=("Arial", 9)).grid(row=1, column=2, sticky="w", padx=(12, 0))
self.bg_var = tk.StringVar(value="#ffffff")
ttk.Entry(cfg_f, textvariable=self.bg_var, width=9).grid(row=1, column=3, padx=4)
# ボタン行
btn_f = tk.Frame(self.root, bg="#1e1e1e")
btn_f.pack(pady=6)
tk.Button(btn_f, text="▶ 生成", command=self._generate,
bg="#1565c0", fg="white", relief=tk.FLAT,
font=("Arial", 12, "bold"), padx=20, pady=6,
activebackground="#0d47a1", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="💾 PNG保存", command=self._save,
bg="#2e7d32", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#1b5e20", bd=0).pack(side=tk.LEFT, padx=6)
tk.Button(btn_f, text="📋 クリップボード", command=self._copy_clipboard,
bg="#3c3c3c", fg="white", relief=tk.FLAT,
font=("Arial", 12), padx=16, pady=6,
activebackground="#505050", bd=0).pack(side=tk.LEFT, padx=6)
# QRプレビューキャンバス
self.canvas = tk.Canvas(self.root, bg="#2d2d2d", height=300,
highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)
self.canvas.create_text(
300, 150, text="「生成」ボタンを押してQRコードを作成", fill="#555",
font=("Arial", 11))
self.status_var = tk.StringVar(value="テキストを入力して生成してください")
tk.Label(self.root, textvariable=self.status_var,
bg="#252526", fg="#858585", font=("Arial", 9),
anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)
# Enterキーでも生成
self.root.bind("<Return>", lambda e: self._generate())
def _generate(self):
if not QRCODE_AVAILABLE or not PIL_AVAILABLE:
messagebox.showerror("エラー",
"pip install qrcode[pil] Pillow")
return
data = self.input_text.get("1.0", tk.END).strip()
if not data:
messagebox.showwarning("警告", "テキストを入力してください")
return
try:
ec = self.EC_LEVELS.get(self.ec_var.get(), ERROR_CORRECT_M)
qr = qrcode.QRCode(
error_correction=ec,
box_size=self.box_var.get(),
border=self.border_var.get())
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(
fill_color=self.fg_var.get(),
back_color=self.bg_var.get())
self._qr_img = img.get_image() if hasattr(img, "get_image") else img._img
except Exception as e:
messagebox.showerror("エラー", str(e))
return
self._show_qr()
size = self._qr_img.size
self.status_var.set(
f"生成完了: {size[0]}×{size[1]}px | バージョン: {qr.version} | "
f"文字数: {len(data)}")
def _show_qr(self):
if not self._qr_img:
return
self.canvas.update_idletasks()
cw = max(100, self.canvas.winfo_width())
ch = max(100, self.canvas.winfo_height())
img = self._qr_img.copy()
img.thumbnail((cw - 20, ch - 20), Image.LANCZOS if hasattr(Image, "LANCZOS") else Image.ANTIALIAS)
self._tk_img = ImageTk.PhotoImage(img)
self.canvas.delete("all")
self.canvas.create_image(cw // 2, ch // 2, image=self._tk_img, anchor="center")
def _save(self):
if not self._qr_img:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
path = filedialog.asksaveasfilename(
initialfile="qrcode.png",
defaultextension=".png",
filetypes=[("PNG", "*.png"), ("すべて", "*.*")])
if not path:
return
try:
self._qr_img.save(path)
self.status_var.set(f"保存完了: {path}")
except Exception as e:
messagebox.showerror("エラー", str(e))
def _copy_clipboard(self):
if not self._qr_img or not PIL_AVAILABLE:
messagebox.showwarning("警告", "先にQRコードを生成してください")
return
try:
# BMP経由でクリップボードにコピー (Windows専用)
import subprocess, sys
if sys.platform != "win32":
messagebox.showinfo("情報", "クリップボードコピーはWindows専用です")
return
buf = io.BytesIO()
self._qr_img.convert("RGB").save(buf, format="BMP")
data = buf.getvalue()[14:] # BMP file header を除去
import ctypes
ctypes.windll.user32.OpenClipboard(0)
ctypes.windll.user32.EmptyClipboard()
h = ctypes.windll.kernel32.GlobalAlloc(0x0042, len(data))
p = ctypes.windll.kernel32.GlobalLock(h)
ctypes.memmove(p, data, len(data))
ctypes.windll.kernel32.GlobalUnlock(h)
ctypes.windll.user32.SetClipboardData(8, h) # CF_DIB = 8
ctypes.windll.user32.CloseClipboard()
self.status_var.set("クリップボードにコピーしました")
except Exception as e:
messagebox.showerror("エラー", str(e))
if __name__ == "__main__":
root = tk.Tk()
app = App073(root)
root.mainloop()
6. ステップバイステップガイド
このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。
-
1ファイルを作成する
新しいファイルを作成して app073.py と保存します。
-
2クラスの骨格を作る
App073クラスを定義し、__init__とmainloop()の最小構成を作ります。
-
3タイトルバーを作る
Frameを使ってカラーバー付きのタイトルエリアを作ります。
-
4入力フォームを実装する
LabelFrameとEntryウィジェットで入力エリアを作ります。
-
5処理ロジックを実装する
_execute()メソッドにメインロジックを実装します。
-
6結果表示を実装する
TextウィジェットかLabelに結果を表示する_show_result()を実装します。
-
7エラー処理を追加する
try-exceptとmessageboxでエラーハンドリングを追加します。
7. カスタマイズアイデア
基本機能を習得したら、以下のカスタマイズに挑戦してみましょう。
💡 ダークモードを追加する
bg色・fg色を辞書で管理し、ボタン1つでダークモード・ライトモードを切り替えられるようにしましょう。
💡 データの保存機能
処理結果をCSV・TXTファイルに保存する機能を追加しましょう。filedialog.asksaveasfilename()でファイル保存ダイアログが使えます。
💡 設定ダイアログ
フォントサイズや色などの設定をユーザーが変更できるオプションダイアログを追加しましょう。
8. よくある問題と解決法
❌ 日本語フォントが表示されない
原因:システムに日本語フォントが見つからない場合があります。
解決法:font引数を省略するかシステムに合ったフォントを指定してください。
❌ ライブラリのインポートエラー
原因:必要なライブラリがインストールされていません。
解決法:pip install コマンドで必要なライブラリをインストールしてください。 (pip install pillow)
❌ ウィンドウサイズが合わない
原因:画面解像度や表示スケールによって異なる場合があります。
解決法:root.geometry()で適切なサイズに調整してください。
9. 練習問題
アプリの理解を深めるための練習問題です。
-
課題1:機能拡張
QRコード生成器に新しい機能を1つ追加してみましょう。
-
課題2:UIの改善
色・フォント・レイアウトを変更して、より使いやすいUIにカスタマイズしましょう。
-
課題3:保存機能の追加
処理結果をファイルに保存する機能を追加しましょう。