【備忘録】Fletでモーダル画面を各ボタンで表示できるようにする

# １．初めに

本記事ではモーダル画面を複数表示するコードについて紹介します。基本ベースは[公式ドキュメント](https://flet.dev/docs/controls/alertdialog)から作成し、これをclassに焼き直しました。その際に最も重要なことはclassコンポーネントに`Page`を渡すことでした。

実行環境

* Python : 3.13.1
* Flet : 0.25.2

# ２．クラスコンポーネント化

公式ドキュメントでは`main`関数内で

 * `AlertDialog`を作成（以下、`dlg_modal`と表現）
 * 作成した`dlg_modal`は以下の関数で表示／非表示
 　* 表示：`page.open(dlg_modal)`
 　* 非表示：`page.close(dlg_modal)`
 * `ElevatedButton`を作成し、クリックイベントに`page.open(dlg_modal)`を設定（引数を設定するためラムダ式を使用）
 * `main`関数内でモーダルを閉じるイベントを定義

といったことをしています。これを、コンポーネント化しました。（モーダルを表示／非表示するイベントを登録した`ElevatedButton`の自作class）このclassの作成に当たり重要なことは`page`をclassの引数に渡すことです。（モーダルの表示／非表示を行うため。）

元ネタは[こちら](https://flet.dev/docs/controls/alertdialog)をご参照いただき、クラス化した結果が以下の通りです。

```python
class My_Dialog(ElevatedButton):
    def __init__(self, page: Page, task_id: int, task_name: str):
        super().__init__()
        self.page = page
        self.task_id = task_id
        self.task_name = task_name
        self.text = f'Open modal Dialog {self.task_id}'
        self.dlg_modal = AlertDialog(
            modal=True,
            title=Text(f'Please Confirm #{self.task_id}'),
            content=Text(f'Task : {self.task_name}'),
            actions=[
                ElevatedButton(f'Yes #{self.task_id}', on_click=self.handle_event),
                ElevatedButton(f'No #{self.task_id}', on_click=self.handle_event),
            ],
            actions_alignment=MainAxisAlignment.END,
        )
        self.on_click = lambda e: self.page.open(self.dlg_modal)

    def handle_event(self, e) -> None:
        self.page.close(self.dlg_modal)
```

（クラスを定義するときに、よく忘れるのがコンストラクタ内の`super().__init__()`ですね。）
上記コンポーネントではモーダルを表示／非表示のみのイベント登録をしていませんが、データ編集やデータ削除機能などを搭載するのが想定される用途です。

# ３．作成物

コンポーネント化されたコードを用いた例を以下に記載します。

```python
import flet as ft
from flet import (
    AlertDialog,
    Column,
    CrossAxisAlignment,
    ElevatedButton,
    MainAxisAlignment,
    Page,
    Text,
)

# テストデータ
tasks = [
    {'id': 1, 'task_name': 'Sample1'},
    {'id': 2, 'task_name': 'Sample2'},
    {'id': 3, 'task_name': 'Sample3'},
    {'id': 4, 'task_name': 'Sample4'},
    {'id': 5, 'task_name': 'Sample5'},
    {'id': 6, 'task_name': 'Sample6'},
    {'id': 7, 'task_name': 'Sample7'},
    {'id': 8, 'task_name': 'Sample8'},
]


class My_Dialog(ElevatedButton):
    def __init__(self, page: Page, task_id: int, task_name: str):
        super().__init__()
        self.page = page
        self.task_id = task_id
        self.task_name = task_name
        self.text = f'Open modal Dialog {self.task_id}'
        self.dlg_modal = AlertDialog(
            modal=True,
            title=Text(f'Please Confirm #{self.task_id}'),
            content=Text(f'Task : {self.task_name}'),
            actions=[
                ElevatedButton(f'Yes #{self.task_id}', on_click=self.handle_event),
                ElevatedButton(f'No #{self.task_id}', on_click=self.handle_event),
            ],
            actions_alignment=MainAxisAlignment.END,
        )
        self.on_click = lambda e: self.page.open(self.dlg_modal)

    def handle_event(self, e) -> None:
        self.page.close(self.dlg_modal)


def main(page: Page):
    page.title = 'My Dialogs'
    page.window.width = 600
    page.horizontal_alignment = CrossAxisAlignment.CENTER
    # モーダル設定済みのボタンを縦に並べる
    modal_btns = Column()
    for task in tasks:
        tmp_modal = My_Dialog(page, task.get('id'), task.get('task_name'))
        modal_btns.controls.append(tmp_modal)
    page.add(modal_btns)


ft.app(target=main)
```

上記コードを実行し、ボタンを押下すると以下のようにモーダル画面が表示されます。（2個目のボタンを押下しています。）

![スクリーンショット 2025-01-15 222923.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3275023/ec304a81-3a47-be39-7bf4-ca5d7c378cd2.png)

# 最後に

比較的簡単にモーダルを表示させることができました。
試してはいませんが、モーダル画面の中でさらにモーダル画面の表示なんかできるのでしょうかね？（機会があれば試してみようと思います。）