Skip to content

FilePicker 继承自:Service, BaseControl

一个控件,让你可以使用原生文件浏览器选择单个或多个文件,并支持扩展名筛选与上传。

重要

在 Linux 上,当以桌面应用方式运行 Flet 时,此控件需要 Zenity。在浏览器中运行 Flet 时则不需要。

在 Ubuntu/Debian 上安装 Zenity,请运行以下命令:

bash
sudo apt-get install zenity

用法 ​

创建一个 FilePicker 实例:

python
import flet as ft

file_picker = ft.FilePicker()

要打开文件选择对话框,请根据使用场景调用以下三个方法之一: pick_files()、save_file() 或 get_directory_path()。

大多数情况下,你可以用 lambda 函数来完成:

python
ft.Button(
    content="Pick files",
    on_click=lambda e: file_picker.pick_files(allow_multiple=True)
)

在 Web 应用中选择文件

浏览器只会在处理点击或按键期间打开文件选择器。从 on_click 处理器中调用 pick_files() 已经太晚了 —— 点击已经传到你的 Python 代码、指令又传了回来,此时权限已经失效。 Safari 会静默拒绝,而 Chrome 和 Firefox 则允许,所以同一个应用往往在所有平台上都能用,唯独 iPhone 和 iPad 上不行。

改为把 PickFiles 动作附加到控件上,让对话框在原始手势内部打开,并在 on_result 中处理选择结果。 参见客户端动作。

通过客户端动作选择文件 ​

已选择的文件会始终与 FilePicker 关联,因此 upload() 的用法与调用 pick_files() 之后完全一样。

python
#
# Picking and uploading files in a way that also works in a web app on iOS.
#
# Run this example with:
#    export FLET_SECRET_KEY=<some_secret_key>
#    uv run flet run --web examples/services/file_picker/pick_files_action/main.py
#
from dataclasses import dataclass, field

import flet as ft


@dataclass
class State:
    picked_files: list[ft.FilePickerFile] = field(default_factory=list)


state = State()


def main(page: ft.Page):
    prog_bars: dict[str, ft.ProgressRing] = {}

    def handle_upload_progress(e: ft.FilePickerUploadEvent):
        prog_bars[e.file_name].value = e.progress

    def handle_result(e: ft.FilePickerResultEvent):
        # A PickFiles action opens the dialog before Python sees the click, so
        # the selection arrives here instead of being returned to a caller.
        state.picked_files = e.files

        prog_bars.clear()
        upload_progress.controls.clear()
        for f in e.files:
            prog = ft.ProgressRing(value=0, bgcolor="#eeeeee", width=20, height=20)
            prog_bars[f.name] = prog
            upload_progress.controls.append(
                ft.Row([prog, ft.Text(f"{f.name} ({f.size} bytes)")])
            )
        upload_button.disabled = len(e.files) == 0

    async def handle_file_upload(e: ft.Event[ft.Button]):
        upload_button.disabled = True
        # The picked files stay on the FilePicker, so upload() takes them as-is.
        await file_picker.upload(
            files=[
                ft.FilePickerUploadFile(
                    name=file.name,
                    upload_url=page.get_upload_url(f"dir/{file.name}", 60),
                )
                for file in state.picked_files
            ]
        )

    file_picker = ft.FilePicker(
        on_result=handle_result,
        on_upload=handle_upload_progress,
    )
    page.services.append(file_picker)

    page.add(
        ft.SafeArea(
            content=ft.Column(
                controls=[
                    ft.Button(
                        content="Select files...",
                        icon=ft.Icons.FOLDER_OPEN,
                        # Attaching the pick to the control - rather than
                        # calling file_picker.pick_files() from an on_click
                        # handler - is what makes the dialog open on iOS.
                        action=ft.PickFiles(file_picker, allow_multiple=True),
                    ),
                    upload_progress := ft.Column(),
                    upload_button := ft.Button(
                        content="Upload",
                        icon=ft.Icons.UPLOAD,
                        on_click=handle_file_upload,
                        disabled=True,
                    ),
                ],
            ),
        )
    )


if __name__ == "__main__":
    ft.run(main, upload_dir="examples")

上传文件 ​

要上传一个或多个文件,请调用 FilePicker.pick_files() 让用户选择文件,然后把返回的列表传给 FilePicker.upload() 执行上传。

按用户区分上传

如果你需要为每个用户区分上传内容,可以在 page.get_upload_url() 调用中指定文件名, 并在其前面任意拼接多级目录,例如:

