見出し画像

Flet チュートリアル(Todoアプリ②)

前回に引き続きFletの公式ページのチュートリアルにある、Todoアプリの作成を進めていきながら、Fletを理解していきたいと思います。

タスクエリアに編集・削除ボタン・機能を追加

1.構造・要件を理解する

まずは、公式ページにあるこの図をよく見てみましょう。

2つ目の白い点線エリア内のタスク表示エリアの構造の説明

僕も英語が得意な訳ではないですが、分からないなりに色々見ていると出てくる単語は割と共通しているのでいつの間にか違和感なく眺められるようになります。(読むではない・・・笑)

①"view" コラム は"display_view" と "edit_view" の2つの行を持つコンテナです。
②"display_view" 行は、デフォルトは表示;
 "Edit"が押されると非表示になる。
③"edit_view" 行は、デフォルトは非表示;
   "Edit" が押されると表示される。

Flet 公式ページ>Docs>Tutorials>Python-To-Do app より

編集・削除ができるタスク表示の状態と、タスクを編集して保存する二つのUIが欲しいけど、両方同時に出す必要ないから、表示状態を切り替えることで実現するということですね。

そして、この後のコードにも出てきますがそれは"visible"というパラメータをTrueまたはFalseとすることでコントロールできるようです。

これは結構使いそうですね。

2. コードを確認する

ちょっと「うぇっ」となる方もいるかも知れませんが、1行1行大体何をしているのか見ていけば大抵大丈夫です。(分からない時もありますが)

class Task(ft.UserControl):
        #①コンストラクタ
    def __init__(self, task_name): 
        super().__init__()
        self.task_name = task_name

        #②ビルドメソッド
    def build(self): 
        self.display_task = ft.Checkbox(value=False, label=self.task_name)
        self.edit_name = ft.TextField(expand=1)

                #③ディスプレイビューメソッド
        self.display_view = ft.Row( 
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.display_task,
                ft.Row(
                    spacing=0,
                    controls=[
                        ft.IconButton(
                            icon=ft.icons.CREATE_OUTLINED,
                            tooltip="Edit To-Do",
                            on_click=self.edit_clicked,
                        ),
                        ft.IconButton(
                            ft.icons.DELETE_OUTLINE,
                            tooltip="Delete To-Do",
                            on_click=self.delete_clicked,
                        ),
                    ],
                ),
            ],
        ) 

                #④エディットビューメソッド
        self.edit_view = ft.Row(
            visible=False,
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.edit_name,
                ft.IconButton(
                    icon=ft.icons.DONE_OUTLINE_OUTLINED,
                    icon_color=ft.colors.GREEN,
                    tooltip="Update To-Do",
                    on_click=self.save_clicked,
                ),
            ],
        )
                #②ビルド
        return ft.Column(controls=[self.display_view, self.edit_view])
        
        #⑤エディットボタン押下メソッド
    def edit_clicked(self, e):
        self.edit_name.value = self.display_task.label
        self.display_view.visible = False
        self.edit_view.visible = True
        self.update()

        #⑥保存ボタン押下メソッド
    def save_clicked(self, e):
        self.display_task.label = self.edit_name.value
        self.display_view.visible = True
        self.edit_view.visible = False
        self.update()

(1) 分解して理解する

#①〜⑥で切り分けてみました。

(2) オブジェクト指向とFletのUserControlについて

①・②はUser_controlといって前回サラッと流してしまいましたが、自分で作成した部品(コンポーネント)を既に存在するFletのコントロールのように再利用するために定義する物です。

①では、コンストラクタにself.task_nameというプロパティを持たせて、クラス作成時に初期化するように定義しています。

def __init__(self, task_name);
   super().__init__()
   self.task_name = task_name

"__init__"というのはクラスにおいて、コンストラクタを定義する際の記述で引数にself(自分自身)と、task_nameという変数を渡しています。

super().__init__()という記述はスーパークラスを呼び出して初期化するための物です。

少し話が脱線しますが、「オブジェクト指向」といって、クラスに部品(コンポーネント)の設計図を作成しておくと、サブクラスという子供のようなものを自身の属性や機能を引き継ぎつつ、新しい属性や機能を足して作成することができます。
差分のみを記載すればいいので、コーディングもそうですし、複数箇所で同じ部品を使い回しできるため効率的に開発・保守ができます。

渡されたtask_nameは、self.task_name(自身のtask_name)に格納されています。

②ではUser Controlにおいて、作成する部品を定義する部分です。

User Controlを用いない場合は、以下のような骨組みでした。

def main(page: flet.Page):
    
    ・・・(処理や部品の定義)・・・
    
    page.add(部品)

ft.app(target=main)

UserControlを用いる場合は、以下のような骨組みとなります。

class (クラス名)(ft.UserContrl):
    def build(self):
       
        ・・・(作成するコントロール)・・・
        
        return ft.Row([
                        (部品),
                        (部品)
                                             ]
                              )

(3) Layoutの記載方法・プロパティ

①タスク名と編集・削除ボタン

間に入っている③・④は、部品の定義をしているんだなーとなんとなく分かると思いますが、具体的に見ていきましょう。

                #③ディスプレイビューメソッド
        self.display_view = ft.Row( 
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.display_task,
                ft.Row(
                    spacing=0,
                    controls=[
                        ft.IconButton(
                            icon=ft.icons.CREATE_OUTLINED,
                            tooltip="Edit To-Do",
                            on_click=self.edit_clicked,
                        ),
                        ft.IconButton(
                            ft.icons.DELETE_OUTLINE,
                            tooltip="Delete To-Do",
                            on_click=self.delete_clicked,
                        ),
                    ],
                ),
            ],
        ) 

