フォントビューアー
インストール済みフォントを一覧表示してプレビューできるアプリ。tkinter.fontモジュールを学びます。
1. アプリ概要
インストール済みフォントを一覧表示してプレビューできるアプリ。tkinter.fontモジュールを学びます。
このアプリはツールカテゴリに分類される実践的なGUIアプリです。使用ライブラリは tkinter(標準ライブラリ) で、難易度は ★★☆ です。
Pythonでは tkinter を使うことで、クロスプラットフォームなGUIアプリを簡単に作成できます。このアプリを通じて、ウィジェットの配置・イベント処理・データ管理など、GUI開発の実践的なスキルを習得できます。
ソースコードは完全な動作状態で提供しており、コピーしてそのまま実行できます。まずは実行して動作を確認し、その後コードを読んで仕組みを理解していきましょう。カスタマイズセクションでは機能拡張のアイデアも紹介しています。
GUIアプリ開発は、プログラミングの楽しさを実感できる最も効果的な学習方法のひとつです。アプリを作ることで、変数・関数・クラス・イベント処理など、プログラミングの重要な概念が自然と身についていきます。このアプリをきっかけに、オリジナルアプリの開発にも挑戦してみてください。
2. 機能一覧
- フォントビューアーのメイン機能
- 直感的なGUIインターフェース
- 入力値のバリデーション
- エラーハンドリング
- 結果の見やすい表示
- キーボードショートカット対応
3. 事前準備・環境
Python 3.10 以上 / Windows・Mac・Linux すべて対応
以下の環境で動作確認しています。
- Python 3.10 以上
- OS: Windows 10/11・macOS 12+・Ubuntu 20.04+
4. 完全なソースコード
右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。
import tkinter as tk
from tkinter import ttk, font as tkfont
class App37:
"""フォントビューアー"""
def __init__(self, root):
self.root = root
self.root.title("フォントビューアー")
self.root.geometry("560x480")
self.root.configure(bg="#f8f9fc")
self._build_ui()
def _build_ui(self):
title_frame = tk.Frame(self.root, bg="#3776ab", pady=12)
title_frame.pack(fill=tk.X)
tk.Label(title_frame, text="フォントビューアー",
font=("Noto Sans JP", 16, "bold"),
bg="#3776ab", fg="white").pack()
main_frame = tk.Frame(self.root, bg="#f8f9fc", padx=16, pady=12)
main_frame.pack(fill=tk.BOTH, expand=True)
paned = tk.PanedWindow(main_frame, orient=tk.HORIZONTAL,
bg="#ddd", sashwidth=4)
paned.pack(fill=tk.BOTH, expand=True)
# 左: フォント一覧
left_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(left_frame, width=200)
tk.Label(left_frame, text="フォント一覧", bg="#f8f9fc",
font=("Noto Sans JP", 11, "bold")).pack(anchor="w", pady=(0, 4))
self.search_var = tk.StringVar()
search_entry = ttk.Entry(left_frame, textvariable=self.search_var, font=("Arial", 11))
search_entry.pack(fill=tk.X, pady=(0, 4))
self.search_var.trace("w", self._filter_fonts)
list_f = tk.Frame(left_frame, bg="#f8f9fc")
list_f.pack(fill=tk.BOTH, expand=True)
self.font_listbox = tk.Listbox(list_f, font=("Arial", 10),
bg="white", relief=tk.FLAT, exportselection=False)
self.font_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
sb = ttk.Scrollbar(list_f, command=self.font_listbox.yview)
self.font_listbox.configure(yscrollcommand=sb.set)
sb.pack(side=tk.RIGHT, fill=tk.Y)
self.font_listbox.bind("<<ListboxSelect>>", self._on_select)
# 右: プレビュー
right_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(right_frame)
ctrl_f = tk.Frame(right_frame, bg="#f8f9fc")
ctrl_f.pack(fill=tk.X, pady=(0, 8))
tk.Label(ctrl_f, text="サイズ:", bg="#f8f9fc",
font=("Noto Sans JP", 10)).pack(side=tk.LEFT)
self.size_var = tk.IntVar(value=18)
ttk.Spinbox(ctrl_f, from_=8, to=72, textvariable=self.size_var,
width=5, font=("Arial", 11),
command=self._update_preview).pack(side=tk.LEFT, padx=4)
self.bold_var = tk.BooleanVar()
self.italic_var = tk.BooleanVar()
ttk.Checkbutton(ctrl_f, text="Bold", variable=self.bold_var,
command=self._update_preview).pack(side=tk.LEFT, padx=4)
ttk.Checkbutton(ctrl_f, text="Italic", variable=self.italic_var,
command=self._update_preview).pack(side=tk.LEFT)
self.selected_label = tk.Label(right_frame, text="フォントを選択してください",
bg="#f8f9fc", font=("Noto Sans JP", 10), fg="#888")
self.selected_label.pack(anchor="w")
preview_f = tk.Frame(right_frame, bg="white", relief=tk.SOLID, bd=1)
preview_f.pack(fill=tk.BOTH, expand=True, pady=(4, 0))
self.preview_text = tk.Text(preview_f, bg="white", relief=tk.FLAT,
padx=10, pady=10, wrap=tk.WORD)
self.preview_text.pack(fill=tk.BOTH, expand=True)
self.preview_text.insert("1.0",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n"
"0123456789 !@#$%^&*()\n"
"あいうえおかきくけこさしすせそ\n"
"The quick brown fox jumps over the lazy dog.")
# フォント読み込み
self.all_fonts = sorted(tkfont.families())
self._populate_fonts(self.all_fonts)
def _populate_fonts(self, fonts):
self.font_listbox.delete(0, tk.END)
for f in fonts:
self.font_listbox.insert(tk.END, f)
def _filter_fonts(self, *args):
query = self.search_var.get().lower()
filtered = [f for f in self.all_fonts if query in f.lower()]
self._populate_fonts(filtered)
def _on_select(self, event=None):
sel = self.font_listbox.curselection()
if not sel:
return
self.current_font = self.font_listbox.get(sel[0])
self.selected_label.config(text=f"選択中: {self.current_font}")
self._update_preview()
def _update_preview(self):
if not hasattr(self, "current_font"):
return
size = self.size_var.get()
weight = "bold" if self.bold_var.get() else "normal"
slant = "italic" if self.italic_var.get() else "roman"
try:
f = tkfont.Font(family=self.current_font, size=size, weight=weight, slant=slant)
self.preview_text.configure(font=f)
except Exception:
pass
if __name__ == "__main__":
root = tk.Tk()
app = App37(root)
root.mainloop()
5. コード解説
フォントビューアーのコードを詳しく解説します。クラスベースの設計で各機能を整理して実装しています。
クラス設計とコンストラクタ
App37クラスにアプリの全機能をまとめています。__init__メソッドでウィンドウの基本設定を行い、_build_ui()でUI構築、process()でメイン処理を担当します。この分離により、各メソッドの責任が明確になりコードが読みやすくなります。
import tkinter as tk
from tkinter import ttk, font as tkfont
class App37:
"""フォントビューアー"""
def __init__(self, root):
self.root = root
self.root.title("フォントビューアー")
self.root.geometry("560x480")
self.root.configure(bg="#f8f9fc")
self._build_ui()
def _build_ui(self):
title_frame = tk.Frame(self.root, bg="#3776ab", pady=12)
title_frame.pack(fill=tk.X)
tk.Label(title_frame, text="フォントビューアー",
font=("Noto Sans JP", 16, "bold"),
bg="#3776ab", fg="white").pack()
main_frame = tk.Frame(self.root, bg="#f8f9fc", padx=16, pady=12)
main_frame.pack(fill=tk.BOTH, expand=True)
paned = tk.PanedWindow(main_frame, orient=tk.HORIZONTAL,
bg="#ddd", sashwidth=4)
paned.pack(fill=tk.BOTH, expand=True)
# 左: フォント一覧
left_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(left_frame, width=200)
tk.Label(left_frame, text="フォント一覧", bg="#f8f9fc",
font=("Noto Sans JP", 11, "bold")).pack(anchor="w", pady=(0, 4))
self.search_var = tk.StringVar()
search_entry = ttk.Entry(left_frame, textvariable=self.search_var, font=("Arial", 11))
search_entry.pack(fill=tk.X, pady=(0, 4))
self.search_var.trace("w", self._filter_fonts)
list_f = tk.Frame(left_frame, bg="#f8f9fc")
list_f.pack(fill=tk.BOTH, expand=True)
self.font_listbox = tk.Listbox(list_f, font=("Arial", 10),
bg="white", relief=tk.FLAT, exportselection=False)
self.font_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
sb = ttk.Scrollbar(list_f, command=self.font_listbox.yview)
self.font_listbox.configure(yscrollcommand=sb.set)
sb.pack(side=tk.RIGHT, fill=tk.Y)
self.font_listbox.bind("<<ListboxSelect>>", self._on_select)
# 右: プレビュー
right_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(right_frame)
ctrl_f = tk.Frame(right_frame, bg="#f8f9fc")
ctrl_f.pack(fill=tk.X, pady=(0, 8))
tk.Label(ctrl_f, text="サイズ:", bg="#f8f9fc",
font=("Noto Sans JP", 10)).pack(side=tk.LEFT)
self.size_var = tk.IntVar(value=18)
ttk.Spinbox(ctrl_f, from_=8, to=72, textvariable=self.size_var,
width=5, font=("Arial", 11),
command=self._update_preview).pack(side=tk.LEFT, padx=4)
self.bold_var = tk.BooleanVar()
self.italic_var = tk.BooleanVar()
ttk.Checkbutton(ctrl_f, text="Bold", variable=self.bold_var,
command=self._update_preview).pack(side=tk.LEFT, padx=4)
ttk.Checkbutton(ctrl_f, text="Italic", variable=self.italic_var,
command=self._update_preview).pack(side=tk.LEFT)
self.selected_label = tk.Label(right_frame, text="フォントを選択してください",
bg="#f8f9fc", font=("Noto Sans JP", 10), fg="#888")
self.selected_label.pack(anchor="w")
preview_f = tk.Frame(right_frame, bg="white", relief=tk.SOLID, bd=1)
preview_f.pack(fill=tk.BOTH, expand=True, pady=(4, 0))
self.preview_text = tk.Text(preview_f, bg="white", relief=tk.FLAT,
padx=10, pady=10, wrap=tk.WORD)
self.preview_text.pack(fill=tk.BOTH, expand=True)
self.preview_text.insert("1.0",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n"
"0123456789 !@#$%^&*()\n"
"あいうえおかきくけこさしすせそ\n"
"The quick brown fox jumps over the lazy dog.")
# フォント読み込み
self.all_fonts = sorted(tkfont.families())
self._populate_fonts(self.all_fonts)
def _populate_fonts(self, fonts):
self.font_listbox.delete(0, tk.END)
for f in fonts:
self.font_listbox.insert(tk.END, f)
def _filter_fonts(self, *args):
query = self.search_var.get().lower()
filtered = [f for f in self.all_fonts if query in f.lower()]
self._populate_fonts(filtered)
def _on_select(self, event=None):
sel = self.font_listbox.curselection()
if not sel:
return
self.current_font = self.font_listbox.get(sel[0])
self.selected_label.config(text=f"選択中: {self.current_font}")
self._update_preview()
def _update_preview(self):
if not hasattr(self, "current_font"):
return
size = self.size_var.get()
weight = "bold" if self.bold_var.get() else "normal"
slant = "italic" if self.italic_var.get() else "roman"
try:
f = tkfont.Font(family=self.current_font, size=size, weight=weight, slant=slant)
self.preview_text.configure(font=f)
except Exception:
pass
if __name__ == "__main__":
root = tk.Tk()
app = App37(root)
root.mainloop()
LabelFrameによるセクション分け
ttk.LabelFrame を使うことで、入力エリアと結果エリアを視覚的に分けられます。padding引数でフレーム内の余白を設定し、見やすいレイアウトを実現しています。
import tkinter as tk
from tkinter import ttk, font as tkfont
class App37:
"""フォントビューアー"""
def __init__(self, root):
self.root = root
self.root.title("フォントビューアー")
self.root.geometry("560x480")
self.root.configure(bg="#f8f9fc")
self._build_ui()
def _build_ui(self):
title_frame = tk.Frame(self.root, bg="#3776ab", pady=12)
title_frame.pack(fill=tk.X)
tk.Label(title_frame, text="フォントビューアー",
font=("Noto Sans JP", 16, "bold"),
bg="#3776ab", fg="white").pack()
main_frame = tk.Frame(self.root, bg="#f8f9fc", padx=16, pady=12)
main_frame.pack(fill=tk.BOTH, expand=True)
paned = tk.PanedWindow(main_frame, orient=tk.HORIZONTAL,
bg="#ddd", sashwidth=4)
paned.pack(fill=tk.BOTH, expand=True)
# 左: フォント一覧
left_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(left_frame, width=200)
tk.Label(left_frame, text="フォント一覧", bg="#f8f9fc",
font=("Noto Sans JP", 11, "bold")).pack(anchor="w", pady=(0, 4))
self.search_var = tk.StringVar()
search_entry = ttk.Entry(left_frame, textvariable=self.search_var, font=("Arial", 11))
search_entry.pack(fill=tk.X, pady=(0, 4))
self.search_var.trace("w", self._filter_fonts)
list_f = tk.Frame(left_frame, bg="#f8f9fc")
list_f.pack(fill=tk.BOTH, expand=True)
self.font_listbox = tk.Listbox(list_f, font=("Arial", 10),
bg="white", relief=tk.FLAT, exportselection=False)
self.font_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
sb = ttk.Scrollbar(list_f, command=self.font_listbox.yview)
self.font_listbox.configure(yscrollcommand=sb.set)
sb.pack(side=tk.RIGHT, fill=tk.Y)
self.font_listbox.bind("<<ListboxSelect>>", self._on_select)
# 右: プレビュー
right_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(right_frame)
ctrl_f = tk.Frame(right_frame, bg="#f8f9fc")
ctrl_f.pack(fill=tk.X, pady=(0, 8))
tk.Label(ctrl_f, text="サイズ:", bg="#f8f9fc",
font=("Noto Sans JP", 10)).pack(side=tk.LEFT)
self.size_var = tk.IntVar(value=18)
ttk.Spinbox(ctrl_f, from_=8, to=72, textvariable=self.size_var,
width=5, font=("Arial", 11),
command=self._update_preview).pack(side=tk.LEFT, padx=4)
self.bold_var = tk.BooleanVar()
self.italic_var = tk.BooleanVar()
ttk.Checkbutton(ctrl_f, text="Bold", variable=self.bold_var,
command=self._update_preview).pack(side=tk.LEFT, padx=4)
ttk.Checkbutton(ctrl_f, text="Italic", variable=self.italic_var,
command=self._update_preview).pack(side=tk.LEFT)
self.selected_label = tk.Label(right_frame, text="フォントを選択してください",
bg="#f8f9fc", font=("Noto Sans JP", 10), fg="#888")
self.selected_label.pack(anchor="w")
preview_f = tk.Frame(right_frame, bg="white", relief=tk.SOLID, bd=1)
preview_f.pack(fill=tk.BOTH, expand=True, pady=(4, 0))
self.preview_text = tk.Text(preview_f, bg="white", relief=tk.FLAT,
padx=10, pady=10, wrap=tk.WORD)
self.preview_text.pack(fill=tk.BOTH, expand=True)
self.preview_text.insert("1.0",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n"
"0123456789 !@#$%^&*()\n"
"あいうえおかきくけこさしすせそ\n"
"The quick brown fox jumps over the lazy dog.")
# フォント読み込み
self.all_fonts = sorted(tkfont.families())
self._populate_fonts(self.all_fonts)
def _populate_fonts(self, fonts):
self.font_listbox.delete(0, tk.END)
for f in fonts:
self.font_listbox.insert(tk.END, f)
def _filter_fonts(self, *args):
query = self.search_var.get().lower()
filtered = [f for f in self.all_fonts if query in f.lower()]
self._populate_fonts(filtered)
def _on_select(self, event=None):
sel = self.font_listbox.curselection()
if not sel:
return
self.current_font = self.font_listbox.get(sel[0])
self.selected_label.config(text=f"選択中: {self.current_font}")
self._update_preview()
def _update_preview(self):
if not hasattr(self, "current_font"):
return
size = self.size_var.get()
weight = "bold" if self.bold_var.get() else "normal"
slant = "italic" if self.italic_var.get() else "roman"
try:
f = tkfont.Font(family=self.current_font, size=size, weight=weight, slant=slant)
self.preview_text.configure(font=f)
except Exception:
pass
if __name__ == "__main__":
root = tk.Tk()
app = App37(root)
root.mainloop()
Entryウィジェットとイベントバインド
ttk.Entryで入力フィールドを作成します。bind('
import tkinter as tk
from tkinter import ttk, font as tkfont
class App37:
"""フォントビューアー"""
def __init__(self, root):
self.root = root
self.root.title("フォントビューアー")
self.root.geometry("560x480")
self.root.configure(bg="#f8f9fc")
self._build_ui()
def _build_ui(self):
title_frame = tk.Frame(self.root, bg="#3776ab", pady=12)
title_frame.pack(fill=tk.X)
tk.Label(title_frame, text="フォントビューアー",
font=("Noto Sans JP", 16, "bold"),
bg="#3776ab", fg="white").pack()
main_frame = tk.Frame(self.root, bg="#f8f9fc", padx=16, pady=12)
main_frame.pack(fill=tk.BOTH, expand=True)
paned = tk.PanedWindow(main_frame, orient=tk.HORIZONTAL,
bg="#ddd", sashwidth=4)
paned.pack(fill=tk.BOTH, expand=True)
# 左: フォント一覧
left_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(left_frame, width=200)
tk.Label(left_frame, text="フォント一覧", bg="#f8f9fc",
font=("Noto Sans JP", 11, "bold")).pack(anchor="w", pady=(0, 4))
self.search_var = tk.StringVar()
search_entry = ttk.Entry(left_frame, textvariable=self.search_var, font=("Arial", 11))
search_entry.pack(fill=tk.X, pady=(0, 4))
self.search_var.trace("w", self._filter_fonts)
list_f = tk.Frame(left_frame, bg="#f8f9fc")
list_f.pack(fill=tk.BOTH, expand=True)
self.font_listbox = tk.Listbox(list_f, font=("Arial", 10),
bg="white", relief=tk.FLAT, exportselection=False)
self.font_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
sb = ttk.Scrollbar(list_f, command=self.font_listbox.yview)
self.font_listbox.configure(yscrollcommand=sb.set)
sb.pack(side=tk.RIGHT, fill=tk.Y)
self.font_listbox.bind("<<ListboxSelect>>", self._on_select)
# 右: プレビュー
right_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(right_frame)
ctrl_f = tk.Frame(right_frame, bg="#f8f9fc")
ctrl_f.pack(fill=tk.X, pady=(0, 8))
tk.Label(ctrl_f, text="サイズ:", bg="#f8f9fc",
font=("Noto Sans JP", 10)).pack(side=tk.LEFT)
self.size_var = tk.IntVar(value=18)
ttk.Spinbox(ctrl_f, from_=8, to=72, textvariable=self.size_var,
width=5, font=("Arial", 11),
command=self._update_preview).pack(side=tk.LEFT, padx=4)
self.bold_var = tk.BooleanVar()
self.italic_var = tk.BooleanVar()
ttk.Checkbutton(ctrl_f, text="Bold", variable=self.bold_var,
command=self._update_preview).pack(side=tk.LEFT, padx=4)
ttk.Checkbutton(ctrl_f, text="Italic", variable=self.italic_var,
command=self._update_preview).pack(side=tk.LEFT)
self.selected_label = tk.Label(right_frame, text="フォントを選択してください",
bg="#f8f9fc", font=("Noto Sans JP", 10), fg="#888")
self.selected_label.pack(anchor="w")
preview_f = tk.Frame(right_frame, bg="white", relief=tk.SOLID, bd=1)
preview_f.pack(fill=tk.BOTH, expand=True, pady=(4, 0))
self.preview_text = tk.Text(preview_f, bg="white", relief=tk.FLAT,
padx=10, pady=10, wrap=tk.WORD)
self.preview_text.pack(fill=tk.BOTH, expand=True)
self.preview_text.insert("1.0",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n"
"0123456789 !@#$%^&*()\n"
"あいうえおかきくけこさしすせそ\n"
"The quick brown fox jumps over the lazy dog.")
# フォント読み込み
self.all_fonts = sorted(tkfont.families())
self._populate_fonts(self.all_fonts)
def _populate_fonts(self, fonts):
self.font_listbox.delete(0, tk.END)
for f in fonts:
self.font_listbox.insert(tk.END, f)
def _filter_fonts(self, *args):
query = self.search_var.get().lower()
filtered = [f for f in self.all_fonts if query in f.lower()]
self._populate_fonts(filtered)
def _on_select(self, event=None):
sel = self.font_listbox.curselection()
if not sel:
return
self.current_font = self.font_listbox.get(sel[0])
self.selected_label.config(text=f"選択中: {self.current_font}")
self._update_preview()
def _update_preview(self):
if not hasattr(self, "current_font"):
return
size = self.size_var.get()
weight = "bold" if self.bold_var.get() else "normal"
slant = "italic" if self.italic_var.get() else "roman"
try:
f = tkfont.Font(family=self.current_font, size=size, weight=weight, slant=slant)
self.preview_text.configure(font=f)
except Exception:
pass
if __name__ == "__main__":
root = tk.Tk()
app = App37(root)
root.mainloop()
Textウィジェットでの結果表示
結果表示にはtk.Textウィジェットを使います。state=tk.DISABLEDでユーザーが直接編集できないようにし、表示前にNORMALに切り替えてからinsert()で内容を更新します。
import tkinter as tk
from tkinter import ttk, font as tkfont
class App37:
"""フォントビューアー"""
def __init__(self, root):
self.root = root
self.root.title("フォントビューアー")
self.root.geometry("560x480")
self.root.configure(bg="#f8f9fc")
self._build_ui()
def _build_ui(self):
title_frame = tk.Frame(self.root, bg="#3776ab", pady=12)
title_frame.pack(fill=tk.X)
tk.Label(title_frame, text="フォントビューアー",
font=("Noto Sans JP", 16, "bold"),
bg="#3776ab", fg="white").pack()
main_frame = tk.Frame(self.root, bg="#f8f9fc", padx=16, pady=12)
main_frame.pack(fill=tk.BOTH, expand=True)
paned = tk.PanedWindow(main_frame, orient=tk.HORIZONTAL,
bg="#ddd", sashwidth=4)
paned.pack(fill=tk.BOTH, expand=True)
# 左: フォント一覧
left_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(left_frame, width=200)
tk.Label(left_frame, text="フォント一覧", bg="#f8f9fc",
font=("Noto Sans JP", 11, "bold")).pack(anchor="w", pady=(0, 4))
self.search_var = tk.StringVar()
search_entry = ttk.Entry(left_frame, textvariable=self.search_var, font=("Arial", 11))
search_entry.pack(fill=tk.X, pady=(0, 4))
self.search_var.trace("w", self._filter_fonts)
list_f = tk.Frame(left_frame, bg="#f8f9fc")
list_f.pack(fill=tk.BOTH, expand=True)
self.font_listbox = tk.Listbox(list_f, font=("Arial", 10),
bg="white", relief=tk.FLAT, exportselection=False)
self.font_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
sb = ttk.Scrollbar(list_f, command=self.font_listbox.yview)
self.font_listbox.configure(yscrollcommand=sb.set)
sb.pack(side=tk.RIGHT, fill=tk.Y)
self.font_listbox.bind("<<ListboxSelect>>", self._on_select)
# 右: プレビュー
right_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(right_frame)
ctrl_f = tk.Frame(right_frame, bg="#f8f9fc")
ctrl_f.pack(fill=tk.X, pady=(0, 8))
tk.Label(ctrl_f, text="サイズ:", bg="#f8f9fc",
font=("Noto Sans JP", 10)).pack(side=tk.LEFT)
self.size_var = tk.IntVar(value=18)
ttk.Spinbox(ctrl_f, from_=8, to=72, textvariable=self.size_var,
width=5, font=("Arial", 11),
command=self._update_preview).pack(side=tk.LEFT, padx=4)
self.bold_var = tk.BooleanVar()
self.italic_var = tk.BooleanVar()
ttk.Checkbutton(ctrl_f, text="Bold", variable=self.bold_var,
command=self._update_preview).pack(side=tk.LEFT, padx=4)
ttk.Checkbutton(ctrl_f, text="Italic", variable=self.italic_var,
command=self._update_preview).pack(side=tk.LEFT)
self.selected_label = tk.Label(right_frame, text="フォントを選択してください",
bg="#f8f9fc", font=("Noto Sans JP", 10), fg="#888")
self.selected_label.pack(anchor="w")
preview_f = tk.Frame(right_frame, bg="white", relief=tk.SOLID, bd=1)
preview_f.pack(fill=tk.BOTH, expand=True, pady=(4, 0))
self.preview_text = tk.Text(preview_f, bg="white", relief=tk.FLAT,
padx=10, pady=10, wrap=tk.WORD)
self.preview_text.pack(fill=tk.BOTH, expand=True)
self.preview_text.insert("1.0",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n"
"0123456789 !@#$%^&*()\n"
"あいうえおかきくけこさしすせそ\n"
"The quick brown fox jumps over the lazy dog.")
# フォント読み込み
self.all_fonts = sorted(tkfont.families())
self._populate_fonts(self.all_fonts)
def _populate_fonts(self, fonts):
self.font_listbox.delete(0, tk.END)
for f in fonts:
self.font_listbox.insert(tk.END, f)
def _filter_fonts(self, *args):
query = self.search_var.get().lower()
filtered = [f for f in self.all_fonts if query in f.lower()]
self._populate_fonts(filtered)
def _on_select(self, event=None):
sel = self.font_listbox.curselection()
if not sel:
return
self.current_font = self.font_listbox.get(sel[0])
self.selected_label.config(text=f"選択中: {self.current_font}")
self._update_preview()
def _update_preview(self):
if not hasattr(self, "current_font"):
return
size = self.size_var.get()
weight = "bold" if self.bold_var.get() else "normal"
slant = "italic" if self.italic_var.get() else "roman"
try:
f = tkfont.Font(family=self.current_font, size=size, weight=weight, slant=slant)
self.preview_text.configure(font=f)
except Exception:
pass
if __name__ == "__main__":
root = tk.Tk()
app = App37(root)
root.mainloop()
例外処理とmessagebox
try-except で ValueError と Exception を捕捉し、messagebox.showerror() でユーザーにわかりやすいエラーメッセージを表示します。入力バリデーションは必ず実装しましょう。
import tkinter as tk
from tkinter import ttk, font as tkfont
class App37:
"""フォントビューアー"""
def __init__(self, root):
self.root = root
self.root.title("フォントビューアー")
self.root.geometry("560x480")
self.root.configure(bg="#f8f9fc")
self._build_ui()
def _build_ui(self):
title_frame = tk.Frame(self.root, bg="#3776ab", pady=12)
title_frame.pack(fill=tk.X)
tk.Label(title_frame, text="フォントビューアー",
font=("Noto Sans JP", 16, "bold"),
bg="#3776ab", fg="white").pack()
main_frame = tk.Frame(self.root, bg="#f8f9fc", padx=16, pady=12)
main_frame.pack(fill=tk.BOTH, expand=True)
paned = tk.PanedWindow(main_frame, orient=tk.HORIZONTAL,
bg="#ddd", sashwidth=4)
paned.pack(fill=tk.BOTH, expand=True)
# 左: フォント一覧
left_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(left_frame, width=200)
tk.Label(left_frame, text="フォント一覧", bg="#f8f9fc",
font=("Noto Sans JP", 11, "bold")).pack(anchor="w", pady=(0, 4))
self.search_var = tk.StringVar()
search_entry = ttk.Entry(left_frame, textvariable=self.search_var, font=("Arial", 11))
search_entry.pack(fill=tk.X, pady=(0, 4))
self.search_var.trace("w", self._filter_fonts)
list_f = tk.Frame(left_frame, bg="#f8f9fc")
list_f.pack(fill=tk.BOTH, expand=True)
self.font_listbox = tk.Listbox(list_f, font=("Arial", 10),
bg="white", relief=tk.FLAT, exportselection=False)
self.font_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
sb = ttk.Scrollbar(list_f, command=self.font_listbox.yview)
self.font_listbox.configure(yscrollcommand=sb.set)
sb.pack(side=tk.RIGHT, fill=tk.Y)
self.font_listbox.bind("<<ListboxSelect>>", self._on_select)
# 右: プレビュー
right_frame = tk.Frame(paned, bg="#f8f9fc")
paned.add(right_frame)
ctrl_f = tk.Frame(right_frame, bg="#f8f9fc")
ctrl_f.pack(fill=tk.X, pady=(0, 8))
tk.Label(ctrl_f, text="サイズ:", bg="#f8f9fc",
font=("Noto Sans JP", 10)).pack(side=tk.LEFT)
self.size_var = tk.IntVar(value=18)
ttk.Spinbox(ctrl_f, from_=8, to=72, textvariable=self.size_var,
width=5, font=("Arial", 11),
command=self._update_preview).pack(side=tk.LEFT, padx=4)
self.bold_var = tk.BooleanVar()
self.italic_var = tk.BooleanVar()
ttk.Checkbutton(ctrl_f, text="Bold", variable=self.bold_var,
command=self._update_preview).pack(side=tk.LEFT, padx=4)
ttk.Checkbutton(ctrl_f, text="Italic", variable=self.italic_var,
command=self._update_preview).pack(side=tk.LEFT)
self.selected_label = tk.Label(right_frame, text="フォントを選択してください",
bg="#f8f9fc", font=("Noto Sans JP", 10), fg="#888")
self.selected_label.pack(anchor="w")
preview_f = tk.Frame(right_frame, bg="white", relief=tk.SOLID, bd=1)
preview_f.pack(fill=tk.BOTH, expand=True, pady=(4, 0))
self.preview_text = tk.Text(preview_f, bg="white", relief=tk.FLAT,
padx=10, pady=10, wrap=tk.WORD)
self.preview_text.pack(fill=tk.BOTH, expand=True)
self.preview_text.insert("1.0",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n"
"0123456789 !@#$%^&*()\n"
"あいうえおかきくけこさしすせそ\n"
"The quick brown fox jumps over the lazy dog.")
# フォント読み込み
self.all_fonts = sorted(tkfont.families())
self._populate_fonts(self.all_fonts)
def _populate_fonts(self, fonts):
self.font_listbox.delete(0, tk.END)
for f in fonts:
self.font_listbox.insert(tk.END, f)
def _filter_fonts(self, *args):
query = self.search_var.get().lower()
filtered = [f for f in self.all_fonts if query in f.lower()]
self._populate_fonts(filtered)
def _on_select(self, event=None):
sel = self.font_listbox.curselection()
if not sel:
return
self.current_font = self.font_listbox.get(sel[0])
self.selected_label.config(text=f"選択中: {self.current_font}")
self._update_preview()
def _update_preview(self):
if not hasattr(self, "current_font"):
return
size = self.size_var.get()
weight = "bold" if self.bold_var.get() else "normal"
slant = "italic" if self.italic_var.get() else "roman"
try:
f = tkfont.Font(family=self.current_font, size=size, weight=weight, slant=slant)
self.preview_text.configure(font=f)
except Exception:
pass
if __name__ == "__main__":
root = tk.Tk()
app = App37(root)
root.mainloop()
6. ステップバイステップガイド
このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。
-
1ファイルを作成する
新しいファイルを作成して app37.py と保存します。
-
2クラスの骨格を作る
App37クラスを定義し、__init__とmainloop()の最小構成を作ります。
-
3タイトルバーを作る
Frameを使ってカラーバー付きのタイトルエリアを作ります。
-
4入力フォームを実装する
LabelFrameとEntryウィジェットで入力エリアを作ります。
-
5処理ロジックを実装する
_calculate()メソッドに計算・処理ロジックを実装します。
-
6結果表示を実装する
TextウィジェットかLabelに結果を表示する_show_result()を実装します。
-
7エラー処理を追加する
try-exceptとmessageboxでエラーハンドリングを追加します。
7. カスタマイズアイデア
基本機能を習得したら、以下のカスタマイズに挑戦してみましょう。少しずつ機能を追加することで、Pythonのスキルが飛躍的に向上します。
💡 ダークモードを追加する
bg色・fg色を辞書で管理し、ボタン1つでダークモード・ライトモードを切り替えられるようにしましょう。
💡 データのエクスポート機能
計算結果をCSV・TXTファイルに保存するエクスポート機能を追加しましょう。filedialog.asksaveasfilename()でファイル保存ダイアログが使えます。
💡 入力履歴機能
以前の入力値を覚えておいてComboboxのドロップダウンで再選択できる履歴機能を追加しましょう。
8. よくある問題と解決法
❌ 日本語フォントが表示されない
原因:システムに日本語フォントが見つからない場合があります。
解決法:font引数を省略するかシステムに合ったフォントを指定してください。
❌ ウィンドウのサイズが変更できない
原因:resizable(False, False)が設定されています。
解決法:resizable(True, True)に変更してください。
9. 練習問題
アプリの理解を深めるための練習問題です。難易度順に挑戦してみてください。
-
課題1:機能拡張
フォントビューアーに新しい機能を1つ追加してみましょう。どんな機能があると便利か考えてから実装してください。
-
課題2:UIの改善
色・フォント・レイアウトを変更して、より使いやすいUIにカスタマイズしてみましょう。
-
課題3:保存機能の追加
入力値や計算結果をファイルに保存する機能を追加しましょう。jsonやcsvモジュールを使います。