8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Fletを試す(2) - 再利用できるようにコンポーネント化

Posted at

はじめに

PythonでFlutterアプリが作れるフレームワーク「Flet」の検証をしました。
FletではControl(画面要素)をコンポーネント化して再利用しやすくできるようだったので、その方法を試してみることにしました。

Fletの公式サイトはこちら。
https://flet.dev/

参考)過去記事はこちら。

前提事項

  • 「Flet」に関する詳細は省略
  • インストール方法、実行方法、デプロイ方法なども省略
  • 試した環境
    • Windows10
    • Python 3.10.8
    • Flet 0.3.2

概要

  • Fletでログインフォームを作り
  • それをコンポーネント化してみる
  • 併せてイベントの実装方法も確認する

最終形を「Fly.io」にデプロイしたものが以下です。
https://flet-report02.fly.dev/#/
Animation.gif

個人的な感想は以下。

  • きちんと作るならコンポーネント化は必須になると思う
  • 簡単なツールだったら、main に全部書いていっても良いと思う
  • 今後の課題
    • 作った class にプロパティやイベントを持たせる形にできないのか
    • 日本語が最初☒の表示になるのは何とかならないのか

詳細

基本のソース構成

import flet as ft


class Component(ft.UserControl):
    """sample"""

    def __init__(self):
        super().__init__()

    def build(self):
        return ft.Card(
            ft.Container(
                ft.Column(
                    [
                        ft.Text("sample"),
                        ft.TextField(),
                        ft.ElevatedButton("button"),
                    ],
                    alignment=ft.MainAxisAlignment.CENTER,
                ),
                width=400,
                padding=30,
            ),
        )


def main(page: ft.Page):

    component1 = Component()
    component2 = Component()
    component3 = Component()

    # Page レイアウト
    page.title = "sample"
    page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
    page.vertical_alignment = ft.MainAxisAlignment.CENTER
    page.add(component1)
    page.add(component2)
    page.add(component3)


if __name__ == "__main__":
    ft.app(target=main)

コンポーネント化する場合の基本のソース構成は↑。

  • UserControl を継承してクラスを作る
  • メソッド build(self) でコンポーネントの中身を作って return する
  • コンポーネントを使う方は、作ったクラスを Control の1つとして生成してレイアウトに組み込む

サンプルでは、Text、TextField、ElevatedButtonをまとめてコンポーネント化しています。
mainでコンポーネントを3つ生成してそのまま配置。

↓がこれを実行した結果です。
同じものが3つ配置され、コンポーネントを変えれば3か所とも変わるような作りになります。
image.png

ログインフォームをコンポーネント化(レイアウトのみ)

ログインフォームをコンポーネント化してみたものが↓のサンプルコード。
ID、Passwordを入力してログインボタンを押すという想定でControlを配置。

実行結果が以下です。
Password入力用のプロパティがあるので、簡単に伏字にできます。
Animation.gif

サンプルコードはこちら
import flet as ft

class FormLogin(ft.UserControl):
    """Login フォーム"""

    def __init__(self):
        super().__init__()

    def build(self):
        # I/O Controls
        self.tfLoginid = ft.TextField(label="Login ID")
        self.tfPassword = ft.TextField(
            label="Password", password=True, can_reveal_password=True)
        self.btnLogin = ft.ElevatedButton("ログイン")

        # Content
        header = ft.Container(
            ft.Text("ログイン"),
        )
        body = ft.Column(
            [
                self.tfLoginid,
                self.tfPassword,
            ],
        )
        footer = ft.Row(
            [
                self.btnLogin,
            ],
        )

        return ft.Card(
            ft.Container(
                ft.Column(
                    [
                        header,
                        body,
                        footer,
                    ],
                    alignment=ft.MainAxisAlignment.CENTER,
                ),
                width=400,
                padding=30,
            ),
        )


def main(page: ft.Page):

    # I/O Controls
    fromLogin = FormLogin()

    # Page レイアウト
    page.title = "Login form"
    page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
    page.vertical_alignment = ft.MainAxisAlignment.CENTER
    page.add(fromLogin)


if __name__ == "__main__":
    ft.app(target=main)

デザイン調整とイベント処理追加

さらにデザイン調整とイベント処理を追加したものが↓のサンプルコード。
「ログイン」ボタンを押したときに main にある login_auth() が呼ばれるようにしてあります。
簡易的に「hoge/hoge」で「ログイン成功」と snack_bar が出るようにしてあります。

実行結果です。
Animation.gif

サンプルコードはこちら
import flet as ft