python
upload_url = page.get_upload_url(f"/{username}/pictures/{f.name}", 600)

/{username}/pictures 目录若不存在,会在 upload_dir 内自动创建。

上传存储 ​

注意这里用到了 page.get_upload_url() 方法 —— 它会为 Flet 内置的上传存储生成一个预签名上传 URL。

为文件上传使用任意存储

你可以使用 boto3 库为 AWS S3 存储 生成预签名上传 URL。

同样的方法也适用于 Wasabi、Backblaze、 MinIO 以及任何提供 S3 兼容 API 的其他存储服务商。

要让 Flet 把上传的文件保存到某个目录,请在 flet.run() 调用中提供该目录的绝对或相对路径:

python
ft.run(main, upload_dir="uploads")

你甚至可以把上传内容放进 assets 目录, 这样上传的文件(例如图片、文档或其他媒体) 就能被 Flet 客户端立即访问:

python
ft.run(main, assets_dir="assets", upload_dir="assets/uploads")

然后在应用中用下面的代码显示已上传的图片:

python
ft.Image(src="/uploads/<some-uploaded-picture.png>")

示例 ​

python
import flet as ft


def main(page: ft.Page):
    async def handle_pick_files(e: ft.Event[ft.Button]):
        files = await ft.FilePicker().pick_files(allow_multiple=True)
        selected_files.value = (
            ", ".join(map(lambda f: f.name, files)) if files else "Cancelled!"
        )

    async def handle_save_file(e: ft.Event[ft.Button]):
        save_file_path.value = await ft.FilePicker().save_file()

    async def handle_get_directory_path(e: ft.Event[ft.Button]):
        directory_path.value = await ft.FilePicker().get_directory_path()

    page.add(
        ft.SafeArea(
            content=ft.Column(
                controls=[
                    ft.Row(
                        controls=[
                            ft.Button(
                                content="Pick files",
                                icon=ft.Icons.UPLOAD_FILE,
                                on_click=handle_pick_files,
                            ),
                            selected_files := ft.Text(),
                        ]
                    ),
                    ft.Row(
                        controls=[
                            ft.Button(
                                content="Save file",
                                icon=ft.Icons.SAVE,
                                on_click=handle_save_file,
                                disabled=page.web,  # disable this button in web mode
                            ),
                            save_file_path := ft.Text(),
                        ]
                    ),
                    ft.Row(
                        controls=[
                            ft.Button(
                                content="Open directory",
                                icon=ft.Icons.FOLDER_OPEN,
                                on_click=handle_get_directory_path,
                                disabled=page.web,  # disable this button in web mode
                            ),
                            directory_path := ft.Text(),
                        ]
                    ),
                ],
            ),
        )
    )


if __name__ == "__main__":
    ft.run(main)

选择并上传文件 ​

下面的示例演示了一个多文件 选择 与上传应用。

python
#
# Example of picking and uploading files with progress indication
#
# Run this example with:
#    export FLET_SECRET_KEY=<some_secret_key>
#    uv run flet run --web examples/services/file_picker/pick_and_upload/main.py
#
from dataclasses import dataclass, field

import flet as ft


@dataclass
class State:
    file_picker: ft.FilePicker | None = None
    picked_files: list[ft.FilePickerFile] = field(default_factory=list)


state = State()


def main(page: ft.Page):
    if not page.web:
        page.add(
            ft.SafeArea(
                content=ft.Column(
                    controls=[
                        ft.Text(
                            "This example is only available in Flet Web mode.\n"
                            "\n"
                            "Run this example with:\n"
                            "    export FLET_SECRET_KEY=<some_secret_key>\n"
                            "    flet run --web "
                            "examples/services/file_picker/pick_and_upload/main.py",
                            color=ft.Colors.RED,
                            selectable=True,
                        )
                    ],
                ),
            )
        )
        return

    prog_bars: dict[str, ft.ProgressRing] = {}

    def on_upload_progress(e: ft.FilePickerUploadEvent):
        prog_bars[e.file_name].value = e.progress

    async def handle_files_pick(e: ft.Event[ft.Button]):
        state.file_picker = ft.FilePicker(on_upload=on_upload_progress)
        files = await state.file_picker.pick_files(allow_multiple=True)
        print("Picked files:", files)
        state.picked_files = files

        # update progress bars
        upload_button.disabled = len(files) == 0
        prog_bars.clear()
        upload_progress.controls.clear()
        for f in files:
            prog = ft.ProgressRing(value=0, bgcolor="#eeeeee", width=20, height=20)
            prog_bars[f.name] = prog
            upload_progress.controls.append(ft.Row([prog, ft.Text(f.name)]))

    async def handle_file_upload(e: ft.Event[ft.Button]):
        upload_button.disabled = True
        await state.file_picker.upload(
            files=[
                ft.FilePickerUploadFile(
                    name=file.name,
                    upload_url=page.get_upload_url(f"dir/{file.name}", 60),
                )
                for file in state.picked_files
            ]
        )

    page.add(
        ft.SafeArea(
            content=ft.Column(
                controls=[
                    ft.Button(
                        content="Select files...",
                        icon=ft.Icons.FOLDER_OPEN,
                        on_click=handle_files_pick,
                    ),
                    upload_progress := ft.Column(),
                    upload_button := ft.Button(
                        content="Upload",
                        icon=ft.Icons.UPLOAD,
                        on_click=handle_file_upload,
                        disabled=True,
                    ),
                ],
            ),
        )
    )


