LoginSignup
4
7

More than 1 year has passed since last update.

Fletを試す(2)-2 - 日付入力コンポーネントを作成してみる

Last updated at Posted at 2023-01-23

はじめに

↑のコンポーネント化の話に関連して、日付入力コンポーネントを作成してみました。
pythonのclassの書き方がきちんと分かっていないので、その辺は参考程度に見てください。

動作イメージは↓。
Animation.gif

こちらで実際の動きを見れます。
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)
4
7
2

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
4
7