2
0

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でシンプルなToDoアプリを作った

Posted at

Python の 学習の一環でシンプルな ToDo アプリを作りました。
ボタンや入力欄があると直感的で使いやすいので、GUIライブラリ「tkinter」を使ってみました。

開発環境

  • Python: 3.11
  • GUIライブラリ: tkinter(標準)
  • カレンダー入力: tkcalendar(別途インストール)
  • 動作確認: Windows 10

アプリの主な機能

  • タスクの登録(期日付き)
  • タスクの削除
  • タスクの完了/未完了切り替え
  • タスクの一覧表示
  • JSONファイルに保存してアプリを閉じても自動復元

完成画面

キャプチャ.PNG

主な機能の説明

add_task() : タスクの追加処理

# タスクを追加する
def add_task(self):
    task = self.entry.get().strip()
    due = self.due_entry.get().strip()
    if task:
        self.tasks.append({"task": task, "done": False, "due": due})
        self.entry.delete(0, tk.END)
        self.update_listbox()
  • self.entry.get() で入力欄からタスク内容を取得
  • self.due_entry.get() で選択した期日を取得
  • タスク内容が空でなければ
    • 辞書形式でリストに追加
    • タスクは done: False(未完了)で初期化
    • 入力欄をクリアし、update_listbox() で表示を更新

delete_task() : タスクの削除処理

# タスクを削除する
def delete_task(self):
    index = self.listbox.curselection()
    if index:
        del self.tasks[index[0]]
        self.update_listbox()
  • self.listbox.curselection() で選択中のインデックスを取得
  • 選択されていれば、そのインデックスにあるタスクを削除
  • タスク一覧の表示を update_listbox() で更新

mark_done() : 完了済み / 未完了 の切り替え処理

# タスクを完了済/未完了にする
def mark_done(self):
    index = self.listbox.curselection()
    # タスクを選んでいない場合はなにもしない(indexエラー対策)
    if not index:
        return
    
    # タスクを完了済みにする
    if self.tasks[index[0]]["done"] == False:
        self.tasks[index[0]]["done"] = True
        self.update_listbox()
    # タスクを未完了にする
    else:
        self.tasks[index[0]]["done"] = False
        self.update_listbox()
  • 現在選択中のタスクがあるか確認(なければ return で何もしないよう処理する)
  • done フラグを True ⇄ False でトグル切り替え
  • 変更後にリストボックスを再描画

タスクの一覧表示

def update_listbox(self):
    self.listbox.delete(0, tk.END)
    for t in self.tasks:
        mark = "✔" if t["done"] else "✖"
        due = f" (期日: {t['due']})" if t.get("due") else ""
        self.listbox.insert(tk.END, f"[{mark}] {t['task']}{due}")
  • self.listbox.delete でリストボックスに表示されているタスクを一度すべて削除する(リストの初期化)
  • self.tasks に登録されているタスクを1件ずつ処理する
    • 各タスクについて、完了済みなら「✔」、未完了なら「✖」のマークをつける
    • 期日が設定されている場合は (期日: YYYY-MM-DD) を表示に含める
    • 上記を組み合わせた1行の文字列をリストボックスに追加する

JSONファイルに保存してアプリを閉じても自動復元

# ウインドウを閉じる
def on_close(self):
    save_tasks(self.tasks)
    self.root.destroy()

# タスクを保存する
def save_tasks(tasks):
    with open(FILENAME, "w", encoding="utf-8") as f:
        json.dump(tasks, f, indent=2, ensure_ascii=False)
  • ウィンドウの「×」ボタン(閉じる)を押したときに呼び出す

  • save_tasks(self.tasks) により、現在のタスクを保存

  • 最後に self.root.destroy() を実行して、アプリケーションを終了

  • 引数 tasks に渡されたタスクリストを、JSON形式でファイルに保存

  • indent=2 により、インデントを整形済みで書き出し

  • ensure_ascii=False を指定することで、日本語も文字化けせずに保存可能

出力ファイル例

[
  {
    "task": "昼ご飯を食べる",
    "done": true,
    "due": "2025-05-29"
  },
  {
    "task": "資格勉強する",
    "done": true,
    "due": "2025-05-29"
  },
  {
    "task": "遊ぶ",
    "done": false,
    "due": "2025-05-29"
  },
  {
    "task": "夕飯を食べる",
    "done": false,
    "due": "2025-05-29"
  }
]

GUI の設定

使用している主なウィジェット

ウィジェット 用途
Label 説明文の表示
Entry タスクのテキスト入力欄
DateEntry カレンダー形式の期日入力欄
Button 各種操作(追加・削除・切替)
Listbox 登録済タスクの一覧表示