if __name__ == "__main__":
    ft.run(main, upload_dir="examples")

选择文本内容并保存/下载 ​

当你需要直接获取文件内容时(例如在 Web 应用中 FilePickerFile.path 不可用), 请使用带 with_data=True 的 pick_files()。

python
#
# Example of picking and saving text content with FilePicker.
#
# Run this example with:
#    uv run flet run --web main.py
#
import flet as ft


def main(page: ft.Page):
    selected_file_name = ft.Text("No file selected")
    selected_file_content = ft.TextField(
        label="Selected file content",
        multiline=True,
        min_lines=8,
        max_lines=14,
    )
    save_status = ft.Text()

    async def pick_text_file(_: ft.Event[ft.Button]):
        files = await ft.FilePicker().pick_files(
            allow_multiple=False,
            with_data=True,
            file_type=ft.FilePickerFileType.CUSTOM,
            allowed_extensions=["txt", "md"],
        )
        if not files:
            selected_file_name.value = "Selection cancelled"
            selected_file_content.value = ""
            return

        selected = files[0]
        selected_file_name.value = f"Selected: {selected.name} ({selected.size} bytes)"
        selected_file_content.value = (
            selected.bytes.decode("utf-8", errors="replace") if selected.bytes else ""
        )
        save_status.value = ""

    async def save_text_file(_: ft.Event[ft.Button]):
        file_name = "flet_text_content.txt"
        file_path = await ft.FilePicker().save_file(
            file_name=file_name,
            file_type=ft.FilePickerFileType.CUSTOM,
            allowed_extensions=["txt"],
            src_bytes=selected_file_content.value.encode("utf-8"),
        )
        if page.web:
            save_status.value = f"Downloaded as {file_name}"
        else:
            save_status.value = (
                f"Saved to: {file_path}" if file_path else "Save cancelled"
            )

    page.add(
        ft.SafeArea(
            content=ft.Column(
                controls=[
                    ft.Text(
                        "Pick a .txt/.md file and load its text from "
                        "FilePickerFile.bytes"
                    ),
                    ft.Button(
                        content="Pick text file",
                        icon=ft.Icons.UPLOAD_FILE,
                        on_click=pick_text_file,
                    ),
                    selected_file_name,
                    selected_file_content,
                    ft.Button(
                        content="Save / Download text",
                        icon=ft.Icons.DOWNLOAD,
                        on_click=save_text_file,
                    ),
                    save_status,
                ],
            ),
        )
    )


if __name__ == "__main__":
    ft.run(main)

属性 ​

名称类型默认值说明
dataAnyskip_任意类型的自定义数据。
keyOptional['KeyValue']None用于在更新之间保持控件标识稳定的键。

只读属性 ​

名称类型默认值说明
parentOptional['BaseControl']—此控件的直接祖先(父级)。 默认为 None,只有在此控件被挂载(加入页面树)后才有值。 Page 控件(页面树的根)是个例外 —— 它的 parent 始终为 None。
page'Page'—此控件所属的页面。

事件 ​

