Abstract
pythonで簡単なGUIを作成する機会があり、その際に日付の入力欄を作る必要があった。要件としては、入力欄は基本的には手打ちだが、カレンダー選択による自動入力機能を付けてほしいとうことだった。Webのノリで簡単にできるだろうと高をくくっていたら意外とはまってしまったので、誰の役に立つかわからないが残しておくことにする。
環境
下記環境で実装してみる。
- python 3.12.5
- Windows10, 11
下記の外部ライブラリを使用しました
- tkcalendar==1.6.1
要件
再利用性を考え下記の簡易要件でクラスを設計する。
- tkinterで呼び出すダイアログ
- ダイアログにカレンダーを表示する
- カレンダーが選択されたか確認するメンバーを有す
- カレンダー選択結果をstringsで取り出すメンバーを有す
- カレンダー選択結果をdatetimeで取り出すメンバーを有す
実装
calendar_dialog.py
import tkinter
from tkinter.simpledialog import Dialog
from typing import override
from datetime import datetime
from tkcalendar import Calendar
class CalendarDialog(Dialog):
__date: str
__calendar : Calendar
def __init__(self, master : tkinter.Tk, title=None) -> None:
"""カレンダーを選択し日付を得るダイアログを生成する
Args:
master (tkinter.Tk): 配置するコンポーネント
title (_type_, optional): _description_. Defaults to None.
"""
self.__date = ""
self.__calendar = None
super().__init__(parent=master, title=title)
@override
def body(self, master : tkinter.Tk) -> None:
"""ダイアログ内に配置するコンポーネント.今回はカレンダーを表示する。
Args:
master (tkinter.Tk): 配置するコンポーネント
"""
self.__calendar = Calendar(master, showweeknumbers=False,date_pattern="yyyy/mm/dd")
self.__calendar.grid(sticky="w", row=0, column=0)
@override
def apply(self) -> None:
"""OKが押された時の処理
"""
self.__date = self.__calendar.get_date()
def __str__(self) -> str:
return self.to_str()
def __repr__(self) -> str:
return str(self)
def to_str(self) -> str:
"""日付をで得る
Returns:
str: 日付
"""
return self.__date
def to_datetime(self) -> datetime:
"""日付を得る
Returns:
datetime: 日付
"""
return datetime.strptime(r"%Y/%m/%d")
def is_selected(self) -> bool:
"""カレンダーが選択されたか"""
return self.__date != ''
いろいろ方法はあると思うが、既存のダイアログをカスタマイズして、カレンダーから情報を返す方法が簡単そうだったので今回はこの方法をとる。
具体的にはtkinter.simpledialog.Dialog
を継承し、下記メンバを2つオーバライドすることで簡単に実現できる。
-
body()
ダイアログの本体を表示するメソッド -
apply()
OKボタンが押されたときに実行するメソッド
body()
でカレンダーを表示させ、OKボタンをトリガーに日付の値をself.__date
メンバーへ格納する。
公式いわくOKやCancelの表示が気に食わないひとはbuttonbox()をオーバーライドしてカスタマイズしてとのこと。
テスト
トップレベルにモジュールとtestコードを置くと仮定します。
calendar_dialog.py
test.py
test.py
import tkinter
from tkinter import Frame
from tkinter import Label
from tkinter import Entry
from tkinter import Button
from calendar_dialog import CalendarDialog
class CalendarDialogTest(tkinter.Tk):
__entry : Entry
def __init__(self, screenName: str | None = None, baseName: str | None = None, className: str = "Tk", useTk: bool = True, sync: bool = False, use: str | None = None) -> None:
super().__init__(screenName, baseName, className, useTk, sync, use)
self.geometry("200x200")
fream = Frame(self, bd=4, relief="groove")
label = Label(fream, text="Date")
label.grid(padx=2, sticky="w", row=0, column=0)
self.__entry = Entry(fream, width=20)
self.__entry.grid(sticky="w", row=1, column=0)
button = Button(fream,text="get",width=4,command=self.__get_date)
button.grid(sticky="w", row=1, column=1)
fream.pack()
def __get_date(self) -> None:
"""コールバック"""
dialog = CalendarDialog(self, title="calendar")
if dialog.is_selected():
self.__entry.delete(0, tkinter.END)
self.__entry.insert(0,dialog.to_str())
if __name__ == '__main__':
CalendarDialogTest().mainloop()
実行可能なテストコードを示す。
手打ち入力とカレンダー入力に対応した入力欄を作ることができた。