Flet 应用中利用 page.client_storage 实现数据持久化教程

聖光之護
发布: 2025-08-06 22:42:01
原创
291人浏览过

flet 应用中利用 page.client_storage 实现数据持久化教程

本教程详细讲解如何在 Flet 应用中使用 page.client_storage 实现数据持久化,以确保用户数据在应用重启后依然存在。我们将深入探讨存储 Flet UI 控件时常见的“循环引用”错误,并提供正确的解决方案:即仅存储可序列化的数据类型(如字符串、数字或由它们组成的列表/字典),而非 Flet 控件对象本身,并通过示例代码演示如何有效管理和加载持久化数据。

1. 理解 Flet 的 page.client_storage

Flet 框架为开发者提供了一个名为 page.client_storage 的机制,用于在客户端(通常是浏览器或桌面应用的本地存储)持久化数据。这类似于 Web 开发中的 localStorage,它允许应用在会话结束后依然保留少量数据,以便在下次启动时恢复。

page.client_storage 是一个简单的键值对存储,其基本操作包括:

  • page.client_storage.set(key, value): 存储一个键值对。value 必须是可序列化的数据类型(字符串、数字、布尔值,或由它们组成的列表/字典)。
  • page.client_storage.get(key): 根据键获取存储的值。
  • page.client_storage.remove(key): 移除指定键的数据。
  • page.client_storage.clear(): 清除所有存储的数据。
  • page.client_storage.get_keys(prefix): 获取所有以指定前缀开头的键。

2. 为什么不能直接存储 Flet 控件?

在尝试使用 page.client_storage 存储 Flet UI 控件(如 ft.Row、ft.Text、ft.Checkbox 等)时,开发者经常会遇到 ValueError: Circular reference detected 或 'dict' object has no attribute '_build_add_commands' 等错误。

这是因为 Flet 控件是复杂的 Python 对象,它们内部包含对其他控件、父控件、事件处理器等的引用。这些复杂的内部结构导致它们无法直接被 JSON 序列化(page.client_storage 在底层通常依赖于 JSON 序列化来存储数据)。当你尝试存储一个包含循环引用的对象时,序列化器会陷入无限循环,从而抛出 Circular reference detected 错误。即使没有循环引用,Flet 控件对象本身也包含许多非数据属性和方法,这些都不是 client_storage 期望存储的简单数据。

核心原则:page.client_storage 应该存储数据,而不是 UI 控件。 你应该存储用户输入的数据(例如,待办事项的文本内容),然后在应用启动时,根据这些存储的数据动态地重建 UI 控件。

3. 实现一个持久化的 To-Do List 应用

我们将基于一个待办事项应用来演示如何正确使用 page.client_storage。

3.1 应用结构概览

我们的 To-Do List 应用将包含:

  • 一个文本输入框用于添加新任务。
  • 一个“添加”按钮。
  • 一个显示所有任务的区域。
  • 一个“清除所有任务”按钮。

3.2 关键实现步骤

  1. 数据存储策略:

    • 不存储 ft.Row 或 ft.Text 对象。
    • 只存储任务的文本内容(字符串)。
    • 为每个任务生成一个唯一的键,例如 t1, t2, t3 等,以便独立存储和检索。我们可以利用一个计数器来实现这一点。
  2. 添加任务与存储:

    AppMall应用商店
    AppMall应用商店

    AI应用商店,提供即时交付、按需付费的人工智能应用服务

    AppMall应用商店 56
    查看详情 AppMall应用商店
    • 当用户输入任务并点击“添加”按钮时,获取文本内容。
    • 更新一个计数器(例如,可以绑定在添加按钮的 data 属性上)。
    • 使用计数器作为键的一部分,将任务文本存储到 page.client_storage。
  3. 加载已存储任务:

    • 应用启动时,遍历 page.client_storage 中所有以特定前缀(例如“t”)开头的键。
    • 对于每个键,获取其对应的值(任务文本)。
    • 根据获取到的任务文本,动态创建 ft.Text 控件,并添加到任务列表中。
  4. 清除所有任务:

    • 当用户点击“清除所有任务”按钮时,调用 page.client_storage.clear() 清除所有数据。
    • 同时清空 UI 中的任务列表。

3.3 示例代码

import flet as ft

