1
2

はじめに

本記事では、PythonとTkinterを使用して、完全自由移動可能なカンバン方式のタスク管理アプリケーションを作成する方法を解説します。このアプリケーションは、シンプルながら柔軟性の高いタスク管理ツールとして機能し、プログラミング初心者からある程度経験のある方まで、幅広い読者に役立つ内容となっています。

image.png

要件

アプリケーションの主な要件は以下の通りです:

  1. カンバン方式のタスク管理(ToDo, Doing, Done の3列)
  2. タスクの追加、編集、削除機能
  3. タスク間の自由な移動(任意の列間で双方向に移動可能)
  4. タスクの色変更機能
  5. シンプルで直感的なユーザーインターフェース

仕様

画面全体
image.png

タスクの追加
image.png

タスクの移動
image.png

タスクの編集
image.png

タスクの編集(色)
image.png

アプリケーションの詳細仕様は以下の通りです:

  1. ユーザーインターフェース

    • ウィンドウサイズ: 800x600ピクセル
    • 3つの列(ToDo, Doing, Done)を持つカンバンボード
    • 各列の背景色を区別(ToDo: 薄い赤、Doing: 薄い緑、Done: 薄い青)
    • 新規タスク入力フィールドと追加ボタン
  2. タスク管理

    • 新規タスクは常に「ToDo」列に追加
    • 各タスクカードに以下の機能を実装:
      • 左右の列への移動ボタン
      • 編集ボタン
      • 色変更ボタン
      • 削除ボタン
  3. タスクの移動

    • 左右の矢印ボタンで隣接する列へ移動可能
    • ToDo ⇔ Doing ⇔ Done の任意の方向に移動可能
  4. タスクの編集

    • タスクの説明文を編集可能
    • タスクカードの色をカラーピッカーで自由に変更可能
  5. データ管理

    • アプリケーション実行中はメモリ上でタスクを管理
    • (オプション)タスクデータの永続化は今後の拡張機能として検討

クラス図

シーケンス図

実装

それでは、上記の要件と仕様に基づいて、アプリケーションの実装を行っていきます。

1. 必要なモジュールのインポート

まず、必要なモジュールをインポートします。

import tkinter as tk
from tkinter import ttk, messagebox, colorchooser, simpledialog
import random

2. タスククラスの定義

個々のタスクを表現するクラスを定義します。

class Task:
    def __init__(self, description, status="ToDo", color=None):
        self.description = description
        self.status = status
        self.color = color or self.random_color()

    def random_color(self):
        return "#{:06x}".format(random.randint(0x808080, 0xFFFFFF))

3. タスクカードクラスの定義

タスクカードのUIと機能を実装するクラスを定義します。

class TaskCard(tk.Frame):
    def __init__(self, master, task, delete_callback, move_callback, edit_callback, **kwargs):
        super().__init__(master, bg=task.color, bd=1, relief=tk.RAISED, **kwargs)
        self.task = task
        self.delete_callback = delete_callback
        self.move_callback = move_callback
        self.edit_callback = edit_callback

        self.description = tk.Label(self, text=task.description, bg=task.color, wraplength=150)
        self.description.pack(expand=True, fill=tk.BOTH, padx=5, pady=5)

        button_frame = tk.Frame(self, bg=task.color)
        button_frame.pack(fill=tk.X, padx=2, pady=2)

        self.left_button = ttk.Button(button_frame, text="", width=2, command=self.move_left)
        self.left_button.pack(side=tk.LEFT, padx=(0, 2))

        self.right_button = ttk.Button(button_frame, text="", width=2, command=self.move_right)
        self.right_button.pack(side=tk.LEFT, padx=(0, 2))

        edit_button = ttk.Button(button_frame, text="編集", width=4, command=self.edit_task)
        edit_button.pack(side=tk.LEFT, padx=(0, 2))

        color_button = ttk.Button(button_frame, text="", width=3, command=self.change_color)
        color_button.pack(side=tk.LEFT, padx=(0, 2))

        delete = ttk.Button(button_frame, text="×", width=2, command=self.delete)
        delete.pack(side=tk.RIGHT)

        self.update_move_buttons()

    def move_left(self):
        self.move_callback(self.task, self.task.status, -1)

    def move_right(self):
        self.move_callback(self.task, self.task.status, 1)

    def delete(self):
        self.delete_callback(self.task, self.task.status)

    def edit_task(self):
        new_description = simpledialog.askstring("タスクの編集", "新しい説明を入力してください:", initialvalue=self.task.description)
        if new_description:
            self.edit_callback(self.task, new_description)
            self.description.config(text=new_description)

    def change_color(self):
        new_color = colorchooser.askcolor(title="タスクの色を選択")[1]
        if new_color:
            self.task.color = new_color
            self.config(bg=new_color)
            self.description.config(bg=new_color)
            for widget in self.winfo_children():
                if isinstance(widget, tk.Frame):
                    widget.config(bg=new_color)

    def update_move_buttons(self):
        order = ["ToDo", "Doing", "Done"]
        current_index = order.index(self.task.status)
        self.left_button.config(state="normal" if current_index > 0 else "disabled")
        self.right_button.config(state="normal" if current_index < len(order) - 1 else "disabled")

4. カンバンボードクラスの定義

