主题
Clipboard 继承自:Service, BaseControl
提供对系统剪贴板的访问。
在 Web 应用中复制
浏览器只允许页面在处理点击或按键期间写入剪贴板,而从事件处理器中调用 set() 已经太晚了 —— 点击已经传到你的 Python 代码又传了回来。Safari 会静默拒绝;Chrome 和 Firefox 更宽容,所以同一个应用往往在所有平台上都能用,唯独 iPhone 和 iPad 上不行。
改为使用 CopyToClipboard 动作,由客户端在原始手势内部执行。参见 客户端动作。
通过客户端动作复制
python
import flet as ft
def main(page: ft.Page):
# `action` is performed by the client while it is still handling the click.
# That is the only moment Safari lets a page write to the clipboard, which
# is why `Clipboard().set()` - which has to reach Python first - does
# nothing on iOS.
token = "flet-1234-5678"
def handle_copied(e):
page.show_dialog(ft.SnackBar(ft.Text("Copied to clipboard")))
page.add(
ft.SafeArea(
content=ft.Column(
controls=[
ft.Text(f"Token: {token}", selectable=True),
ft.Button(
"Copy token",
icon=ft.Icons.CONTENT_COPY,
action=ft.CopyToClipboard(token),
on_click=handle_copied,
),
ft.Divider(),
ft.Text(
"An action's arguments are fixed before the click, so "
"to copy something typed just now, update the action "
"as the text changes."
),
note := ft.TextField(
label="Note",
value="Edit me, then copy",
on_change=lambda e: setattr(
copy_note, "action", ft.CopyToClipboard(note.value)
),
),
copy_note := ft.Button(
"Copy note",
icon=ft.Icons.CONTENT_COPY,
action=ft.CopyToClipboard("Edit me, then copy"),
on_click=handle_copied,
),
],
),
)
)
if __name__ == "__main__":
ft.run(main)示例
python
import flet as ft
async def main(page: ft.Page):
async def set_to_clipboard():
await ft.Clipboard().set(text_to_copy.value)
text_to_copy.value = ""
page.show_dialog(ft.SnackBar("Text copied to clipboard"))
async def get_from_clipboard():
contents = await ft.Clipboard().get()
page.add(ft.Text(f"Clipboard contents: {contents}"))
page.add(
ft.SafeArea(
content=ft.Column(
controls=[
ft.Column(
controls=[
text_to_copy := ft.TextField(label="Text to copy"),
ft.Button("Set to clipboard", on_click=set_to_clipboard),
ft.Button(
"Get from clipboard", on_click=get_from_clipboard
),
],
)
],
),
)
)
if __name__ == "__main__":
ft.run(main)python
import base64
import flet as ft
SAMPLE_PNG = base64.b64decode(
"iVBORw0KGgoAAAANSUhEUgAAABkAAAAgCAYAAADnnNMGAAAACXBIWXMAAAORAAADkQFnq8zdAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAA6dJREFUSImllltoHFUYx3/fzOzm0lt23ZrQ1AQbtBehNpvQohgkBYVo410RwQctNE3Sh0IfiiBoIAjqi6TYrKnFy4O3oiiRavDJFi3mXomIBmOxNZe63ay52GR3Zj4f2sTEzmx3m//TYf7/c35zvgPnO6KqrESXqpq3muocAikv6m+/zytj3ejik1VN21G31YA9CgJ6xC+bMyQZPVCuarciPAMYC99V6Vw5pLbFSibHmlVoRVj9P3cmPBM8tSJI/M6mzabpfoAQ9fIF7WK4bd5vvuFnLGgy2vi0abg94A0AcJGvMq3hDxGRyar9r4F+iLAm0yIiRk8m37tctS1WsrIhhrI30+Srmg+J87OXUf3lWGS1q89dC6ltsSanxk4Aj2QBABii96300g87P/rtlrWr8l+vyDMfdlXSyyEikqxsiOUAQJCBhfHdXRfCq1LSsSlcWG+KBAGStvvrMkgiuv8lUc2mREukPwLUfHG+uTQv8Eown7VL3XlbBxYhf1c17hbVF3MDwA9bts280TnaU1YYqPby07aeFlUlHt27wSQ4CLo+F8AvoTCvHmyKF+ZbEb/M77P2LgvAwmrTHAHflN3KZxVbMC2jMFNOpgPnrMSOhvvFkMezXdwV4ePbtvHtxnJAMQ0j4JtVnO+eLb5oiSlt5HDbv7t1O90lpYCCCKbhfzW5kAIwUAazR0BlfII8Ow0I6uoVmI9MyAMwbMs8CExmDbk4zgu931MyO4OI4KrYflkRjOoTI+uM9d1vjotwKPu9QMk/sxzuO8POiVFcdZ1M2YBVsMEAKOqLvaPIe7mACuw0z/80SMH58SMplxlfiDhVi7dw2pltRhjKBQTQdrSja2KKTfE551NHuaZ0QVPvWYQUn31/Vm2nDvgjF4grVJx6suSvrvrSJ/6cSW2Oz9mf264uNrB806xZ1k/CZ49dUKgDEtlCROX2hfHpx8pGuuo3PpqYulw8fjndOp1yhgtNKRevJ1FyR2Ola+jXAjdnwTkZ6o896GdWdxDw7IxFg+0DpmXchTKSBWQnIuJn9u4j7dt+13UfHXEkXQOcuQ4kMhVtqsgUyPiQiPQfHw1NB2sRjmXKuTg1NwwBYLhtPtQX26eqTwGXPDOqvmcC4Hnwfrrad94GrVsOYTqUTkQY+iTlNe/6O1miSP/x0VB/+wMIDwHn/vtV1iQC4Xv95uUEWVCoL9Y5Z+gdovoyMHUFJHv88jmVy0vTuw7cZNv2YaA61Bfb7ZX5F8SaUv2xwZevAAAAAElFTkSuQmCC"
)
async def main(page: ft.Page):
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
async def set_image_to_clipboard(e: ft.Event[ft.Button]):
await ft.Clipboard().set_image(SAMPLE_PNG)
status.value = f"Sample image copied to clipboard ({len(SAMPLE_PNG)} bytes)."
preview.content = ft.Image(src=SAMPLE_PNG)
async def get_image_from_clipboard(e: ft.Event[ft.Button]):
clipboard_image = await ft.Clipboard().get_image()
if clipboard_image is None:
status.value = "No image found in clipboard."
preview.content = None
return
else:
preview.content = ft.Image(src=clipboard_image)
status.value = (
f"Image loaded from clipboard ({len(clipboard_image)} bytes)."
)
page.add(
ft.SafeArea(
ft.Column(
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
ft.Button(
"Set example image to clipboard",
on_click=set_image_to_clipboard,
disabled=not (page.web or page.platform.is_mobile()),
tooltip="Supported on mobile platforms only."
if not (page.web or page.platform.is_mobile())
else None,
),
ft.Button(
"Get image from clipboard",
on_click=get_image_from_clipboard,
),
status := ft.Text(),
preview := ft.Container(width=360, height=240),
],
)
)
)
if __name__ == "__main__":
ft.run(main)python
import os
import subprocess
import sys
import tempfile
from pathlib import Path
import flet as ft
def open_file(_, path: str):
"""Open a desktop file in the default application."""
if os.path.isfile(path):
if sys.platform == "darwin": # macOS
subprocess.run(["open", path])
elif sys.platform == "win32": # Windows
os.startfile(path)
else: # Linux / BSD
subprocess.run(["xdg-open", path])
async def main(page: ft.Page):
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
# supported-platform checkers
supports_set_files = not page.web and page.platform.is_desktop()
supports_get_files = not page.web and (
page.platform.is_desktop() or page.platform == ft.PagePlatform.ANDROID
)
def get_sample_files() -> list[str]:
# create temporary files
sample_dir = Path(tempfile.gettempdir()) / "flet_clipboard_files_example"
sample_dir.mkdir(parents=True, exist_ok=True)
sample_files = [
sample_dir / "sample_1.txt",
sample_dir / "sample_2.txt",
]
sample_files[0].write_text("Clipboard sample file #1\n", encoding="utf-8")
sample_files[1].write_text("Clipboard sample file #2\n", encoding="utf-8")
return [str(p) for p in sample_files]
async def set_files_to_clipboard(e: ft.Event[ft.Button]):
files = get_sample_files()
ok = await ft.Clipboard().set_files(files)
status.value = (
f"Set {len(files)} file references to clipboard (result: {ok})."
if ok
else "Failed to set file references to clipboard."
)
async def get_files_from_clipboard(e: ft.Event[ft.Button]):
files = await ft.Clipboard().get_files()
if files:
files_lv.controls = [
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ft.Text(f, selectable=True),
ft.IconButton(
icon=ft.Icons.OPEN_IN_NEW,
icon_size=15,
on_click=lambda e, f=f: open_file(e, f),
tooltip="Open file",
),
],
)
for f in files
]
else:
files_lv.controls = []
status.value = f"Read {len(files)} file reference(s) from clipboard."
page.add(
ft.SafeArea(
ft.Column(
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
ft.Button(
"Set example files to clipboard",
on_click=set_files_to_clipboard,
disabled=not supports_set_files,
tooltip="Supported on desktop platforms only."
if not supports_set_files
else None,
),
ft.Button(
"Get files from clipboard",
on_click=get_files_from_clipboard,
disabled=not supports_get_files,
tooltip="Supported on Android and desktop platforms only."
if not supports_get_files
else None,
),
status := ft.Text(),
files_lv := ft.Column(
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
spacing=0,
controls=[],
),
],
)
)
)
if __name__ == "__main__":
ft.run(main)属性
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
data | Any | skip_ | 任意类型的自定义数据。 |
key | Optional['KeyValue'] | None | 用于在更新之间保持控件标识稳定的键。 |
只读属性
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
parent | Optional['BaseControl'] | — | 此控件的直接祖先(父级)。 默认为 None,只有在此控件被挂载(加入页面树)后才有值。 Page 控件(页面树的根)是个例外 —— 它的 parent 始终为 None。 |
page | 'Page' | — | 此控件所属的页面。 |
方法
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
set | None | — | 将给定的数据存储到剪贴板。 参数: value:要存储到剪贴板的字符串数据。 |
get | Optional[str] | — | 从剪贴板检索数据。 返回: 从剪贴板检索到的字符串数据;如果剪贴板为空或不包含字符串数据则返回 None。 |
set_image | None | — | 将图片字节存储到剪贴板。 参数: value:要存储到剪贴板的图片数据(字节)。 抛出异常: FletUnsupportedPlatformException:如果调用的平台不是以下之一:Android、iOS、Web。 |
get_image | Optional[bytes] | — | 从剪贴板检索图片数据。 返回: 从剪贴板检索到的图片数据(字节);如果剪贴板为空或不包含图片数据则返回 None。 |
set_files | bool | — | 将文件引用存储到剪贴板。 参数: files:文件路径的列表。 返回: 如果操作成功则返回 True,否则返回 False。 抛出异常: FletUnsupportedPlatformException:如果调用的平台不是以下之一:macOS、Windows、Linux。 |
get_files | list[str] | — | 从剪贴板检索文件引用。 返回: 剪贴板中可用的文件引用列表。 在 Android 上,这些通常是 content URI。 抛出异常: FletUnsupportedPlatformException:如果调用的平台不是以下之一:Android、macOS、Windows、Linux。 |
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 始终来自框架触发的事件,所以当此方法运行时,通道在两端都已存在。 |
update | None | — | 为此控件请求一次 UI 更新。 在更改控件状态或属性后调用。控件必须已挂载到页面且未被标记为冻结。 |