0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonとTkinterで実現するマルチウィンドウアプリケーション - コールバックによるデータ共有

Posted at

はじめに

Tkinterを使用してマルチウィンドウアプリケーションを開発する際、課題の一つが「ウィンドウ間でのデータ共有」です。この記事では、コールバック関数を使用したデータ共有の実装方法について、具体的な実装例とともに解説します。

image.png

実装するアプリケーションの概要

シンプルなタスク管理アプリケーションを例に、ウィンドウ間でのデータ共有を実装します。

  • メインウィンドウ:タスク一覧を表示
  • サブウィンドウ:新規タスクの入力
  • データ共有:入力されたタスクをメインウィンドウのリストに反映

コード実装

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
from datetime import datetime

class MainWindow:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("タスク管理")
        self.root.geometry("400x500")
        
        # タスク一覧を表示するTreeview
        self.tree = ttk.Treeview(self.root, columns=('Task', 'Priority', 'Due Date'))
        self.tree.heading('Task', text='タスク')
        self.tree.heading('Priority', text='優先度')
        self.tree.heading('Due Date', text='期限')
        self.tree.column('#0', width=0, stretch=tk.NO)  # 非表示の先頭カラムを幅0に
        self.tree.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
        
        # 新規タスク追加ボタン
        self.add_button = tk.Button(
            self.root,
            text="新規タスク追加",
            command=self.open_task_window
        )
        self.add_button.pack(pady=10)
        
        # タスクの合計を表示するラベル
        self.total_label = tk.Label(self.root, text="合計タスク数: 0")
        self.total_label.pack(pady=5)
    
    def open_task_window(self):
        """タスク入力ウィンドウを開く"""
        TaskWindow(self.root, self.add_task)
    
    def add_task(self, task_data):
        """
        新規タスクを追加するコールバック関数
        TaskWindowから呼び出される
        """
        # Treeviewにタスクを追加
        self.tree.insert(
            '',
            'end',
            values=(
                task_data['task'],
                task_data['priority'],
                task_data['due_date']
            )
        )
        
        # 合計タスク数を更新
        total = len(self.tree.get_children())
        self.total_label.config(text=f"合計タスク数: {total}")
    
    def run(self):
        """アプリケーションの実行"""
        self.root.mainloop()

class TaskWindow:
    def __init__(self, master, callback):
        # コールバック関数を保持
        self.callback = callback
        
        # ウィンドウの作成
        self.window = tk.Toplevel(master)
        self.window.title("新規タスク")
        self.window.geometry("300x400")
        
        # タスク名入力
        tk.Label(self.window, text="タスク名:").pack(pady=(10, 0))
        self.task_entry = tk.Entry(self.window, width=40)
        self.task_entry.pack(pady=(0, 10))
        
        # 優先度選択
        tk.Label(self.window, text="優先度:").pack(pady=(10, 0))
        self.priority_var = tk.StringVar(value="")
        priorities = ["", "", ""]
        self.priority_combo = ttk.Combobox(
            self.window,
            textvariable=self.priority_var,
            values=priorities,
            state="readonly"
        )
        self.priority_combo.pack(pady=(0, 10))
        
        # 期限日入力
        tk.Label(self.window, text="期限日 (YYYY-MM-DD):").pack(pady=(10, 0))
        self.due_date_entry = tk.Entry(self.window, width=40)
        self.due_date_entry.pack(pady=(0, 10))
        
        # 保存ボタン
        self.save_button = tk.Button(
            self.window,
            text="保存",
            command=self.save_task
        )
        self.save_button.pack(pady=20)
        
        # ウィンドウを親の中央に配置
        self.window.transient(master)
        self.window.grab_set()
    
    def save_task(self):
        """タスクを保存し、コールバック関数を呼び出す"""
        # 入力値の取得と検証
        task = self.task_entry.get().strip()
        priority = self.priority_var.get()
        due_date = self.due_date_entry.get().strip()
        
        # 入力チェック
        if not task:
            messagebox.showwarning("警告", "タスク名を入力してください")
            return
            
        if not due_date:
            messagebox.showwarning("警告", "期限日を入力してください")
            return
            
        # 日付形式のチェック
        try:
            datetime.strptime(due_date, '%Y-%m-%d')
        except ValueError:
            messagebox.showwarning(
                "警告",
                "期限日はYYYY-MM-DD形式で入力してください\n例: 2024-12-31"
            )
            return
        
        # データを辞書形式でまとめる
        task_data = {
            'task': task,
            'priority': priority,
            'due_date': due_date
        }
        
        # コールバック関数を呼び出してデータを渡す
        self.callback(task_data)
        
        # ウィンドウを閉じる
        self.window.destroy()

if __name__ == "__main__":
    app = MainWindow()
    app.run()

コードの解説

1. データ共有の仕組み

この実装では、以下の流れでウィンドウ間のデータ共有を実現しています:

  1. メインウィンドウ(MainWindow)がコールバック関数(add_task)を用意
  2. サブウィンドウ(TaskWindow)の作成時にコールバック関数を渡す
  3. サブウィンドウでデータが入力され保存ボタンが押されると、コールバック関数を通じてデータをメインウィンドウに渡す

2. 重要なポイント

コールバック関数の受け渡し

def open_task_window(self):
    TaskWindow(self.root, self.add_task)  # コールバック関数を渡す

コールバック関数の保持

def __init__(self, master, callback):
    self.callback = callback  # コールバック関数を保持

データの受け渡し

# サブウィンドウでデータを辞書形式にまとめる
task_data = {
    'task': task,
    'priority': priority,
    'due_date': due_date
}

# コールバック関数を呼び出してデータを渡す
self.callback(task_data)

3. エラーハンドリング

入力値の検証とエラーメッセージの表示を実装しています:

  • 必須項目のチェック
  • 日付形式の検証
  • ユーザーへのフィードバック

4. リソース管理

サブウィンドウの適切な管理のため、以下の実装を行っています:

  • transient(master)による親ウィンドウとの関連付け
  • grab_set()によるモーダルウィンドウ化
  • 処理完了時のdestroy()によるリソース解放

実行結果

このコードを実行すると:

  1. タスク一覧を表示するメインウィンドウが表示されます

image.png

  1. 「新規タスク追加」ボタンをクリックすると、タスク入力用のサブウィンドウが開きます

image.png

  1. サブウィンドウでタスクを入力し保存すると、メインウィンドウのリストに追加されます

image.png

  1. タスクが追加されるたびに、合計タスク数が更新されます

image.png

まとめ

image.png

コールバック関数を使用したウィンドウ間のデータ共有には、以下のメリットがあります:

  1. シンプルな実装: 複雑な状態管理が不要
  2. 疎結合: ウィンドウ間の依存関係が最小限
  3. 柔軟性: データの受け渡し方法を自由に定義可能

クラス図

シーケンス図

0
1
0

Register as a new user and use Qiita more conveniently

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?