def main(page: ft.Page):
    # 页面基本设置
    page.window_center()
    page.window_width = 400
    page.window_height = 600
    page.title = "Flet 持久化待办事项"
    page.bgcolor = "#141414"

    # 1. 定义清除所有任务的函数
    def all_clear(e):
        # 清除 client_storage 中的所有数据
        page.client_storage.clear()
        # 清除 UI 中的所有任务控件
        task_column.controls.clear()
        # 更新页面
        page.update()

    # 设置浮动操作按钮用于清除所有任务
    page.floating_action_button = ft.FloatingActionButton(
        icon=ft.icons.CLEANING_SERVICES_ROUNDED,
        on_click=all_clear,
        bgcolor="#4B90BE",
        scale=0.95
    )

    # 2. 定义添加任务的函数
    def add_task(e):
        # 确保输入框不为空
        if tf.value:
            # 增加添加按钮的data属性作为任务的唯一ID计数器
            # add_button.data 初始值为 0,每次点击增加
            add_button.data += 1

            # 获取任务文本
            task_text = tf.value

            # 创建一个 ft.Text 控件来显示任务
            # 注意:这里我们创建了控件,但不会直接存储它
            task_control = ft.Text(task_text, color=ft.colors.WHITE)

            # 将任务控件添加到显示任务的列中
            task_column.controls.append(task_control)

            # 将任务文本存储到 client_storage
            # 使用 f"task_{add_button.data}" 作为唯一的键
            page.client_storage.set(f"task_{add_button.data}", task_text)

            # 清空输入框
            tf.value = ""

            # 更新页面以显示新任务和清空的输入框
            page.update()

            # 打印当前所有存储的键,用于调试
            print("当前存储的键:", page.client_storage.get_keys(''))

        # 如果输入框为空,则不执行任何操作
        else:
            tf.focus() # 重新聚焦输入框
            page.update()


    # 任务输入框
    tf = ft.TextField(
        label='输入新任务',
        expand=True, # 允许文本框扩展以填充可用空间
        color=ft.colors.WHITE,
        hint_text_style=ft.TextStyle(color=ft.colors.GREY_500)
    )

    # 添加任务按钮
    # data=0 用于作为任务ID的计数器
    add_button = ft.IconButton(
        icon=ft.icons.ADD,
        on_click=add_task,
        data=0,
        icon_color="#4B90BE",
        tooltip="添加任务"
    )

    # 用于显示所有任务的列
    task_column = ft.Column(
        controls=[],
        spacing=10 # 任务之间的间距
    )

    # 将输入框和添加按钮添加到页面顶部
    page.add(
        ft.Row(
            controls=[
                tf,
                add_button
            ],
            alignment=ft.MainAxisAlignment.CENTER
        )
    )

    # 将任务显示列添加到页面
    page.add(
        ft.Container(
            content=task_column,
            expand=True, # 允许任务列表扩展
            padding=ft.padding.all(10),
            alignment=ft.alignment.top_left
        )
    )

    # 3. 应用启动时加载已存储的任务
    # 检查 client_storage 中是否存在以 'task_' 开头的键
    stored_keys = page.client_storage.get_keys('task_')
    if stored_keys:
        # 对键进行排序,以确保任务按添加顺序显示
        # 提取数字部分进行排序,例如 'task_10' -> 10
        sorted_keys = sorted(stored_keys, key=lambda k: int(k.split('_')[1]))

        for key in sorted_keys:
            # 获取存储的任务文本
            task_text = page.client_storage.get(key)
            if task_text: # 确保获取到的值不为空
                # 根据任务文本创建 ft.Text 控件
                task_control = ft.Text(task_text, color=ft.colors.WHITE)
                # 将控件添加到任务显示列中
                task_column.controls.append(task_control)

                # 更新 add_button.data,确保新的任务ID不会与现有ID冲突
                # 找到最大的ID,并将其设置为 add_button.data 的起始值
                current_max_id = int(key.split('_')[1])
                if current_max_id > add_button.data:
                    add_button.data = current_max_id

        # 加载完成后,将 add_button.data 增加1,为下一个新任务做准备
        add_button.data += 1

        # 更新页面以显示加载的任务
        page.update()


# 运行 Flet 应用
ft.app(target=main)
登录后复制

3.4 注意事项与改进

  1. 唯一键管理: 在上述示例中,我们使用 add_button.data 作为计数器来生成唯一的任务键(例如 task_1, task_2)。这是一种简单有效的方法。在应用启动时,需要遍历所有已存储的键,找出最大的数字,并更新 add_button.data,以确保新添加的任务不会覆盖现有任务。

  2. 单个任务的删除: 示例中只实现了“清除所有任务”。如果需要删除单个任务,则在创建每个任务的 UI 控件时,需要将其对应的 client_storage 键关联起来(例如,通过 control.data 属性)。当删除按钮被点击时,可以根据这个键从 client_storage 中移除对应的数据,并从 UI 中移除该控件。

  3. 更复杂的数据结构: 如果需要存储更复杂的数据(例如,任务的文本、完成状态、截止日期等),可以考虑将每个任务存储为一个字典,然后将所有任务的字典列表进行 JSON 序列化,作为一个单一的值存储到 client_storage 中。

    import json
    # 存储
    tasks_data = [{"text": "Buy milk", "completed": False}, {"text": "Learn Flet", "completed": True}]
    page.client_storage.set("all_tasks", json.dumps(tasks_data))
    
    # 读取
    stored_json = page.client_storage.get("all_tasks")
    if stored_json:
        loaded_tasks = json.loads(stored_json)
        # 根据 loaded_tasks 重新构建 UI
    登录后复制

    这种方式更易于管理和更新,因为它将所有相关数据存储在一个条目下。

4. 总结

page.client_storage 是 Flet 应用实现数据持久化的强大工具,但正确使用它至关重要。核心在于理解它是一个键值对存储,只能存储可序列化的数据,而非复杂的 Flet UI 控件对象。通过将数据与 UI 逻辑分离,并在应用启动时动态重建 UI,我们可以有效地利用 client_storage 来提升用户体验,确保应用数据的持久性。

以上就是Flet 应用中利用 page.client_storage 实现数据持久化教程的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号