PythonIDE Docs
中文
简体中文

从 ui 迁移到 appui

命令式 旧版手动布局 API 写法迁移到声明式 AppUI。

ui 是命令式控件树,appui 是声明式 View 树。迁移时不要逐行翻译控件操作;先把界面状态整理出来,再用 body() 描述当前状态下的界面。

#对照表

ui 写法appui 写法
创建控件后设置属性在构造函数和修饰符链里声明属性
view.add_subview(...)VStack/HStack/ZStack/List/Form 组合子 View
回调里直接改控件回调里改 State
手动 push/presentNavigationStack、sheet、alert
保存控件实例用于后续修改保存状态或 Ref
frame 手算布局Stack/Grid/frame/padding

#最小迁移例子

ui 思路:

python
import ui

count = 0
label = ui.Label(text="0")

def add(sender):
    global count
    count += 1
    label.text = str(count)

button = ui.Button(title="Add")
button.action = add

appui 写法:

python
import appui

state = appui.State(count=0)


def increment():
    state.count += 1


def body():
    return appui.VStack([
        appui.Text(str(state.count)).font("largeTitle").bold(),
        appui.Button("Add", action=increment),
    ], spacing=16).padding()


appui.run(body, state=state)

重点不是 Label -> Text,而是“控件文本”变成了 state.count 的投影。

#列表迁移

旧写法通常是维护 table 数据源并手动刷新。appui 里直接让列表来自状态。

python
import appui

state = appui.State(items=["Milk", "Coffee"])


def add_item():
    state.items = list(state.items) + [f"Item {len(state.items) + 1}"]


def item_key(item):
    return item


def row_view(item):
    return appui.Text(item)


def body():
    return appui.NavigationStack(
        appui.List([
            appui.Section("Items", [
                appui.ForEach(state.items, row_builder=row_view, key=item_key)
            ])
        ])
        .navigation_title("List")
        .toolbar([
            appui.ToolbarItem(
                placement="navigation_bar_trailing",
                content=appui.Button("Add", action=add_item),
            )
        ])
    )


appui.run(body, state=state)

需要增删改的列表要么整体替换 state.items,要么使用 ObservableList;不要原地修改普通 list 后期待 UI 自动刷新。

#表单迁移

ui.TextFieldaction / delegate 通常迁移成绑定。

python
import appui

state = appui.State(name="", notifications=True)


def set_name(value):
    state.name = value


def set_notifications(value):
    state.notifications = value


def body():
    return appui.Form([
        appui.Section("Profile", [
            appui.TextField("Name", text=state.name, on_change=set_name),
            appui.Toggle("Notifications", is_on=state.notifications, on_change=set_notifications),
        ]),
        appui.Section("Preview", [
            appui.Text(f"Hello, {state.name or 'Guest'}"),
        ]),
    ]).navigation_title("Profile")


appui.run(body, state=state)

表单字段用当前值加 on_change 写回;不要在多个地方维护同一个字段的副本。

#导航迁移

命令式 present/push 迁移到数据化导航:

python
import appui

path = appui.NavigationPath()
state = appui.State(selected="A")


def open_detail():
    state.selected = "A"
    path.append("detail")


def go_back():
    path.pop()


def detail_destination(value):
    return appui.VStack([
        appui.Text(f"Detail {state.selected}").font("title2"),
        appui.Button("Back", action=go_back),
    ], spacing=12).padding()


def body():
    return appui.NavigationStack(
        content=appui.VStack([
            appui.Text("Home"),
            appui.Button("Open detail", action=open_detail),
        ], spacing=16).padding().navigation_title("Home"),
        path=path,
        destinations={
            "detail": detail_destination,
        },
    )


appui.run(body, state=state)

如果只是固定页面跳转,也可以用 NavigationLink

#迁移步骤

  1. 列出页面状态:文本、开关、选中项、列表数据、弹层显示状态。
  2. 把旧控件树改成 body() 返回的 View 树。
  3. 把“修改控件属性”的回调改成“修改 State”。
  4. 把 frame 手算布局改成 Stack/Grid/ScrollView。
  5. 把 push/present 改成 NavigationStack/sheet/alert。
  6. 每次迁移一屏,保留可运行入口。

#不要直接翻译的写法

旧习惯问题appui 改法
label.text = ...绕过状态源Text(f"{state.value}")
全局保存控件对象重建后对象可能失效保存 State / Ref
手动设置每个 frame不适配设备Stack + frame + padding
回调里同时改很多字段中间状态闪动state.batch_update(...)
复制其他框架参数名Python API 可能不同查 appui API 参考

#下一步