アプリケーションのメインウィンドウとカンバンボードの機能を実装するクラスを定義します。

class KanbanBoard(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("タスク管理アプリ")
        self.geometry("800x600")
        self.configure(bg="#f0f0f0")

        self.columns = ["ToDo", "Doing", "Done"]
        self.tasks = {column: [] for column in self.columns}

        self.create_widgets()

    def create_widgets(self):
        self.create_header()
        self.create_board()
        self.create_task_entry()

    def create_header(self):
        header = tk.Frame(self, bg="#4a4a4a")
        header.pack(fill=tk.X, padx=10, pady=10)

        title = tk.Label(header, text="タスク管理アプリ", font=("Helvetica", 18, "bold"), bg="#4a4a4a", fg="white")
        title.pack(side=tk.LEFT, padx=10)

    def create_board(self):
        board = tk.Frame(self)
        board.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)

        colors = {"ToDo": "#ffcccc", "Doing": "#ccffcc", "Done": "#ccccff"}

        for i, column in enumerate(self.columns):
            column_frame = tk.Frame(board, bg=colors[column], bd=2, relief=tk.RAISED)
            column_frame.grid(row=0, column=i, padx=5, pady=5, sticky="nsew")

            column_label = tk.Label(column_frame, text=column, font=("Helvetica", 14, "bold"), bg=colors[column])
            column_label.pack(pady=10)

            task_frame = tk.Frame(column_frame, bg=colors[column])
            task_frame.pack(expand=True, fill=tk.BOTH, padx=5, pady=5)

            self.tasks[column] = []
            
            board.columnconfigure(i, weight=1)
        board.rowconfigure(0, weight=1)

    def create_task_entry(self):
        entry_frame = tk.Frame(self, bg="#f0f0f0")
        entry_frame.pack(fill=tk.X, padx=10, pady=10)

        self.task_entry = ttk.Entry(entry_frame, font=("Helvetica", 12), width=40)
        self.task_entry.pack(side=tk.LEFT, padx=(0, 10))

        add_button = ttk.Button(entry_frame, text="タスクを追加", command=self.add_task)
        add_button.pack(side=tk.LEFT)

    def add_task(self):
        description = self.task_entry.get().strip()
        if description:
            task = Task(description)
            self.create_task_card("ToDo", task)
            self.task_entry.delete(0, tk.END)
        else:
            messagebox.showwarning("警告", "タスクの説明を入力してください。")

    def create_task_card(self, column, task):
        column_frame = self.winfo_children()[1].grid_slaves(row=0, column=self.columns.index(column))[0]
        task_frame = column_frame.winfo_children()[1]

        card = TaskCard(task_frame, task, delete_callback=self.delete_task, 
                        move_callback=self.move_task, edit_callback=self.edit_task)
        card.pack(fill=tk.X, padx=5, pady=5)

        task.status = column
        self.tasks[column].append((task, card))

    def move_task(self, task, from_column, direction):
        current_index = self.columns.index(from_column)
        new_index = current_index + direction
        if 0 <= new_index < len(self.columns):
            to_column = self.columns[new_index]
            self.delete_task(task, from_column)
            self.create_task_card(to_column, task)
            task.status = to_column  # タスクのステータスを更新
            self.update_all_task_cards()  # すべてのタスクカードを更新

    def delete_task(self, task, column):
        for t, card in self.tasks[column]:
            if t == task:
                card.destroy()
                self.tasks[column].remove((t, card))
                break

    def edit_task(self, task, new_description):
        task.description = new_description
        # タスクカードの表示を更新
        for column in self.tasks:
            for t, card in self.tasks[column]:
                if t == task:
                    card.description.config(text=new_description)
                    break

    def update_all_task_cards(self):
        for column in self.tasks:
            for task, card in self.tasks[column]:
                card.update_move_buttons()

if __name__ == "__main__":
    app = KanbanBoard()
    app.mainloop()

実行方法

上記のコードを kanban_task_manager.py として保存し、以下のコマンドで実行します:

python kanban_task_manager.py

機能説明

  1. タスクの追加: 下部の入力フィールドにタスクの説明を入力し、「タスクを追加」ボタンをクリックします。
  2. タスクの移動: タスクカード上の「←」「→」ボタンで左右の列に移動できます。
  3. タスクの編集: 「編集」ボタンをクリックし、新しい説明を入力します。
  4. タスクの色変更: 「色」ボタンをクリックし、カラーピッカーから新しい色を選択します。
  5. タスクの削除: 「×」ボタンをクリックしてタスクを削除します。

まとめ

このアプリケーションでは、PythonとTkinterを使用して、カンバン方式のタスク管理ツールを実装しました。完全自由移動可能なタスク、カラフルなタスクカード、シンプルで直感的なUIにより、効率的なタスク管理が可能です。

発展的な機能追加のアイデア

  1. データの永続化(ファイルやデータベースへの保存)
  2. タスクの優先度設定
  3. 締め切り日の追加
  4. 複数のプロジェクトやボードの管理
  5. ドラッグ&ドロップによるタスク移動

これらの機能を追加することで、より高度なタスク管理アプリケーションを作成できます。

ぜひ、このコードを基に自分なりのカスタマイズを加えて、理想のタスク管理ツールを作ってみてください!

1
2
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
1
2