名称类型默认值说明
on_resultOptional[EventHandler[FilePickerResultEvent]]None当通过 PickFiles 动作选择文件时调用。 PickFiles 动作会在你的代码看到点击之前先在客户端打开对话框,因此选择结果无法像 pick_files 那样返回给调用方 —— 而是通过这里送达。 已选择的文件会始终与此 FilePicker 关联,因此可以直接传给 upload。
on_uploadOptional[EventHandler[FilePickerUploadEvent]]None当通过 upload 方法上传文件时调用。 每上传一个文件,此回调至少会被调用两次:一次在上传开始前,progress 为 0.0;一次在上传完成时,progress 为 1.0。 对于大于 1 MB 的文件,还会在每增加 10% 时发出额外的进度事件(例如 0.1、0.2……)。

方法 ​

名称类型默认值说明
upload——将已选择的文件上传到指定的上传 URL。 在调用此方法之前,必须先调用 pick_files,以确保内部文件选择器的选择结果不为空。 调用后,Flet 会异步地逐个上传所选文件,并通过 on_upload 事件报告进度。 参数: files:一个 FilePickerUploadFile 列表,其中每一项指定要上传哪个文件,以及上传到何处(使用 PUT 或 POST)。
get_directory_pathOptional[str]—选择一个目录并返回其绝对路径。 参数: dialog_title:对话框窗口的标题。默认为 [FilePicker]。 initial_directory:对话框打开时所在的初始目录。 返回: 所选目录的路径;如果对话框被取消则返回 None。 抛出异常: FletUnsupportedPlatformException:如果在 Web 模式下调用。
save_fileOptional[str]—打开一个保存文件对话框,让用户选择保存文件的路径和文件名。 注意: - 在桌面上,此方法会打开一个对话框,让用户选择位置和文件名。如果提供了 src_bytes,这些字节会被写入所选文件。 参数: dialog_title:对话框窗口的标题。 file_name:默认文件名。 initial_directory:对话框打开时所在的初始目录。 file_type:允许选择的文件类型。 src_bytes:文件的内容。在 Web、iOS 或 Android 模式下必须提供。 allowed_extensions:允许的文件扩展名。仅当 file_type 为 CUSTOM 时生效。 抛出异常: ValueError:在 Web 模式、iOS 或 Android 上调用且未提供 src_bytes 时。 ValueError:在 Web 模式下且未提供 file_name 时。
pick_fileslist[FilePickerFile]—打开一个选择文件对话框。 提示: 要上传已选择的文件,请把它们及其上传 URL 一并传给 upload 方法。 参数: dialog_title:对话框窗口的标题。 initial_directory:对话框打开时所在的初始目录。 file_type:允许选择的文件类型。 allow_multiple:允许一次选择多个文件。 with_data:将所选文件内容读入 bytes。 compression_quality:图片压缩质量,范围从 0 到 100。0 表示不压缩。 cancel_upload_on_window_blur:仅 Web。是否将浏览器窗口失焦视为取消选择。设为 False 可避免在慢速网络或慢速机器上丢失有效选择。 allowed_extensions:允许的文件扩展名。仅当 file_type 为 CUSTOM 时生效。 返回: 所选文件的列表。 抛出异常: ValueError:如果 compression_quality 不在 0 到 100(含)之间。
init———
is_isolated——返回此控件是否被标记为隔离。 隔离控件会被排除在父级驱动的更新遍历之外,并需要自行管理更新边界。
build——在控件初始化期间调用一次,用于定义其子控件。在此方法中 page 属性可用。
before_update——每当此控件被更新时都会调用此方法。 注意: 务必不要在此处调用或请求 update()。
before_event——在事件处理器执行之前拦截事件。 返回 False 可取消分发。返回 True 或 None 则继续正常的事件处理。
did_mount——在控件挂载到页面树之后调用。 重写此方法以启动需要已挂载页面的资源,例如订阅、定时器或服务监听器。
will_unmount——在控件从页面树移除之前调用。 重写此方法以释放 did_mount() 中创建的资源,例如订阅、定时器或外部句柄。
get_data_channel——解析为此 widget 在 Dart 侧分配的 [DataChannel]。模式: on_data_channel_open: Optional[ft.EventHandler[DataChannelOpenEvent]] = None def init(self): self.on_data_channel_open = self._on_open def _on_open(self, e): self._channel = self.get_data_channel(e.channel_id) 幂等 —— 底层的 Connection 会按 id 缓存 DataChannel,因此重复调用会返回同一实例。没有错误路径:该 id 始终来自框架触发的事件,所以当此方法运行时,通道在两端都已存在。
updateNone—为此控件请求一次 UI 更新。 在更改控件状态或属性后调用。控件必须已挂载到页面且未被标记为冻结。

关注我,学习大模型、大数据、Python等热门技术。