self.display_viewという変数に、ft.Row(引数たち)を格納しているのが分かります。

引数たちの内訳として、alignment(横並び)というプロパティにはスペースを間に挟んで横並びにしてねと定義しています。
vertical_alignmentというプロパティ(横並び時の縦方向の配置定義)では、真ん中にそろうよう定義しています。

プロパティの定義は公式ページのこちらで確認可能です。

controlsにはリスト型で複数のパーツが渡されていますが、ビルドのところで定義したself.display_taskでタスク名とチェックボックスを、Rowメソッドの配下に更にcontrolsとして編集ボタン(アイコン+説明+クリック時の動作)と、削除ボタン(アイコン+説明+クリック時の動作)を格納しています。

②編集用画面パーツ

タスク名の編集を行うedit_viewは、①のEditボタンが押されるまでは非表示で、ボタンが押されると表示され、タスク名が編集されたら結果を保存できるようSaveボタンも保有します。

        self.edit_view = ft.Row(
            visible=False,
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.edit_name,
                ft.IconButton(
                    icon=ft.icons.DONE_OUTLINE_OUTLINED,
                    icon_color=ft.colors.GREEN,
                    tooltip="Update To-Do",
                    on_click=self.save_clicked,
                ),
            ],
        )
        return ft.Column(controls=[self.display_view, self.edit_view])

なので、visibleはFalseとなっています。
編集のためのテキストフィールドである、self.edit_nameと、save_clickedメソッドを保有するsaveアイコンをcontrols配列として保持します。

edit_clickedメソッドと、save_clickedメソッドを定義し、それぞれのボタンの処理(on_click = 〜 )として渡してあげればOKです。

3.プログラムを実行してみる

では、ここまで書いてきたコードを実行してみましょう。

なお、deleteボタンに関する機能がまだ書けていないので、以下のようにon_click=deleted_clickedの部分はコメントアウトします。(①部分)

また、classはあくまでも設計書で実体化してあげないと、どこにもアウトプットされないので、②部分のように付け足して実行します。
(なお、Taskクラスはtask_nameを引数として取るため、引数を渡してあげないとエラーとなります。とりま、testと言う文字列を渡しています)

import flet as ft

class Task(ft.UserControl):
    def __init__(self, task_name):
        super().__init__()
        self.task_name = task_name

    def build(self):
        self.display_task = ft.Checkbox(value=False, label=self.task_name)
        self.edit_name = ft.TextField(expand=1)

        self.display_view = ft.Row(
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.display_task,
                ft.Row(
                    spacing=0,
                    controls=[
                        ft.IconButton(
                            icon=ft.icons.CREATE_OUTLINED,
                            tooltip="Edit To-Do",
                            on_click=self.edit_clicked,
                        ),
                        ft.IconButton(
                            ft.icons.DELETE_OUTLINE,
                            tooltip="Delete To-Do",
                            #①部分(削除ボタンに紐づく機能は未定義なのでコメントアウト)
                            #on_click=self.delete_clicked, 
                        ),
                    ],
                ),
            ],
        )

        self.edit_view = ft.Row(
            visible=False,
            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
            vertical_alignment=ft.CrossAxisAlignment.CENTER,
            controls=[
                self.edit_name,
                ft.IconButton(
                    icon=ft.icons.DONE_OUTLINE_OUTLINED,
                    icon_color=ft.colors.GREEN,
                    tooltip="Update To-Do",
                    on_click=self.save_clicked,
                ),
            ],
        )
        return ft.Column(controls=[self.display_view, self.edit_view])

    def edit_clicked(self, e):
        self.edit_name.value = self.display_task.label
        self.display_view.visible = False
        self.edit_view.visible = True
        self.update()

    def save_clicked(self, e):
        self.display_task.label = self.edit_name.value
        self.display_view.visible = True
        self.edit_view.visible = False
        self.update()


#②部分 todo変数にTaskクラスを定義して実行。
def main(page:ft.Page):
    page.title = "ToDo App"
    page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
    page.update()

    todo = Task('test')

    page.add(todo)

ft.app(target = main)

実行すると・・・(私はtodo_study_5.pyと言うファイル名でコードを保存していました)

python todo_study_5.py

こんな感じで表示されるかと思います。

1行のタスクが表示された

定義した通り、タスク名と編集・削除ボタンが表示されており、編集エリアは非表示となっています。
試しに編集ボタンを押して、タスク名を変えて、保存ボタンを押してみましょう。

タスク名を変更後のタスク名として、右端の保存ボタン(緑色のチェックボタン)を押下

タスク名が変わりました。

削除ボタンは機能を紐づけてないので押しても反応がない

これで1行ずつ表示したいタスクに関するレイアウトが大体完成しました。

次回も公式のチュートリアルを見ながら、最後まで完成させていこうと思います。

読んでいただきありがとうございました!やってみたけど上手くいかない・・・などあったら、私で分かる範囲で一緒に考えたいと思いますので、お気軽にコメント等ください!

この記事が気に入ったらサポートをしてみませんか?