BaseApp 派生自
Panel。你的应用 就是 面板树的根,
这就是为什么 AddPanel 和
AddControl 可以直接在它上面工作,也是为什么没有另一个
需要学习的场景图类型。
| 阶段 | 在这里做什么是安全的 |
|---|---|
| 你的构造函数 | 标题、分辨率、相机、StorageService.DirectoryBase,以及每个 RenderQuality.Default* 值。GPU 还不存在。 |
Init() | 由平台运行器在设备和交换链存在后调用。只有当你需要包住初始化时才重写它。 |
Create() | 构建面板树、加载字体、注册 Compute 特效。先调用 base.Create()。 |
Update(time) | 每帧一次。推进你自己的状态;管线读取结果。 |
Resize() / ResizeContent() | 在窗口或方向变化时调用。在这里重新摆放你的 2D 布局。 |
Draw() | 由引擎持有。你通常不重写它。 |
Create 被声明为
async void,这不寻常但是有意为之:它让你可以
await 字体或设置读取,而首批帧
已经在呈现。管线中没有任何东西等待它,所以晚三帧加入的控件
就只是晚三帧出现。
面板拥有控件和其他面板。没有布局系统,没有停靠,也没有 约束求解器 — 面板是放置相关事物的位置,也是放置驱动它们的代码的位置。
| 成员 | 说明 |
|---|---|
Controls、Panels | 普通列表。你可以读取和重排它们;使用 Add/Remove 方法来变更。 |
AddControl | 添加一个叶子控件并排队它的加载。如果它已存在则返回 false。 |
AddPanel | 添加一个子面板。它 不会 排队加载 — 有真实加载工作的面板需要显式排队,见下文。 |
RemoveControl / RemovePanel | 分离 并 释放子项。如果你想分离并保留它,就自己从列表中移除它。 |
Layer、Order | 父级内的绘制顺序。平局时回退到插入索引,因此排序是稳定的全序。 |
Alpha | 沿子树向下相乘。Alpha = 0 会把整个分支从每个通道中剪掉 — 隐藏一个屏幕最廉价的方式。 |
SetMode(string) | 为带变体的面板提供的一个字符串钩子,这样你不需要为每个状态建子类。 |
OnClose | 在面板关闭自身时调用;对话框用它告诉持有者它已完成。 |
IsDisposed | 由 Dispose 设置。加载队列会检查它,所以加载中途释放是安全的。 |
Panel.Draw 在每个通道中替你遍历它的控件和子面板,
并且是排序后的。Panel 有意
不 重写 Update:你在你自己的 Update 中,
按你想要的顺序更新你拥有的子项。这就是为什么
更新顺序是你可以推理的东西 — 也是为什么一个你忘记更新的控件
仍然会绘制,处于它最后的状态。
每个控件和面板都有相同的形态。先调用
base.Update,然后驱动你拥有的一切。可选
参数让父级可以只覆盖一帧的摆放,而不改动子项的字段。
public override bool Update(float time, float? alpha = null,
float? posX = null, float? posY = null, float? posZ = null,
float? width = null, float? height = null, float? depth = null)
{
var result = base.Update(time, alpha: alpha,
posX: posX, posY: posY, posZ: posZ,
width: width, height: height, depth: depth);
// time is the delta in seconds since the previous frame.
angle += time * 0.5f;
robot.Rotation = angle;
robot.Update(time);
return result;
}
| 要点 | 为什么重要 |
|---|---|
time 是增量 | 自上一帧以来的秒数,不是绝对时钟。如果你需要相位,BaseApp.Time 是累计总量。 |
返回 true | 表示这一帧已被消费 — 一次点击已被处理。交互式面板用它来阻止传播。 |
没有 Draw 要写 | 更新状态就是全部契约。应用代码中没有绘制调用、没有命令列表、没有材质绑定。 |
| 输入在这里到达 | OnClick 和 OnTouch 从 BaseControl.Update 内部触发,这就是为什么从未被更新的控件也永远不会可点击。 |
基类声明把参数排序为
posX, posY, posZ, width, height, depth,但几种控件
类型把它们的重写声明为
posX, posY, width, height, posZ, depth。这些类型全都是
float?,所以编译器不会报错,而每个重写
都按名称转发给它的基类,这保证了行为正确 — 只对具名调用者而言。
一个越过 alpha 之后的位置调用,可能会悄悄把你的 Z 放进
Width。请传具名参数,或者干脆省略参数,
在调用
Update(time) 之前直接给属性赋值。
Ready 是标志
没有任何东西同步加载。AddControl 通过 BaseApp.RequestLoad
把子项放进一个全局队列;队列带并发上限地调用
Load(),成功后把
Ready = true 置位并打上
LoadComplete 标记。
Load 返回 false 会让控件保持未就绪,且什么都不抛出。一个看不见的模型通常意味着资产路径错误。Ready 因类型而异。 对叶子控件来说,它意味着 GPU 资源存在且可绘制;对面板来说,它只意味着面板自身的加载已完成,而子项还在它身后陆续就绪。IsDisposed,并丢弃结果而不是复活一个已死的控件。// Wait for something before using it, without blocking the frame:
if (robot.Ready)
robot.Update(time);
// A panel that does real loading work of its own opts in by declaring
// the interface; the base class already provides every member.
internal class Rocks : Panel, ILoadable
{
public override async Task<bool> Load() { /* extract in the background */ return true; }
}
// AddPanel does not queue, so queue it yourself:
AddPanel(rocks);
DeviceServices.BaseApp?.RequestLoad(rocks);
这个切分是有意的:ILoadable 是加载队列
认得的,IControl 是渲染通道认得的。
面板可加载但不可绘制;精灵两者都是。因为面板只有在确有工作要做时才声明
ILoadable,类型系统
防止你意外地把容器排队。