Tkinter
Python標準ライブラリに含まれるGUIツールキット。Tcl/Tkをベースとし、追加インストール不要で即座に利用可能。シンプルなアプリケーションやプロトタイピングに最適で、学習コストが低い。
フレームワーク
Tkinter
概要
TkinterはPythonの標準ライブラリに含まれる軽量なGUIフレームワークです。追加インストール不要で、シンプルなデスクトップアプリケーション開発が可能です。学習向けやプロトタイプ作成、軽量なツール開発に適しており、Pythonプログラマーのデスクトップアプリ入門として広く利用されています。
詳細
Tkinterは2025年において、Pythonデスクトップ開発の「最初の選択肢」として確固たる地位を占めています。Pythonの標準ライブラリの一部として20年以上の実績があり、安定性と互換性において他のGUIフレームワークに勝る優位性を持っています。
最大の特徴は追加インストールが不要であることです。Pythonがインストールされていれば即座にGUIアプリケーション開発が可能で、初学者にとって最もアクセスしやすいGUIフレームワークです。Tcl/Tkをベースとしており、軽量でメモリ効率が良く、シンプルなアプリケーションには十分な機能を提供します。
教育分野、プロトタイプ開発、社内ツール、データ分析用GUI、設定画面などで広く採用されています。IDLEエディタやpip-GUI、多くのPython学習教材のサンプルアプリケーションでTkinterが使用されており、Python開発者のデスクトップアプリケーション開発の基礎となっています。
メリット・デメリット
メリット
- 標準ライブラリ: 追加インストール不要で即座に利用可能
- 軽量: 最小限のリソース消費でシンプルなGUI作成
- 学習コスト低: 基本的なGUI作成に必要な概念が少ない
- 安定性: 20年以上の実績と高い互換性
- クロスプラットフォーム: Windows、macOS、Linux対応
- 豊富な情報: 多数のチュートリアルと学習リソース
- プロトタイピング: 迅速なUI試作に適している
- 依存関係なし: Pythonのみで完結する開発環境
デメリット
- 見た目の制約: モダンなUIデザインには不向き
- ウィジェット数: 基本的な部品のみで高機能ウィジェットが少ない
- カスタマイズ性: 外観のカスタマイズが限定的
- レスポンシブ性: 複雑なレイアウトには不向き
- アニメーション: 動的なUI効果の実装が困難
- スケーラビリティ: 大規模アプリケーションには不適
主要リンク
- Tkinter公式ドキュメント
- Tkinter公式チュートリアル
- Tk/Tcl公式サイト
- tkdocs.com(モダンTkinter学習)
- Tkinter 8.5 リファレンス
- Python.jp Tkinterガイド
書き方の例
Hello Worldアプリケーション
# hello_world.py
import tkinter as tk
from tkinter import ttk
class HelloWorldApp:
def __init__(self, root):
self.root = root
self.root.title("Tkinter Hello World")
self.root.geometry("300x150")
# メインフレーム
self.main_frame = ttk.Frame(root, padding="10")
self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# ラベル
self.label = ttk.Label(self.main_frame, text="Hello, Tkinter!")
self.label.grid(row=0, column=0, columnspan=2, pady=10)
# ボタン
self.button = ttk.Button(
self.main_frame,
text="クリックしてください",
command=self.on_button_click
)
self.button.grid(row=1, column=0, columnspan=2, pady=5)
# カウンター表示
self.counter_label = ttk.Label(self.main_frame, text="クリック回数: 0")
self.counter_label.grid(row=2, column=0, columnspan=2, pady=5)
# ウィンドウのリサイズ設定
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
self.main_frame.columnconfigure(0, weight=1)
self.click_count = 0
def on_button_click(self):
self.click_count += 1
self.counter_label.config(text=f"クリック回数: {self.click_count}")
if self.click_count == 1:
self.label.config(text="ボタンがクリックされました!")
else:
self.label.config(text=f"{self.click_count}回クリックされました!")
if __name__ == "__main__":
root = tk.Tk()
app = HelloWorldApp(root)
root.mainloop()
ウィジェットの基本使用法
# basic_widgets.py
import tkinter as tk
from tkinter import ttk, messagebox
class BasicWidgetsApp:
def __init__(self, root):
self.root = root
self.root.title("基本ウィジェットの例")
self.root.geometry("400x500")
# スクロール可能なフレーム
self.setup_scrollable_frame()
self.create_widgets()
def setup_scrollable_frame(self):
# スクロールバー付きのフレーム
self.canvas = tk.Canvas(self.root)
self.scrollbar = ttk.Scrollbar(self.root, orient="vertical", command=self.canvas.yview)
self.scrollable_frame = ttk.Frame(self.canvas)
self.scrollable_frame.bind(
"<Configure>",
lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
)
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.pack(side="left", fill="both", expand=True)
self.scrollbar.pack(side="right", fill="y")
def create_widgets(self):
# タイトル
title = ttk.Label(self.scrollable_frame, text="基本ウィジェット集", font=("Arial", 16, "bold"))
title.pack(pady=10)
# 入力フィールド
self.create_entry_section()
# ボタン
self.create_button_section()
# チェックボックスとラジオボタン
self.create_checkbox_radio_section()
# リストボックスとコンボボックス
self.create_listbox_combobox_section()
# スケールとプログレスバー
self.create_scale_progress_section()
# テキストエリア
self.create_text_section()
def create_entry_section(self):
# エントリーフィールド
entry_frame = ttk.LabelFrame(self.scrollable_frame, text="テキスト入力", padding=10)
entry_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(entry_frame, text="名前:").grid(row=0, column=0, sticky="w")
self.name_entry = ttk.Entry(entry_frame, width=30)
self.name_entry.grid(row=0, column=1, padx=5, pady=2)
ttk.Label(entry_frame, text="パスワード:").grid(row=1, column=0, sticky="w")
self.password_entry = ttk.Entry(entry_frame, show="*", width=30)
self.password_entry.grid(row=1, column=1, padx=5, pady=2)
# エントリー内容表示ボタン
ttk.Button(
entry_frame,
text="入力内容表示",
command=self.show_entry_content
).grid(row=2, column=1, sticky="e", pady=5)
def create_button_section(self):
# ボタン
button_frame = ttk.LabelFrame(self.scrollable_frame, text="ボタン", padding=10)
button_frame.pack(fill="x", padx=10, pady=5)
button_row = ttk.Frame(button_frame)
button_row.pack(fill="x")
ttk.Button(button_row, text="情報", command=lambda: messagebox.showinfo("情報", "情報メッセージです")).pack(side="left", padx=2)
ttk.Button(button_row, text="警告", command=lambda: messagebox.showwarning("警告", "警告メッセージです")).pack(side="left", padx=2)
ttk.Button(button_row, text="エラー", command=lambda: messagebox.showerror("エラー", "エラーメッセージです")).pack(side="left", padx=2)
def create_checkbox_radio_section(self):
# チェックボックスとラジオボタン
check_radio_frame = ttk.LabelFrame(self.scrollable_frame, text="選択", padding=10)
check_radio_frame.pack(fill="x", padx=10, pady=5)
# チェックボックス
self.check_vars = {}
check_frame = ttk.Frame(check_radio_frame)
check_frame.pack(fill="x")
ttk.Label(check_frame, text="好きな言語:").pack(anchor="w")
for lang in ["Python", "JavaScript", "Java", "C++"]:
var = tk.BooleanVar()
self.check_vars[lang] = var
ttk.Checkbutton(check_frame, text=lang, variable=var).pack(anchor="w")
# ラジオボタン
radio_frame = ttk.Frame(check_radio_frame)
radio_frame.pack(fill="x", pady=(10, 0))
ttk.Label(radio_frame, text="経験レベル:").pack(anchor="w")
self.radio_var = tk.StringVar(value="初級")
for level in ["初級", "中級", "上級"]:
ttk.Radiobutton(radio_frame, text=level, variable=self.radio_var, value=level).pack(anchor="w")
# 選択内容表示ボタン
ttk.Button(
check_radio_frame,
text="選択内容表示",
command=self.show_selection
).pack(pady=5)
def create_listbox_combobox_section(self):
# リストボックスとコンボボックス
list_combo_frame = ttk.LabelFrame(self.scrollable_frame, text="リストとコンボ", padding=10)
list_combo_frame.pack(fill="x", padx=10, pady=5)
list_frame = ttk.Frame(list_combo_frame)
list_frame.pack(fill="x")
# リストボックス
ttk.Label(list_frame, text="都市選択:").pack(anchor="w")
self.listbox = tk.Listbox(list_frame, height=4)
cities = ["東京", "大阪", "名古屋", "福岡", "札幌", "仙台"]
for city in cities:
self.listbox.insert(tk.END, city)
self.listbox.pack(fill="x", pady=2)
# コンボボックス
ttk.Label(list_frame, text="国選択:").pack(anchor="w", pady=(10, 0))
self.combobox = ttk.Combobox(list_frame, values=["日本", "アメリカ", "イギリス", "ドイツ", "フランス"])
self.combobox.pack(fill="x", pady=2)
self.combobox.set("日本")
def create_scale_progress_section(self):
# スケールとプログレスバー
scale_progress_frame = ttk.LabelFrame(self.scrollable_frame, text="スケールとプログレス", padding=10)
scale_progress_frame.pack(fill="x", padx=10, pady=5)
# スケール
ttk.Label(scale_progress_frame, text="値設定:").pack(anchor="w")
self.scale_var = tk.DoubleVar()
self.scale = ttk.Scale(
scale_progress_frame,
from_=0,
to=100,
variable=self.scale_var,
command=self.on_scale_change
)
self.scale.pack(fill="x", pady=2)
self.scale_label = ttk.Label(scale_progress_frame, text="値: 0")
self.scale_label.pack(anchor="w")
# プログレスバー
ttk.Label(scale_progress_frame, text="プログレス:").pack(anchor="w", pady=(10, 0))
self.progress = ttk.Progressbar(scale_progress_frame, mode='determinate')
self.progress.pack(fill="x", pady=2)
ttk.Button(
scale_progress_frame,
text="プログレス開始",
command=self.start_progress
).pack(pady=5)
def create_text_section(self):
# テキストエリア
text_frame = ttk.LabelFrame(self.scrollable_frame, text="テキストエリア", padding=10)
text_frame.pack(fill="x", padx=10, pady=5)
self.text_area = tk.Text(text_frame, height=5, wrap=tk.WORD)
text_scrollbar = ttk.Scrollbar(text_frame, orient="vertical", command=self.text_area.yview)
self.text_area.configure(yscrollcommand=text_scrollbar.set)
self.text_area.pack(side="left", fill="both", expand=True)
text_scrollbar.pack(side="right", fill="y")
self.text_area.insert("1.0", "ここにテキストを入力してください...")
def show_entry_content(self):
name = self.name_entry.get()
password = self.password_entry.get()
messagebox.showinfo("入力内容", f"名前: {name}\nパスワード: {'*' * len(password)}")
def show_selection(self):
# チェックボックスの選択
selected_langs = [lang for lang, var in self.check_vars.items() if var.get()]
langs_text = "、".join(selected_langs) if selected_langs else "なし"
# ラジオボタンの選択
level = self.radio_var.get()
messagebox.showinfo("選択内容", f"好きな言語: {langs_text}\n経験レベル: {level}")
def on_scale_change(self, value):
self.scale_label.config(text=f"値: {float(value):.1f}")
self.progress['value'] = float(value)
def start_progress(self):
# プログレスバーのアニメーション
self.progress['value'] = 0
self.animate_progress()
def animate_progress(self):
current_value = self.progress['value']
if current_value < 100:
self.progress['value'] = current_value + 2
self.root.after(50, self.animate_progress)
if __name__ == "__main__":
root = tk.Tk()
app = BasicWidgetsApp(root)
root.mainloop()
レイアウト管理(Grid、Pack、Place)
# layout_examples.py
import tkinter as tk
from tkinter import ttk
class LayoutExamples:
def __init__(self, root):
self.root = root
self.root.title("レイアウト管理の例")
self.root.geometry("600x400")
# ノートブック(タブ)作成
self.notebook = ttk.Notebook(root)
self.notebook.pack(fill="both", expand=True, padx=10, pady=10)
# 各レイアウトのタブを作成
self.create_grid_tab()
self.create_pack_tab()
self.create_place_tab()
def create_grid_tab(self):
# Gridレイアウトタブ
grid_frame = ttk.Frame(self.notebook)
self.notebook.add(grid_frame, text="Grid レイアウト")
# 計算機のようなレイアウト
ttk.Label(grid_frame, text="Grid レイアウト - 計算機風", font=("Arial", 12, "bold")).grid(row=0, column=0, columnspan=4, pady=10)
# ディスプレイ
self.display_var = tk.StringVar(value="0")
display = ttk.Entry(grid_frame, textvariable=self.display_var, font=("Arial", 14), justify="right", state="readonly")
display.grid(row=1, column=0, columnspan=4, sticky="ew", padx=5, pady=5)
# ボタン配置
buttons = [
['C', '±', '%', '÷'],
['7', '8', '9', '×'],
['4', '5', '6', '-'],
['1', '2', '3', '+'],
['0', '', '.', '=']
]
for row_idx, row in enumerate(buttons):
for col_idx, text in enumerate(row):
if text:
if text == '0':
btn = ttk.Button(grid_frame, text=text, command=lambda t=text: self.calculator_click(t))
btn.grid(row=row_idx+2, column=col_idx, columnspan=2, sticky="ew", padx=2, pady=2)
else:
btn = ttk.Button(grid_frame, text=text, command=lambda t=text: self.calculator_click(t))
btn.grid(row=row_idx+2, column=col_idx, sticky="ew", padx=2, pady=2)
# グリッドの重み設定
for i in range(4):
grid_frame.columnconfigure(i, weight=1)
def create_pack_tab(self):
# Packレイアウトタブ
pack_frame = ttk.Frame(self.notebook)
self.notebook.add(pack_frame, text="Pack レイアウト")
ttk.Label(pack_frame, text="Pack レイアウト - ツールバー風", font=("Arial", 12, "bold")).pack(pady=10)
# 上部ツールバー
toolbar = ttk.Frame(pack_frame, relief="raised", borderwidth=1)
toolbar.pack(fill="x", padx=5, pady=2)
ttk.Button(toolbar, text="新規").pack(side="left", padx=2)
ttk.Button(toolbar, text="開く").pack(side="left", padx=2)
ttk.Button(toolbar, text="保存").pack(side="left", padx=2)
ttk.Separator(toolbar, orient="vertical").pack(side="left", fill="y", padx=5)
ttk.Button(toolbar, text="コピー").pack(side="left", padx=2)
ttk.Button(toolbar, text="貼り付け").pack(side="left", padx=2)
# メインエリア
main_area = ttk.Frame(pack_frame)
main_area.pack(fill="both", expand=True, padx=5, pady=5)
# 左サイドバー
sidebar = ttk.LabelFrame(main_area, text="サイドバー", width=150)
sidebar.pack(side="left", fill="y", padx=(0, 5))
sidebar.pack_propagate(False) # サイズ固定
for i in range(5):
ttk.Button(sidebar, text=f"メニュー {i+1}").pack(fill="x", padx=5, pady=2)
# 中央コンテンツ
content = ttk.LabelFrame(main_area, text="コンテンツエリア")
content.pack(side="left", fill="both", expand=True)
ttk.Label(content, text="メインコンテンツがここに表示されます", font=("Arial", 14)).pack(expand=True)
# 下部ステータスバー
statusbar = ttk.Frame(pack_frame, relief="sunken", borderwidth=1)
statusbar.pack(fill="x", side="bottom", padx=5, pady=2)
ttk.Label(statusbar, text="ステータス: 準備完了").pack(side="left", padx=5)
ttk.Label(statusbar, text="行: 1, 列: 1").pack(side="right", padx=5)
def create_place_tab(self):
# Placeレイアウトタブ
place_frame = ttk.Frame(self.notebook)
self.notebook.add(place_frame, text="Place レイアウト")
ttk.Label(place_frame, text="Place レイアウト - 絶対配置", font=("Arial", 12, "bold")).place(x=10, y=10)
# 相対位置での配置
ttk.Label(place_frame, text="左上", background="lightblue").place(relx=0.1, rely=0.2, relwidth=0.2, relheight=0.1)
ttk.Label(place_frame, text="右上", background="lightgreen").place(relx=0.7, rely=0.2, relwidth=0.2, relheight=0.1)
ttk.Label(place_frame, text="中央", background="lightyellow").place(relx=0.4, rely=0.5, relwidth=0.2, relheight=0.1, anchor="center")
ttk.Label(place_frame, text="左下", background="lightcoral").place(relx=0.1, rely=0.8, relwidth=0.2, relheight=0.1)
ttk.Label(place_frame, text="右下", background="lightpink").place(relx=0.7, rely=0.8, relwidth=0.2, relheight=0.1)
# 絶対位置での配置
ttk.Button(place_frame, text="絶対配置ボタン1").place(x=50, y=100, width=120, height=30)
ttk.Button(place_frame, text="絶対配置ボタン2").place(x=200, y=140, width=120, height=30)
# 重なり配置の例
canvas = tk.Canvas(place_frame, width=200, height=100, background="white")
canvas.place(x=350, y=250)
canvas.create_rectangle(10, 10, 90, 50, fill="red", tags="rect1")
canvas.create_rectangle(50, 30, 130, 70, fill="blue", tags="rect2")
canvas.create_text(100, 80, text="重なり表示", tags="text1")
def calculator_click(self, text):
current = self.display_var.get()
if text == 'C':
self.display_var.set("0")
elif text in "0123456789":
if current == "0":
self.display_var.set(text)
else:
self.display_var.set(current + text)
elif text == ".":
if "." not in current:
self.display_var.set(current + ".")
else:
# 演算子の場合(簡単な実装)
self.display_var.set(current + " " + text + " ")
if __name__ == "__main__":
root = tk.Tk()
app = LayoutExamples(root)
root.mainloop()
ファイル操作とダイアログ
# file_operations.py
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
from pathlib import Path
class FileOperationsApp:
def __init__(self, root):
self.root = root
self.root.title("ファイル操作とダイアログ")
self.root.geometry("600x500")
self.current_file = None
self.setup_ui()
def setup_ui(self):
# メニューバー
self.create_menu()
# ツールバー
toolbar = ttk.Frame(self.root)
toolbar.pack(fill="x", padx=5, pady=2)
ttk.Button(toolbar, text="新規", command=self.new_file).pack(side="left", padx=2)
ttk.Button(toolbar, text="開く", command=self.open_file).pack(side="left", padx=2)
ttk.Button(toolbar, text="保存", command=self.save_file).pack(side="left", padx=2)
ttk.Button(toolbar, text="名前を付けて保存", command=self.save_as_file).pack(side="left", padx=2)
ttk.Separator(toolbar, orient="vertical").pack(side="left", fill="y", padx=5)
ttk.Button(toolbar, text="フォルダ選択", command=self.select_folder).pack(side="left", padx=2)
# メインエリア
main_frame = ttk.Frame(self.root)
main_frame.pack(fill="both", expand=True, padx=5, pady=5)
# ファイル情報表示
info_frame = ttk.LabelFrame(main_frame, text="ファイル情報", padding=5)
info_frame.pack(fill="x", pady=(0, 5))
self.file_info_label = ttk.Label(info_frame, text="ファイル: 新規ドキュメント")
self.file_info_label.pack(anchor="w")
# テキストエディタ
editor_frame = ttk.LabelFrame(main_frame, text="テキストエディタ", padding=5)
editor_frame.pack(fill="both", expand=True)
# テキストウィジェットとスクロールバー
self.text_widget = tk.Text(editor_frame, wrap=tk.WORD, undo=True)
scrollbar = ttk.Scrollbar(editor_frame, orient="vertical", command=self.text_widget.yview)
self.text_widget.configure(yscrollcommand=scrollbar.set)
self.text_widget.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# テキスト変更時のイベント
self.text_widget.bind('<Modified>', self.on_text_modified)
# ステータスバー
self.status_var = tk.StringVar(value="準備完了")
status_bar = ttk.Label(self.root, textvariable=self.status_var, relief="sunken", anchor="w")
status_bar.pack(fill="x", side="bottom", padx=5, pady=2)
# ファイルドロップ対応
self.text_widget.drop_target_register(tk.DND_FILES)
self.text_widget.dnd_bind('<<Drop>>', self.on_file_drop)
def create_menu(self):
menubar = tk.Menu(self.root)
self.root.config(menu=menubar)
# ファイルメニュー
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="ファイル", menu=file_menu)
file_menu.add_command(label="新規", command=self.new_file, accelerator="Ctrl+N")
file_menu.add_command(label="開く", command=self.open_file, accelerator="Ctrl+O")
file_menu.add_separator()
file_menu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
file_menu.add_command(label="名前を付けて保存", command=self.save_as_file, accelerator="Ctrl+Shift+S")
file_menu.add_separator()
file_menu.add_command(label="終了", command=self.root.quit)
# 編集メニュー
edit_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="編集", menu=edit_menu)
edit_menu.add_command(label="元に戻す", command=lambda: self.text_widget.edit_undo(), accelerator="Ctrl+Z")
edit_menu.add_command(label="やり直し", command=lambda: self.text_widget.edit_redo(), accelerator="Ctrl+Y")
edit_menu.add_separator()
edit_menu.add_command(label="全選択", command=self.select_all, accelerator="Ctrl+A")
# キーボードショートカット
self.root.bind('<Control-n>', lambda e: self.new_file())
self.root.bind('<Control-o>', lambda e: self.open_file())
self.root.bind('<Control-s>', lambda e: self.save_file())
self.root.bind('<Control-S>', lambda e: self.save_as_file())
self.root.bind('<Control-a>', lambda e: self.select_all())
def new_file(self):
if self.check_unsaved_changes():
self.text_widget.delete(1.0, tk.END)
self.current_file = None
self.update_file_info()
self.status_var.set("新規ファイルを作成しました")
def open_file(self):
if not self.check_unsaved_changes():
return
file_path = filedialog.askopenfilename(
title="ファイルを開く",
filetypes=[
("テキストファイル", "*.txt"),
("Pythonファイル", "*.py"),
("すべてのファイル", "*.*")
]
)
if file_path:
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
self.text_widget.delete(1.0, tk.END)
self.text_widget.insert(1.0, content)
self.current_file = file_path
self.update_file_info()
self.text_widget.edit_modified(False)
self.status_var.set(f"ファイルを開きました: {os.path.basename(file_path)}")
except UnicodeDecodeError:
# UTF-8で読めない場合はShift_JISで試す
try:
with open(file_path, 'r', encoding='shift_jis') as file:
content = file.read()
self.text_widget.delete(1.0, tk.END)
self.text_widget.insert(1.0, content)
self.current_file = file_path
self.update_file_info()
self.text_widget.edit_modified(False)
self.status_var.set(f"ファイルを開きました: {os.path.basename(file_path)}")
except Exception as e:
messagebox.showerror("エラー", f"ファイルを開けませんでした:\n{str(e)}")
except Exception as e:
messagebox.showerror("エラー", f"ファイルを開けませんでした:\n{str(e)}")
def save_file(self):
if self.current_file:
self.save_to_file(self.current_file)
else:
self.save_as_file()
def save_as_file(self):
file_path = filedialog.asksaveasfilename(
title="名前を付けて保存",
defaultextension=".txt",
filetypes=[
("テキストファイル", "*.txt"),
("Pythonファイル", "*.py"),
("すべてのファイル", "*.*")
]
)
if file_path:
self.save_to_file(file_path)
self.current_file = file_path
self.update_file_info()
def save_to_file(self, file_path):
try:
content = self.text_widget.get(1.0, tk.END + "-1c") # 最後の改行を除く
with open(file_path, 'w', encoding='utf-8') as file:
file.write(content)
self.text_widget.edit_modified(False)
self.status_var.set(f"保存しました: {os.path.basename(file_path)}")
except Exception as e:
messagebox.showerror("エラー", f"ファイルを保存できませんでした:\n{str(e)}")
def select_folder(self):
folder_path = filedialog.askdirectory(title="フォルダを選択")
if folder_path:
# フォルダ内のファイル一覧を表示
self.show_folder_contents(folder_path)
def show_folder_contents(self, folder_path):
# 新しいウィンドウでフォルダ内容を表示
folder_window = tk.Toplevel(self.root)
folder_window.title(f"フォルダ内容: {os.path.basename(folder_path)}")
folder_window.geometry("400x300")
# リストボックスとスクロールバー
frame = ttk.Frame(folder_window)
frame.pack(fill="both", expand=True, padx=10, pady=10)
listbox = tk.Listbox(frame)
scrollbar = ttk.Scrollbar(frame, orient="vertical", command=listbox.yview)
listbox.configure(yscrollcommand=scrollbar.set)
# ファイルとフォルダのリスト表示
try:
items = os.listdir(folder_path)
for item in sorted(items):
item_path = os.path.join(folder_path, item)
if os.path.isdir(item_path):
listbox.insert(tk.END, f"📁 {item}")
else:
listbox.insert(tk.END, f"📄 {item}")
except Exception as e:
listbox.insert(tk.END, f"エラー: {str(e)}")
listbox.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# ダブルクリックでファイルを開く
def on_double_click(event):
selection = listbox.curselection()
if selection:
item_name = listbox.get(selection[0])[2:] # アイコンを除く
item_path = os.path.join(folder_path, item_name)
if os.path.isfile(item_path):
folder_window.destroy()
self.open_specific_file(item_path)
listbox.bind('<Double-1>', on_double_click)
def open_specific_file(self, file_path):
if not self.check_unsaved_changes():
return
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
self.text_widget.delete(1.0, tk.END)
self.text_widget.insert(1.0, content)
self.current_file = file_path
self.update_file_info()
self.text_widget.edit_modified(False)
self.status_var.set(f"ファイルを開きました: {os.path.basename(file_path)}")
except Exception as e:
messagebox.showerror("エラー", f"ファイルを開けませんでした:\n{str(e)}")
def check_unsaved_changes(self):
if self.text_widget.edit_modified():
result = messagebox.askyesnocancel(
"未保存の変更",
"ファイルに未保存の変更があります。保存しますか?"
)
if result is True: # Yes
self.save_file()
return True
elif result is False: # No
return True
else: # Cancel
return False
return True
def update_file_info(self):
if self.current_file:
file_path = Path(self.current_file)
file_info = f"ファイル: {file_path.name} ({file_path.parent})"
# ファイルサイズとタイムスタンプ
try:
stat = file_path.stat()
size = stat.st_size
mtime = stat.st_mtime
from datetime import datetime
mtime_str = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
file_info += f" | サイズ: {size} bytes | 更新: {mtime_str}"
except:
pass
else:
file_info = "ファイル: 新規ドキュメント"
self.file_info_label.config(text=file_info)
def on_text_modified(self, event):
# テキストが変更された時の処理
if self.text_widget.edit_modified():
title = self.root.title()
if not title.endswith(" *"):
self.root.title(title + " *")
else:
title = self.root.title()
if title.endswith(" *"):
self.root.title(title[:-2])
def on_file_drop(self, event):
# ファイルドロップ時の処理(簡単な実装)
# 実際のドロップ対応には外部ライブラリが必要
pass
def select_all(self):
self.text_widget.tag_add(tk.SEL, "1.0", tk.END)
self.text_widget.mark_set(tk.INSERT, "1.0")
self.text_widget.see(tk.INSERT)
return "break"
if __name__ == "__main__":
root = tk.Tk()
app = FileOperationsApp(root)
root.mainloop()
設定と環境構築
# Tkinterは標準ライブラリなので追加インストール不要
# Python環境確認
python -c "import tkinter; print('Tkinter使用可能')"
# テーマ使用(ttk.Style)
python -c "import tkinter as tk; from tkinter import ttk; root=tk.Tk(); print(ttk.Style().theme_names()); root.destroy()"
# Pillow(画像処理)が必要な場合
pip install Pillow
# アプリケーション実行
python hello_world.py
# スタンドアロン実行ファイル作成
pip install pyinstaller
pyinstaller --onefile --windowed hello_world.py
# Linux環境でのtkinter確認
# Ubuntu/Debian
sudo apt-get install python3-tk
# CentOS/RHEL
sudo yum install tkinter
# または
sudo dnf install python3-tkinter
プロジェクト構造例
my_tkinter_app/
├── main.py # メインアプリケーション
├── widgets/
│ ├── __init__.py
│ ├── custom_widgets.py # カスタムウィジェット
│ └── dialogs.py # カスタムダイアログ
├── utils/
│ ├── __init__.py
│ ├── file_handler.py # ファイル操作
│ └── config.py # 設定管理
├── resources/
│ ├── icons/ # アイコンファイル
│ └── images/ # 画像ファイル
├── tests/
│ ├── __init__.py
│ └── test_widgets.py # テストファイル
├── requirements.txt
└── README.md