0. 初めに
みなさん、TKinterしてますか?
僕はしてます
TKinterとはPythonの標準ライブラリで簡単にGUIアプリを作成することができます
細かいチュートリアルなどはこちらが分かりやすかったので参考にさせていただきました
https://qiita.com/nnahito/items/ad1428a30738b3d93762
1. なぜ作ったか
これはある日僕が仕事での業務アプリを作っている時のこと、カレンダーのポップアップが出てきて日付を複数選択するUIを作る必要が出てきました
色々調べた結果、TKinterにはカレンダーを表示するライブラリがありました。
しかし、そちらを使おうとするとどうしても複数選択にバインドすることができない。
ないなら自分で作れ。エンジニアとしての本能が僕を駆り立てて今回作成にあたりました。
2. 実装
import tkinter as tk
import calendar
from datetime import datetime, timedelta
def get_dates_and_weekdays(year, month):
# 月の最初の日を取得
start_date = datetime(year, month, 1)
# 翌月の最初の日を計算し、その1日前(月の最後の日)を取得
if month == 12:
end_date = datetime(year + 1, 1, 1) - timedelta(days=1)
else:
end_date = datetime(year, month + 1, 1) - timedelta(days=1)
# 月の全ての日付と曜日を含むリストを作成
dates_and_weekdays = []
current_date = start_date
while current_date <= end_date:
weekday_name = calendar.day_name[current_date.weekday()]
dates_and_weekdays.append((current_date.strftime("%Y-%m-%d"), weekday_name))
current_date += timedelta(days=1)
return dates_and_weekdays
class MultiSelectCalendar(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.create_grid()
self.create_widgets()
self.date_list = []
def create_grid(self):
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=4)
self.grid_rowconfigure(2, weight=1)
def create_widgets(self):
self.top = tk.Frame(self)
self.current_year = datetime.now().year
self.current_month = datetime.now().month
self.current_day = datetime.now().day
self.year_choose = tk.IntVar()
self.month_choose = tk.IntVar()
self.year_choose.set(self.current_year)
self.month_choose.set(self.current_month)
self.year_spinbox = tk.Spinbox(self.top, from_=self.current_year-5, to=self.current_year+10, textvariable=self.year_choose, width=5,command =self.fill_day)
self.year_spinbox.pack(side="left")
self.year_label = tk.Label(self.top, text="年")
self.year_label.pack(side="left")
self.month_spinbox = tk.Spinbox(self.top, from_=1, to=12, textvariable=self.month_choose, width=5,command = self.fill_day)
self.month_spinbox.pack(side="left")
self.month_label = tk.Label(self.top, text="月")
self.month_label.pack(side="left")
self.top.grid(row=0, column=0, sticky="nsew")
self.calender = tk.Canvas(self, width=500, height=500)
self.calender.bind("<Button-1>", self.select_day)
self.create_calender()
self.fill_day()
self.calender.grid(row=1, column=0, sticky="nsew")
self.exit_button = tk.Button(self, text="閉じる", command=self.close)
self.exit_button.grid(row=2, column=0, sticky="nsew")
def close(self):
self.master.destroy()
def create_calender(self):
self.calender.create_rectangle(0, 0, 490, 490, fill="white")
self.calender.create_line(0, 70, 490, 70)
self.calender.create_line(0, 140, 490, 140)
self.calender.create_line(0, 210, 490, 210)
self.calender.create_line(0, 280, 490, 280)
self.calender.create_line(0, 350, 490, 350)
self.calender.create_line(0, 420, 490, 420)
self.calender.create_line(70, 0, 70, 490)
self.calender.create_line(140, 0, 140, 490)
self.calender.create_line(210, 0, 210, 490)
self.calender.create_line(280, 0, 280, 490)
self.calender.create_line(350, 0, 350, 490)
self.calender.create_line(420, 0, 420, 490)
self.calender.create_text(35, 35, text="日", font=("Helvetica", 20), fill="red")
self.calender.create_text(105, 35, text="月", font=("Helvetica", 20))
self.calender.create_text(175, 35, text="火", font=("Helvetica", 20))
self.calender.create_text(245, 35, text="水", font=("Helvetica", 20))
self.calender.create_text(315, 35, text="木", font=("Helvetica", 20))
self.calender.create_text(385, 35, text="金", font=("Helvetica", 20))
self.calender.create_text(455, 35, text="土", font=("Helvetica", 20), fill="blue")
def fill_day(self):
self.calender.delete("all")
self.create_calender()
self.year = int(self.year_spinbox.get())
self.month =int(self.month_spinbox.get())
day_list = get_dates_and_weekdays(self.year,self.month)
week_count = 0
for day in day_list:
if day[1] == "Sunday":
self.calender.create_text(35, 105+70*week_count, text=day[0][8:], font=("Helvetica", 20), fill="red")
if day[1] == "Monday":
self.calender.create_text(105, 105+70*week_count, text=day[0][8:], font=("Helvetica", 20))
if day[1] == "Tuesday":
self.calender.create_text(175, 105+70*week_count, text=day[0][8:], font=("Helvetica", 20))
if day[1] == "Wednesday":
self.calender.create_text(245, 105+70*week_count, text=day[0][8:], font=("Helvetica", 20))
if day[1] == "Thursday":
self.calender.create_text(315, 105+70*week_count, text=day[0][8:], font=("Helvetica", 20))
if day[1] == "Friday":
self.calender.create_text(385, 105+70*week_count, text=day[0][8:], font=("Helvetica", 20))
if day[1] == "Saturday":
self.calender.create_text(455, 105+70*week_count, text=day[0][8:], font=("Helvetica", 20), fill="blue")
week_count += 1
def choose_day(self,x,y):
x = x//70
y = y//70-1
day = self.calender.itemcget(self.calender.find_closest(x*70+35, y*70+105)[0], "text")
if not day.isdigit():
return
date = f"{self.year}-{self.month}-{day:02}"
if date in self.date_list:
self.date_list.remove(date)
self.calender.itemconfig(self.calender.find_closest(x*70+35,y*70+105)[0], fill = "black")
else:
self.date_list.append(date)
self.calender.itemconfig(self.calender.find_closest(x*70+35,y*70+105)[0], fill = "green")
print(self.date_list)
return self.date_list
def select_day(self, event):
self.choose_day(event.x, event.y)
print(sorted(self.date_list))
クラスにて定義してます。必要なさいにインスタンス生成するとポップアップが表示されます
root = tk.Tk()
app = MultiSelectCalendar(root)
root.mainloop()
3. 結果
使用イメージです。
選択した日付の文字は緑に表示されています。
上のスピンボックスにて年、月を指定します。
4. 感想
今回初めてGUIアプリを設計、実装しています。作る側になって初めて優れたUIとは何か、という視点を獲得できました。説明書なしでも簡単に扱えるアプリ、そんなアプリを作れるように日々邁進していきます