GUIの初期化部分(__init__

def __init__(self, root):
    self.root = root
    self.root.title("ToDoアプリ")
    self.tasks = load_tasks()
  • root:アプリケーションのウィンドウ本体
  • self.root.title(...):ウィンドウのタイトルを設定
  • self.tasks = load_tasks():保存されたタスクを読み込み

タスク入力欄

tk.Label(root, text="タスク:").pack(anchor="w", padx=10)
self.entry = tk.Entry(root, width=40)
self.entry.pack(pady=5)
  • Label:タスク入力欄の見出し
  • Entry:タスクを入力するテキストボックス

期日入力欄

tk.Label(root, text="期日を入力 (例: 2025-04-30):").pack(anchor="w", padx=10)
self.due_entry = DateEntry(root, width=20, date_pattern='yyyy-mm-dd',
    year=date.today().year, month=date.today().month, day=date.today().day)
self.due_entry.pack(pady=5)
  • DateEntry(tkcalendar):カレンダー付きの入力欄
  • date_pattern='yyyy-mm-dd':年月日を指定フォーマットで表示

操作ボタン

self.add_button = tk.Button(root, text="タスク追加", command=self.add_task)
self.done_button = tk.Button(root, text="完了済/未完了", command=self.mark_done)
self.delete_button = tk.Button(root, text="削除", command=self.delete_task)
  • 各ボタンは押下時に対応する関数(コールバック関数)を実行
    • add_task():タスクを追加
    • mark_done():完了/未完了の切替
    • delete_task():タスク削除

タスクリスト表示欄

self.listbox = tk.Listbox(root, width=50, selectmode=tk.SINGLE)
  • Listbox:登録されたタスクを1行ずつ表示

閉じる処理の登録

self.root.protocol("WM_DELETE_WINDOW", self.on_close)
  • ウィンドウの「×」ボタンを押したときに on_close() を呼び出す

最終的な全体コード

import tkinter as tk
from tkinter import messagebox
from tkcalendar import DateEntry
from datetime import date
import json
import os

FILENAME = "tasks.json"

# 保存してあるタスクを読み込む
def load_tasks():
    if os.path.exists(FILENAME):
        with open(FILENAME, "r", encoding="utf-8") as f:
            return json.load(f)
    else:
        return []

# タスクを保存する
def save_tasks(tasks):
    with open(FILENAME, "w", encoding="utf-8") as f:
        json.dump(tasks, f, indent=2, ensure_ascii=False)


class TodoApp:
    def __init__(self, root):
        self.root = root
        self.root.title("ToDoアプリ")
        self.tasks = load_tasks()

        tk.Label(root, text="タスク:").pack(anchor="w", padx=10)
        self.entry = tk.Entry(root, width=40)
        self.entry.pack(pady=5)

        tk.Label(root, text="期日を入力 (例: 2025-04-30):").pack(anchor="w", padx=10)
        self.due_entry = DateEntry(root, width=20, date_pattern='yyyy-mm-dd', year=date.today().year, month=date.today().month, day=date.today().day)
        self.due_entry.pack(pady=5)

        self.add_button = tk.Button(root, text="タスク追加", command=self.add_task)
        self.add_button.pack(padx=5)

        self.listbox = tk.Listbox(root, width=50, selectmode=tk.SINGLE)
        self.listbox.pack(pady=5)

        self.done_button = tk.Button(root, text="完了済/未完了", command=self.mark_done)
        self.done_button.pack(pady=5)

        self.delete_button = tk.Button(root, text="削除", command=self.delete_task)
        self.delete_button.pack(pady=5)

        self.update_listbox()

        self.root.protocol("WM_DELETE_WINDOW", self.on_close)

    # タスクリストを表示させている
    def update_listbox(self):
        self.listbox.delete(0, tk.END)
        for t in self.tasks:
            mark = "✔" if t["done"] else "✖"
            due = f" (期日: {t['due']})" if t.get("due") else ""
            self.listbox.insert(tk.END, f"[{mark}] {t['task']}{due}")

    # タスクを追加する
    def add_task(self):
        task = self.entry.get().strip()
        due = self.due_entry.get().strip()
        if task:
            self.tasks.append({"task": task, "done": False, "due": due})
            self.entry.delete(0, tk.END)
            self.update_listbox()
    
    # タスクを削除する
    def delete_task(self):
        index = self.listbox.curselection()
        if index:
            del self.tasks[index[0]]
            self.update_listbox()
    
    # タスクを完了済/未完了にする
    def mark_done(self):
        index = self.listbox.curselection()
        # タスクを選んでいない場合はなにもしない(indexエラー対策)
        if not index:
            return
        
        # タスクを完了済みにする
        if self.tasks[index[0]]["done"] == False:
            self.tasks[index[0]]["done"] = True
            self.update_listbox()
        # タスクを未完了にする
        else:
            self.tasks[index[0]]["done"] = False
            self.update_listbox()

    # ウインドウを閉じる
    def on_close(self):
        save_tasks(self.tasks)
        self.root.destroy()

# アプリ起動時にmain()を実行させる記述
if __name__=="__main__":
    root = tk.Tk()
    app = TodoApp(root)
    root.mainloop()

まとめ

今回の ToDo アプリ作成を通して、Python の基本であるリスト操作、関数化、JSON ファイルの入出力について実践的に学ぶことができました。また、GUI ライブラリ(tkinter)を使うことで、コマンドラインではなく、視覚的にわかりやすい画面を気軽に作れる点もとても魅力的だと感じました。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?