Tkinter

Python標準ライブラリに含まれるGUIツールキット。Tcl/Tkをベースとし、追加インストール不要で即座に利用可能。シンプルなアプリケーションやプロトタイピングに最適で、学習コストが低い。

デスクトップPythonGUI標準ライブラリ軽量

フレームワーク

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効果の実装が困難
  • スケーラビリティ: 大規模アプリケーションには不適

主要リンク

書き方の例

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