生成AIって本当にすごい・・・。
プログラミングほぼできないけど、生成AIにコード書かせるだけで、簡単にアプリ作れちゃう!
python入れて、以下のコードを.pyで保存するだけ。
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import json
import os
from datetime import datetime
class TodoApp:
def __init__(self, root):
self.root = root
self.root.title("Todo アプリ ")
self.root.geometry("900x750")
# データファイル
self.data_file = "todos.json"
self.todos = self.load_todos()
# 変数の初期化
self.filter_var = tk.StringVar(value="すべて")
self.priority_var = tk.StringVar(value="普通")
self.create_widgets()
self.setup_keyboard_shortcuts()
self.refresh_list()
def create_widgets(self):
# メインフレーム
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 入力エリア
input_frame = ttk.Frame(main_frame)
input_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
ttk.Label(input_frame, text="新しいタスク:", font=('', 12)).grid(row=0, column=0, sticky=tk.W)
self.task_entry = ttk.Entry(input_frame, width=60, font=('', 12))
self.task_entry.grid(row=1, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
self.task_entry.bind('<Return>', lambda e: self.add_task())
ttk.Button(input_frame, text="追加", command=self.add_task).grid(row=1, column=1)
# 優先度選択
ttk.Label(input_frame, text="優先度:", font=('', 12)).grid(row=2, column=0, sticky=tk.W, pady=(10, 0))
priority_combo = ttk.Combobox(input_frame, textvariable=self.priority_var,
values=["高", "普通", "低"], state="readonly", width=10)
priority_combo.grid(row=3, column=0, sticky=tk.W, pady=(0, 10))
input_frame.columnconfigure(0, weight=1)
# フィルターエリア
self.create_filter_widgets(main_frame)
# リストエリア
list_frame = ttk.Frame(main_frame)
list_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
# Treeview(リスト表示)
columns = ('status', 'priority', 'task', 'created')
self.tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=18)
self.tree.heading('status', text='状態')
self.tree.heading('priority', text='優先度')
self.tree.heading('task', text='タスク')
self.tree.heading('created', text='作成日時')
self.tree.column('status', width=100)
self.tree.column('priority', width=80)
self.tree.column('task', width=400)
self.tree.column('created', width=180)
# 優先度別の色分け設定
self.tree.tag_configure('high_priority', background='#ffebee')
self.tree.tag_configure('normal_priority', background='white')
self.tree.tag_configure('low_priority', background='#f3e5f5')
self.tree.tag_configure('completed', background='#e8f5e8')
# ダブルクリックで編集
self.tree.bind('<Double-1>', lambda e: self.edit_task())
# 右クリックメニュー
self.create_context_menu()
self.tree.bind('<Button-3>', self.show_context_menu)
# スクロールバー
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscrollcommand=scrollbar.set)
self.tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
list_frame.columnconfigure(0, weight=1)
list_frame.rowconfigure(0, weight=1)
# ボタンエリア
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
# 左側のボタン
left_buttons = ttk.Frame(button_frame)
left_buttons.pack(side=tk.LEFT)
ttk.Button(left_buttons, text="完了にする (Ctrl+D)", command=self.mark_completed).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(left_buttons, text="未完了に戻す", command=self.mark_pending).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(left_buttons, text="編集 (ダブルクリック)", command=self.edit_task).pack(side=tk.LEFT, padx=(0, 5))
# 中央のボタン(優先度変更)
center_buttons = ttk.Frame(button_frame)
center_buttons.pack(side=tk.LEFT, padx=(20, 0))
ttk.Label(center_buttons, text="優先度変更:", font=('', 10)).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(center_buttons, text="🔴高", command=lambda: self.change_priority("高"), width=6).pack(side=tk.LEFT, padx=(0, 2))
ttk.Button(center_buttons, text="🟡普通", command=lambda: self.change_priority("普通"), width=6).pack(side=tk.LEFT, padx=(0, 2))
ttk.Button(center_buttons, text="🔵低", command=lambda: self.change_priority("低"), width=6).pack(side=tk.LEFT, padx=(0, 2))
# 右側のボタン
right_buttons = ttk.Frame(button_frame)
right_buttons.pack(side=tk.RIGHT)
ttk.Button(right_buttons, text="削除 (Delete)", command=self.delete_task).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(right_buttons, text="完了済みを全削除", command=self.clear_completed).pack(side=tk.LEFT)
# ステータスバー
self.create_status_bar(main_frame)
# ウィンドウのリサイズ設定
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(2, weight=1)
def create_context_menu(self):
"""右クリックメニューを作成"""
self.context_menu = tk.Menu(self.root, tearoff=0)
self.context_menu.add_command(label="編集", command=self.edit_task)
self.context_menu.add_separator()
self.context_menu.add_command(label="完了にする", command=self.mark_completed)
self.context_menu.add_command(label="未完了に戻す", command=self.mark_pending)
self.context_menu.add_separator()
# 優先度変更サブメニュー
priority_menu = tk.Menu(self.context_menu, tearoff=0)
priority_menu.add_command(label="🔴 高優先度", command=lambda: self.change_priority("高"))
priority_menu.add_command(label="🟡 普通優先度", command=lambda: self.change_priority("普通"))
priority_menu.add_command(label="🔵 低優先度", command=lambda: self.change_priority("低"))
self.context_menu.add_cascade(label="優先度変更", menu=priority_menu)
self.context_menu.add_separator()
self.context_menu.add_command(label="削除", command=self.delete_task)
def show_context_menu(self, event):
"""右クリックメニューを表示"""
# クリックした位置のアイテムを選択
item = self.tree.identify_row(event.y)
if item:
self.tree.selection_set(item)
self.context_menu.post(event.x_root, event.y_root)
def create_filter_widgets(self, parent):
# フィルターフレーム
filter_frame = ttk.Frame(parent)
filter_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 5))
ttk.Label(filter_frame, text="表示:", font=('', 11)).pack(side=tk.LEFT, padx=(0, 5))
filter_combo = ttk.Combobox(filter_frame, textvariable=self.filter_var,
values=["すべて", "未完了のみ", "完了済みのみ", "高優先度", "普通優先度", "低優先度"],
state="readonly", width=15)
filter_combo.pack(side=tk.LEFT, padx=(0, 20))
filter_combo.bind('<<ComboboxSelected>>', lambda e: self.refresh_list())
# 検索機能
ttk.Label(filter_frame, text="検索:", font=('', 11)).pack(side=tk.LEFT, padx=(0, 5))
self.search_var = tk.StringVar()
search_entry = ttk.Entry(filter_frame, textvariable=self.search_var, width=20)
search_entry.pack(side=tk.LEFT)
search_entry.bind('<KeyRelease>', lambda e: self.refresh_list())
# ソート機能
ttk.Label(filter_frame, text="並び順:", font=('', 11)).pack(side=tk.LEFT, padx=(20, 5))
self.sort_var = tk.StringVar(value="作成日時")
sort_combo = ttk.Combobox(filter_frame, textvariable=self.sort_var,
values=["作成日時", "優先度", "タスク名", "状態"],
state="readonly", width=12)
sort_combo.pack(side=tk.LEFT)
sort_combo.bind('<<ComboboxSelected>>', lambda e: self.refresh_list())
def create_status_bar(self, parent):
self.status_frame = ttk.Frame(parent)
self.status_frame.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(5, 0))
self.status_label = ttk.Label(self.status_frame, text="", font=('', 10))
self.status_label.pack(side=tk.LEFT)
# ショートカットヒント
hint_label = ttk.Label(self.status_frame,
text="ヒント: Ctrl+N=新規入力, Ctrl+D=完了, Delete=削除, ダブルクリック=編集, 右クリック=メニュー",
font=('', 9), foreground='gray')
hint_label.pack(side=tk.RIGHT)
def setup_keyboard_shortcuts(self):
# キーボードショートカット
self.root.bind('<Control-n>', lambda e: self.task_entry.focus())
self.root.bind('<Control-N>', lambda e: self.task_entry.focus())
self.root.bind('<Delete>', lambda e: self.delete_task())
self.root.bind('<Control-d>', lambda e: self.mark_completed())
self.root.bind('<Control-D>', lambda e: self.mark_completed())
self.root.bind('<F2>', lambda e: self.edit_task())
# 優先度変更のショートカット
self.root.bind('<Control-1>', lambda e: self.change_priority("高"))
self.root.bind('<Control-2>', lambda e: self.change_priority("普通"))
self.root.bind('<Control-3>', lambda e: self.change_priority("低"))
def change_priority(self, new_priority):
"""選択されたタスクの優先度を変更"""
selected = self.tree.selection()
if not selected:
messagebox.showwarning("警告", "優先度を変更するタスクを選択してください")
return
item = self.tree.item(selected[0])
task_text = item['values'][2] # タスク列
# タスクを見つけて優先度を変更
for todo in self.todos:
if todo['task'] == task_text:
old_priority = todo.get('priority', '普通')
todo['priority'] = new_priority
# 変更ログを追加(オプション)
if 'priority_history' not in todo:
todo['priority_history'] = []
todo['priority_history'].append({
'from': old_priority,
'to': new_priority,
'changed_at': datetime.now().strftime("%Y-%m-%d %H:%M")
})
break
self.save_todos()
self.refresh_list()
# 成功メッセージ
priority_symbols = {'高': '🔴', '普通': '🟡', '低': '🔵'}
messagebox.showinfo("成功", f"優先度を {priority_symbols.get(new_priority, '')} {new_priority} に変更しました")
def add_task(self):
task_text = self.task_entry.get().strip()
if not task_text:
messagebox.showwarning("警告", "タスクを入力してください")
return
# 重複チェック
if any(todo['task'] == task_text for todo in self.todos):
messagebox.showwarning("警告", "同じタスクが既に存在します")
return
priority = self.priority_var.get()
new_todo = {
'id': max([todo.get('id', 0) for todo in self.todos], default=0) + 1,
'task': task_text,
'completed': False,
'priority': priority,
'created': datetime.now().strftime("%Y-%m-%d %H:%M"),
'priority_history': [{
'from': None,
'to': priority,
'changed_at': datetime.now().strftime("%Y-%m-%d %H:%M")
}]
}
self.todos.append(new_todo)
self.task_entry.delete(0, tk.END)
self.priority_var.set("普通") # 優先度をリセット
self.save_todos()
self.refresh_list()
messagebox.showinfo("成功", "タスクを追加しました")
def edit_task(self):
selected = self.tree.selection()
if not selected:
messagebox.showwarning("警告", "編集するタスクを選択してください")
return
item = self.tree.item(selected[0])
current_task = item['values'][2] # タスク列のインデックスを修正
# 編集ダイアログ
new_task = simpledialog.askstring("タスク編集", "新しいタスク名:", initialvalue=current_task)
if new_task and new_task.strip() and new_task.strip() != current_task:
# 重複チェック
if any(todo['task'] == new_task.strip() for todo in self.todos):
messagebox.showwarning("警告", "同じタスクが既に存在します")
return
for todo in self.todos:
if todo['task'] == current_task:
todo['task'] = new_task.strip()
break
self.save_todos()
self.refresh_list()
messagebox.showinfo("成功", "タスクを編集しました")
def mark_completed(self):
selected = self.tree.selection()
if not selected:
messagebox.showwarning("警告", "タスクを選択してください")
return
item = self.tree.item(selected[0])
task_text = item['values'][2] # タスク列のインデックスを修正
for todo in self.todos:
if todo['task'] == task_text:
todo['completed'] = True
todo['completed_date'] = datetime.now().strftime("%Y-%m-%d %H:%M")
break
self.save_todos()
self.refresh_list()
def mark_pending(self):
selected = self.tree.selection()
if not selected:
messagebox.showwarning("警告", "タスクを選択してください")
return
item = self.tree.item(selected[0])
task_text = item['values'][2] # タスク列のインデックスを修正
for todo in self.todos:
if todo['task'] == task_text:
todo['completed'] = False
if 'completed_date' in todo:
del todo['completed_date']
break
self.save_todos()
self.refresh_list()
def delete_task(self):
selected = self.tree.selection()
if not selected:
messagebox.showwarning("警告", "タスクを選択してください")
return
if messagebox.askyesno("確認", "選択したタスクを削除しますか?"):
item = self.tree.item(selected[0])
task_text = item['values'][2] # タスク列のインデックスを修正
self.todos = [todo for todo in self.todos if todo['task'] != task_text]
self.save_todos()
self.refresh_list()
messagebox.showinfo("成功", "タスクを削除しました")
def clear_completed(self):
completed_count = sum(1 for todo in self.todos if todo['completed'])
if completed_count == 0:
messagebox.showinfo("情報", "完了済みのタスクはありません")
return
if messagebox.askyesno("確認", f"完了済みのタスク {completed_count} 件を削除しますか?"):
self.todos = [todo for todo in self.todos if not todo['completed']]
self.save_todos()
self.refresh_list()
messagebox.showinfo("成功", f"{completed_count} 件のタスクを削除しました")
def get_filtered_todos(self):
filtered_todos = self.todos.copy()
# フィルタリング
filter_value = self.filter_var.get()
if filter_value == "未完了のみ":
filtered_todos = [todo for todo in filtered_todos if not todo['completed']]
elif filter_value == "完了済みのみ":
filtered_todos = [todo for todo in filtered_todos if todo['completed']]
elif filter_value == "高優先度":
filtered_todos = [todo for todo in filtered_todos if todo.get('priority', '普通') == '高']
elif filter_value == "普通優先度":
filtered_todos = [todo for todo in filtered_todos if todo.get('priority', '普通') == '普通']
elif filter_value == "低優先度":
filtered_todos = [todo for todo in filtered_todos if todo.get('priority', '普通') == '低']
# 検索
search_text = self.search_var.get().lower() if hasattr(self, 'search_var') else ""
if search_text:
filtered_todos = [todo for todo in filtered_todos if search_text in todo['task'].lower()]
# ソート
sort_value = self.sort_var.get() if hasattr(self, 'sort_var') else "作成日時"
if sort_value == "優先度":
priority_order = {'高': 0, '普通': 1, '低': 2}
filtered_todos.sort(key=lambda x: priority_order.get(x.get('priority', '普通'), 1))
elif sort_value == "タスク名":
filtered_todos.sort(key=lambda x: x['task'].lower())
elif sort_value == "状態":
filtered_todos.sort(key=lambda x: x['completed'])
else: # 作成日時
filtered_todos.sort(key=lambda x: x['created'], reverse=True)
return filtered_todos
def refresh_list(self):
# リストをクリア
for item in self.tree.get_children():
self.tree.delete(item)
# フィルタリング・ソートされたタスクを取得
filtered_todos = self.get_filtered_todos()
# タスクを追加
for todo in filtered_todos:
status = "✓ 完了" if todo['completed'] else "○ 未完了"
priority = todo.get('priority', '普通')
# 優先度記号を追加
priority_symbol = {'高': '🔴', '普通': '🟡', '低': '🔵'}
priority_display = f"{priority_symbol.get(priority, '🟡')} {priority}"
# タグを設定(色分け用)
tags = []
if todo['completed']:
tags.append('completed')
else:
if priority == '高':
tags.append('high_priority')
elif priority == '低':
tags.append('low_priority')
else:
tags.append('normal_priority')
self.tree.insert('', tk.END,
values=(status, priority_display, todo['task'], todo['created']),
tags=tags)
self.update_status()
def update_status(self):
total = len(self.todos)
completed = sum(1 for todo in self.todos if todo['completed'])
pending = total - completed
high_priority = sum(1 for todo in self.todos if todo.get('priority', '普通') == '高' and not todo['completed'])
status_text = f"総タスク数: {total} | 完了: {completed} | 未完了: {pending}"
if high_priority > 0:
status_text += f" | 高優先度(未完了): {high_priority}"
self.status_label.config(text=status_text)
def load_todos(self):
if os.path.exists(self.data_file):
try:
with open(self.data_file, 'r', encoding='utf-8') as f:
todos = json.load(f)
# 既存のデータに優先度がない場合は追加
for todo in todos:
if 'priority' not in todo:
todo['priority'] = '普通'
return todos
except Exception as e:
messagebox.showerror("エラー", f"データの読み込みに失敗しました: {e}")
return []
return []
def save_todos(self):
try:
with open(self.data_file, 'w', encoding='utf-8') as f:
json.dump(self.todos, f, ensure_ascii=False, indent=2)
except Exception as e:
messagebox.showerror("エラー", f"保存に失敗しました: {e}")
if __name__ == "__main__":
root = tk.Tk()
app = TodoApp(root)
root.mainloop()
