はじめに
↑のコンポーネント化の話に関連して、日付入力コンポーネントを作成してみました。
pythonのclassの書き方がきちんと分かっていないので、その辺は参考程度に見てください。
こちらで実際の動きを見れます。
https://flet-sample-datepicker.fly.dev/
個人的な感想。
- コンポーネント化は、結構、簡単にできるような気がする
- 慣れれば、画面のレイアウト調整も楽なのではないかと思う
- 恐らくもう少し経てば公式のControlがもっと増えていくでしょう
前提事項
- 試した環境
- Windows10
- Python 3.10.8
- Flet 0.3.2
コンポーネントの仕様(ざっくり)
- カレンダー表示をするコンポーネント
- 初期表示は当日を選択した状態とする
- 日付クリックでon_selectedイベント実行する(処理内容は外部から指定してもらう)
- ヘッダに年月と左右の矢印、「今日」ボタンを配置
- 矢印クリックでカレンダーを前月と次月に書き換える
- 「今日」ボタンクリックで当日を選択した状態に戻す
サンプルコード
コンポーネントとコンポーネント利用のサンプルコードは以下です。
カレンダーアイコンとテキスト入力欄を配置。
その下に作ったコンポーネントを配置。
アイコンクリックでカレンダーを表示/非表示するようにしてあります。
日付を選択したときに、日付をコンポーネントからもらって入力欄に設定し、カレンダーを非表示にしています。
少し変えればポップアップ形式にもできると思います。
実際に利用するには、入力欄+カレンダーでコンポーネントにしてしまう方が良い気がします。
import flet as ft
import datetime
from dateutil.relativedelta import relativedelta
import calendar
import itertools
class DatePiker(ft.UserControl):
"""日付選択"""
def __init__(self, on_selected=None):
super().__init__()
# 初期値の日付
self.default_date = datetime.date.today()
# カレンダー表示年月
self.yearmonth: datetime = datetime.date(
self.default_date.year, self.default_date.month, 1)
# 選択している日付
self.selected_date: datetime = self.default_date
# 日付選択時の処理
self.on_selected = on_selected
def build(self):
DAY_WIDTH = 36
DAY_SPACING = 4
def updateCalender():
"""カレンダーの中身を更新する"""
# ヘッダの年月設定
self.txt_yearmonth.value = self.yearmonth.strftime("%Y/%m")
# 日付のボタンを全部クリア
for idx in range(42): # 7日×6週
self.btn_days[idx].text = "-"
self.btn_days[idx].disabled = True
self.btn_days[idx].style = ft.ButtonStyle(
padding=0, bgcolor=ft.colors.BACKGROUND)
# カレンダー情報取得
list_cal = list(
itertools.chain.from_iterable(
calendar.monthcalendar(self.yearmonth.year, self.yearmonth.month)))
# カレンダー情報を日付のボタンに設定
idx = 0
for day in list_cal:
if day > 0:
self.btn_days[idx].text = day
self.btn_days[idx].disabled = False
if datetime.date(self.yearmonth.year, self.yearmonth.month, day) == self.selected_date:
self.btn_days[idx].style = ft.ButtonStyle(
padding=0, bgcolor=ft.colors.TRANSPARENT)
idx = idx + 1
def prev_clicked(e):
self.yearmonth = self.yearmonth - relativedelta(months=1)
updateCalender()
self.update()
def next_clicked(e):
self.yearmonth = self.yearmonth + relativedelta(months=1)
updateCalender()
self.update()
def today_clicked(e):
self.yearmonth = datetime.date.today()
self.selected_date = datetime.date.today()
updateCalender()
self.update()
def day_clicked(e):
day = e.control.text
self.selected_date = datetime.date(
self.yearmonth.year, self.yearmonth.month, day)
updateCalender()
self.update()
if self.on_selected:
self.on_selected()
# I/O Controls
self.btn_prev = ft.IconButton(
icon=ft.icons.ARROW_BACK, on_click=prev_clicked
)
self.btn_next = ft.IconButton(
icon=ft.icons.ARROW_FORWARD, on_click=next_clicked
)
self.txt_yearmonth = ft.Text(
self.yearmonth.strftime("%Y/%m"), size=24, weight=ft.FontWeight.BOLD
)
self.btn_today = ft.ElevatedButton(
"今日", on_click=today_clicked
)
self.btn_days = []
week_cols = []
for week in range(6):
# 6週分の行を追加
calenderRows = []
for day in range(7):
# 7日分のボタンを追加
btn_day = ft.ElevatedButton(
width=DAY_WIDTH, style=ft.ButtonStyle(padding=0), on_click=day_clicked)
self.btn_days.append(btn_day)
calenderRows.append(btn_day)
week_cols.append(
ft.Row(
controls=calenderRows,
alignment=ft.MainAxisAlignment.CENTER,
spacing=DAY_SPACING,
)
)
updateCalender()
return ft.Column(
[
# ヘッダ
ft.Row(
[
self.btn_prev, self.txt_yearmonth, self.btn_next, self.btn_today,
],
alignment=ft.MainAxisAlignment.CENTER,
),
# カレンダーヘッダ
ft.Row(
[
ft.Text("月", width=DAY_WIDTH,
text_align=ft.TextAlign.CENTER),
ft.Text("火", width=DAY_WIDTH,
text_align=ft.TextAlign.CENTER),
ft.Text("水", width=DAY_WIDTH,
text_align=ft.TextAlign.CENTER),
ft.Text("木", width=DAY_WIDTH,
text_align=ft.TextAlign.CENTER),
ft.Text("金", width=DAY_WIDTH,
text_align=ft.TextAlign.CENTER),
ft.Text("土", width=DAY_WIDTH,
text_align=ft.TextAlign.CENTER, color=ft.colors.BLUE),
ft.Text("日", width=DAY_WIDTH,
text_align=ft.TextAlign.CENTER, color=ft.colors.RED),
],
alignment=ft.MainAxisAlignment.CENTER,
spacing=DAY_SPACING,
),
# カレンダー内容
ft.Row(
[
ft.Column(
controls=week_cols,
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
]
)
def update(self):
super().update()
def main(page: ft.Page):
def btn_calender_clicked(e):
card.visible = not card.visible
page.update()
def date_selected():
tf_date.value = datepiker.selected_date.strftime("%Y/%m/%d")
card.visible = False
page.update()
# I/O Controls
tf_date = ft.TextField()
btn_calender = ft.IconButton(
icon=ft.icons.CALENDAR_TODAY, on_click=btn_calender_clicked)
datepiker = DatePiker(on_selected=date_selected)
card = ft.Card(
ft.Container(
datepiker,
margin=10,
width=300,
),
visible=True,
)
# Page レイアウト
page.theme_mode = ft.ThemeMode.LIGHT
page.add(
ft.Column(
[
ft.Row(
[
btn_calender,
tf_date,
]
),
card,
ft.Text("↑日付をカレンダーで入力できる"),
]
)
)
if __name__ == "__main__":
ft.app(target=main)