WPF FrameWorkElement->UIElement->Visual

布局系统

提及 MeasureOverrideArrangeOverride,大家都会认为这是 WPF 布局系统给我们提供的两个可供重写的方法。然而,这两个方法其实也是 FrameworkElement 才提供的。

真正布局的方法是 Measure 和 Arrange,而可供重写的方法是 MeasureCoreArrangeCore。这两组方法均来自于 UIElement,而布局系统其实是 UIElement 引入的。

那么 FrameworkElement 做了什么呢?它密封了 MeasureCoreArrangeCore 这两个布局的重写方法,以便能够处理 WidthHeightMinWidthMinHeightMaxWidthMaxHeightMargin 这些属性对布局的影响。

你觉得 WidthHeight 属性是元素的最终宽高吗?我们在 宽度和高度 一节中已经说了不是,前面一段也说了不是——它们真的只是布局属性!然而,这真的很容易形成误解!Width``Height 属性其实和 MinWidth``MinHeightMaxWidth``MaxHeight 是完全一样的用途,只是在布局过程中为计算最终尺寸提供的布局限制而已。只不过 MinWidth``MinHeightMaxWidth``MaxHeight 用大于和小于进行尺寸的限制,而 Width``Height 用等于进行尺寸的限制。最终的尺寸依然是 ActualWidth``ActualHeight,而这个值跟 RenderSize 其实是一个意思,因为内部获取的就是 RenderSize

值得注意的是,ActualWidth``ActualHeight 与 RenderSize 一样,是布局结束后才会更新的,开发中需要如果修改了属性立即获取这些值其实必然是旧的,拿这些值进行计算会造成错误的尺寸数据。

顺便吐槽一下:其实微软是喜欢用 Core 来作为子类重写方法的后缀的,比如 FreezableEasingFunction 都是用 Core 后缀来处理重写。Override 后缀纯属是因为 UIElement 把这个名字用了而pingmu 

屏幕交互

UIElement 中存在着布局计算,FrameworkElement 中存在着带限制的布局计算,这很容易让人以为屏幕相关的坐标计算会存在于 UIElement 或者 FrameworkElement 中。

然而其实 UIElement 或者 FrameworkElement 只涉及到控件之间的坐标计算(TranslatePoint),真正涉及到屏幕坐标的转换是位于 Visual 中的,典型的是这几个:

  • TransformToAncestor
  • TransformToDescendant
  • TransformToVisual
  • PointFromScreen
  • PointToScreen

所以其实如果希望做出非常轻量级的高性能 UI,继承自 Visual 也是一个大胆的选择。当然,真正遇到瓶颈的时候,继承自 Visual 也解决不了多少问

样式和模板

FrameworkElement 开始有了样式(Style),Control 开始有了模板(Template)。而模板极大地方便了样式定制的同时,也造成了强大的性能开销,因为本来的一个 Visual 瞬间变成了几个、几十个。一般情况下这根本不会是性能瓶颈,然而当这种控件会一次性产生几十个甚至数百个(例如表格)的时候,这种瓶颈就会非常明显。

总结容易出现理解偏差的几个点

  1. Width 和 Height 属性其实只是为布局过程中的计算进行限制而已,跟 MinWidthMinHeightMaxWidthMaxHeight 没有区别,并不直接决定实际尺寸。
  2. 如果发现元素布局中被切掉了,这并不是不可避免的问题;因为切掉是 FrameworkElement 为我们引入的特性,不喜欢可以随时关掉。
  3. 微软对于子类重写核心逻辑的方法喜欢使用 Core 后缀,布局中用了 Override 只是因为名字被占用了。
  4. Visual 就可以计算与屏幕坐标之间的转换。
  5. 模板(Template)会额外产生很多个 Visual,有可能会成为性能瓶颈。
posted @ 2021-05-21 13:26  zhlhl  阅读(377)  评论(0编辑  收藏  举报