class FormLogin(ft.UserControl):
    """Login フォーム"""

    # const
    CONTENT_WIDTH = 400
    CONTENT_HIGHT = 400

    def __init__(self, func_login_auth=None):
        self.func_login_auth = func_login_auth
        super().__init__()

    def build(self):
        # I/O Controls
        self.tfLoginid = ft.TextField(label="Login ID", autofocus=True)
        self.tfPassword = ft.TextField(
            label="Password", password=True, can_reveal_password=True)
        self.btnLogin = ft.ElevatedButton(
            "ログイン", on_click=self.login_clicked)

        # Content
        header = ft.Container(
            ft.Text("ログイン", style=ft.TextThemeStyle.DISPLAY_MEDIUM, weight=ft.FontWeight.BOLD,
                    width=self.CONTENT_WIDTH, text_align=ft.TextAlign.CENTER),
            margin=ft.margin.only(bottom=20),
        )
        body = ft.Column(
            [
                self.tfLoginid,
                self.tfPassword,
            ],
        )
        footer = ft.Row(
            [
                self.btnLogin,
            ],
            alignment=ft.MainAxisAlignment.CENTER,
        )

        return ft.Card(
            ft.Container(
                ft.Column(
                    [
                        header,
                        body,
                        ft.Divider(),
                        footer,
                    ],
                    alignment=ft.MainAxisAlignment.CENTER,
                ),
                width=self.CONTENT_WIDTH,
                height=self.CONTENT_HIGHT,
                padding=30,
            ),
        )

    def login_clicked(self, e):
        if self.func_login_auth:
            loginid = self.tfLoginid.value
            password = self.tfPassword.value
            self.func_login_auth(loginid, password)


def main(page: ft.Page):

    def login_auth(loginid, password):
        if loginid == "hoge" and password == "hoge":
            txtMesssage.value = f"ログイン成功! {loginid} {password}"
            txtMesssage.color = ft.colors.GREEN
        else:
            txtMesssage.value = f"ログイン失敗! {loginid} {password}"
            txtMesssage.color = ft.colors.RED
        page.snack_bar.open = True
        page.update()

    # I/O Controls
    fromLogin = FormLogin(func_login_auth=login_auth)
    txtMesssage = ft.Text(size=30)

    # Page レイアウト
    page.title = "Login form"
    page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
    page.vertical_alignment = ft.MainAxisAlignment.CENTER
    page.add(fromLogin)
    page.snack_bar = ft.SnackBar(txtMesssage)


if __name__ == "__main__":
    ft.app(target=main)

ソース最終形

せっかくコンポーネントにしたので、モーダルダイアログでログインフォームを出すボタンを下に追加。
モーダルダイアログも簡単に出せることを確認できました。

実行結果は概要に貼ってあるものです。

サンプルコードはこちら
import flet as ft


class FormLogin(ft.UserControl):
    """Login フォーム"""

    # const
    CONTENT_WIDTH = 400
    CONTENT_HIGHT = 400

    def __init__(self, func_login_auth=None):
        self.func_login_auth = func_login_auth
        super().__init__()

    def build(self):
        # I/O Controls
        self.tfLoginid = ft.TextField(label="Login ID", autofocus=True)
        self.tfPassword = ft.TextField(
            label="Password", password=True, can_reveal_password=True)
        self.btnLogin = ft.ElevatedButton(
            "ログイン", on_click=self.login_clicked)

        # Content
        header = ft.Container(
            ft.Text("ログイン", style=ft.TextThemeStyle.DISPLAY_MEDIUM, weight=ft.FontWeight.BOLD,
                    width=self.CONTENT_WIDTH, text_align=ft.TextAlign.CENTER),
            margin=ft.margin.only(bottom=20),
        )
        body = ft.Column(
            [
                self.tfLoginid,
                self.tfPassword,
            ],
        )
        footer = ft.Row(
            [
                self.btnLogin,
            ],
            alignment=ft.MainAxisAlignment.CENTER,
        )

        return ft.Card(
            ft.Container(
                ft.Column(
                    [
                        header,
                        body,
                        ft.Divider(),
                        footer,
                    ],
                    alignment=ft.MainAxisAlignment.CENTER,
                ),
                width=self.CONTENT_WIDTH,
                height=self.CONTENT_HIGHT,
                padding=30,
            ),
        )

    def login_clicked(self, e):
        if self.func_login_auth:
            loginid = self.tfLoginid.value
            password = self.tfPassword.value
            self.func_login_auth(loginid, password)


def main(page: ft.Page):

    def open_login(e):
        dlgLogin.open = True
        page.update()

    def close_login(e):
        dlgLogin.open = False
        page.update()

    def login_auth(loginid, password):
        if loginid == "hoge" and password == "hoge":
            txtMesssage.value = f"ログイン成功! {loginid} {password}"
            txtMesssage.color = ft.colors.GREEN
        else:
            txtMesssage.value = f"ログイン失敗! {loginid} {password}"
            txtMesssage.color = ft.colors.RED
        page.snack_bar.open = True
        page.update()

    # I/O Controls
    fromLogin = FormLogin(func_login_auth=login_auth)
    txtMesssage = ft.Text(size=30)
    # Add Dialog
    btnLoginDialog = ft.ElevatedButton("ログイン Dialog", on_click=open_login)
    dlgLogin = ft.AlertDialog(
        modal=True,
        title=ft.Text("Login"),
        content=FormLogin(func_login_auth=login_auth),
        actions=[ft.TextButton("Cancel", on_click=close_login)],
        actions_alignment=ft.MainAxisAlignment.END,
    )

    # Page レイアウト
    page.title = "Login form"
    page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
    page.vertical_alignment = ft.MainAxisAlignment.CENTER
    page.add(fromLogin)
    page.add(ft.Divider())
    page.snack_bar = ft.SnackBar(txtMesssage)
    # Add Dialog
    page.add(btnLoginDialog)
    page.dialog = dlgLogin


if __name__ == "__main__":
    ft.app(target=main)

以上です。

8
10
4

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
8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?