做桌面端 UI 时,自定义控件往往不是因为“系统控件不好看”,而是业务交互已经超出了默认容器的表达能力。Avalonia 的优势在于跨平台布局一致性不错,但只要自己开始接管测量和排列逻辑,就必须对布局系统的时机和边界非常清楚。
先判断是否真的需要自定义布局
我会先问自己两个问题:现有 `Panel` 组合是否真的无法表达目标布局?交互复杂度是否值得维护一个新控件?很多布局问题其实通过 `Grid`、`DockPanel` 或者包装一个 `TemplatedControl` 就能解决,没有必要一上来就重写整个布局流程。
布局代码一旦写进控件内部,就从“页面排版问题”升级成“框架级别的长期维护问题”。
只有当子元素位置依赖运行时状态、可视区域或自定义规则时,我才会考虑直接继承 `Panel` 并实现自己的 `MeasureOverride` 与 `ArrangeOverride`。
测量阶段要解决的是“需要多大”
`MeasureOverride` 的职责不是摆放元素,而是告诉父容器:在给定约束下,我至少需要多大空间才能正常工作。这里最容易犯的错,是把排列规则提前带进测量阶段,导致控件为了暂时的视觉布局而报出错误的期望尺寸。
protected override Size MeasureOverride(Size availableSize)
{
double width = 0;
double height = 0;
foreach (var child in Children)
{
child.Measure(availableSize);
width = Math.Max(width, child.DesiredSize.Width);
height += child.DesiredSize.Height;
}
return new Size(width, height);
}
这个示例虽然简单,但足以说明思路:先从所有子元素的 `DesiredSize` 出发,再推导容器自己的尺寸需求。越早把布局规则抽象清楚,后续改动越不容易牵一发动全身。
排列阶段再落地业务规则
`ArrangeOverride` 才是把每个子元素放进最终矩形的位置。在这里,我倾向于显式维护游标、间距和换行策略,而不是把逻辑写成一堆分支交织的坐标计算。布局测试的重点,就是验证不同窗口宽度下这些规则有没有稳定生效。
- 宽度不足时是否优雅换行,而不是简单截断。
- 隐藏状态的子元素是否仍然参与间距计算。
- 滚动容器嵌套时,是否出现不必要的二次测量。
如果布局依赖动画或过渡效果,建议把视觉变化留给样式层或行为层,不要让布局引擎承担太多时序责任。
把布局测试变成可重复的样例集
我后来给控件单独做了一个样例页,固定几组典型数据:极短文本、超长文本、不同 DPI、不同窗口宽度。这样每次改布局代码时,第一时间就能看出哪些场景被打坏了。对于桌面 UI 来说,这种“展示型测试”非常值得保留。
如果项目中已经用了 `Headless` 测试或截图比对,也可以把关键状态做成自动化回归。但即使没有自动化,手工样例页也比在主页面里肉眼试一遍可靠得多。
结论:布局逻辑应尽量小而清晰
自定义控件真正难的不是写出能工作的第一版,而是半年后仍然敢改。Avalonia 给了我们足够自由,但布局代码一旦膨胀,就会直接拖慢调试效率。我的做法是:先用组合控件解决 80% 的问题,只有在布局规则确实成为产品能力的一部分时,才把它沉淀为专用控件。