Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

08 Aug 2020

厘清XAML的UIElement/FrameworkElement/Control之间的关系

本文来谈谈WinRT的Windows.UI.XAML的UIElement、FrameworkElement以及Control之间的关系。

如果打开UIElement的文档,会发现它继承自DependencyObject(姑且叫做相依对象)。相依对象再往上,就是IInspectable类型了,对应的是C#中的System.Object。

过往的文章有提到,XAML中的元素对象几乎都是从相依对象继承而来的。相依对象给XAML提供了相依部属,让不同的元素对象可以通过相依部属来互相通信。这其实是给静态的、声明式的XAML提供了一个可计算的平台,可以通过相依部属来进行值推导。所以稍微带点协作功能的XAML元素对象,其类型都是从相依对象派生出来的。在DependencyObject页面可以看到一大堆从它派生出来的对象。大概有上百个。

UIElement所有UI对象的基础类型。它包含的部属对大部分UI对象都通用。那什么样的部属对大部分UI对象都通用呢?基本上是事件处理相关的。不管一个UI对象有没有在窗口上显示,它都可以有事件处理相关的逻辑不是!所以UIElement定义了血多UI相关的事件。

此外UIElement还定义了跟显示有关,但是跟布局无关的部属,比如Visibility和Opacity。

UIElement也定义了跟显示有关的方法,比如Mesure和Arrange,来获取一众子对象的大小以及对其进行排布。

UIElement唯一的派生类是FrameworkElement。这里让我感觉到奇怪的一点是,既然只有一个唯一派生的类,为什么不把两个类型合在一起呢?觉得稍微有点多此一举。

FrameworkElement的派生类型就比较多了,基本上能在屏幕占上尺寸的元素对象都是从FrameworkElement派生出来的。你所熟知的跟尺寸布局相关的部属,都是FrameworkElement定义的:Height, Width, ActualHeight, ActualWidth, Margin, MeasureOverride, ArrangeOverride, HorizontalAlignment, VerticalAlignment, LayoutUpdated。

跟对象生命周期相关的成员:Loaded, SizeChanged, Unloaded, OnApplyTemplate。可能对于XAML来说,构成UI图形对象树的,都被看做是FrameworkElement吧。然后

MeasureOverride和ArrangeOverride是在UIElement进行Measure和Arrange的时候被调用的。可能是为了将命令式的函数调用转为被动式的操作吧。

FrameworkElement还对XAML绑定提供支持,相关的成员包括:DataContext, DataContextChanged, SetBinding, GetBindingExpression.。对在XAML中查找元素或者进行遍历提供支持的成员包括:Name, FindName, Parent, BaseUri, OnApplyTemplate。

FrameworkElement的FindName可以通过名字查询XAML中定义的对象。事实上,XAML第一次解析的时候,虽然在代码中生成相应的方法或者部属来访问XAML中定义的具名对象,但是这些对象真正的初始化是在调用InitializeComponent的时候,通过FindName找到对应的内部引用,并进行初始化的。具体参考XAML namescopes

另外FrameworkElement还对全球化,以及样式和主题提供支持。相关的成员包括:Style、RequestedTheme以及Resources。关于Resources,更多参考ResourceDictionary and XAML resource references

FrameworkElement派生出很多子类型:

  • Control
  • Presenters
  • 媒体和web元素:Image, WebView, MediaElement, CaptureElement
  • 文字显示相关:TextBlock, RichTextBlock, RichTextBlockOverflow, Glyphs.
  • Panel
  • Shape
  • IconElement
  • 其他,如Popup, TickBar, Viewbox

如果更进一步细看的话,其实上面这些类型大概可以分为三大类:

  • 内容需要通过XAML来推导出来的,主要是Control以及辅助它的Presenters和Panel。
  • 内容需要由平台提供的功能决出的,就是媒体、图形、文字相关的这些元素。
  • 其他其他杂七杂八的类型

下面来说说Control类型。这个Control特指那些界面呈现是由ControleTemplate来进行控制的控件。可以有隐性的样式,符合很多XAML定义的操作精神,包括界面呈现状态控制、输入焦点控制、事件控制等等。另外Control还定义了一些和文字显示相关的相依部属。

关于事件处理,为了实现特定的逻辑Control预先挂载了很多UIElement中定义了事件,所以很多事件变成了Handled。比如ButtonBase会把PointerPressed和KeyDown事件转化为Click事件。然后PointerPressed和KeyDown就变成Handled了。为了方便派生类型对Handled的事件进行响应和处理,Control通过OnEvent的形式提供钩子给派生类,比如OnPointerPressed和OnKeyDown。虽然Control的OnEvent其实是空实现,但是Control的某个前序类型可能提供了某些实现,不过是内部代码所以开发者看不到。

ControlTemplate是XAML中三大Template之一,其他两个是DataTemplate和ItemsPanelTemplate。这些Template类型都是从FrameworkTemplate继承出来的,而FrameworkTemplate又是直接从相依对象派生出来的。FrameworkTemplate定义的时候使用了一个从属值,来标注指定的模板必须保存在Template部属中:

[Windows.UI.Xaml.Markup.ContentProperty(Name="Template")]

Template的作用之一是推迟决定Control的界面呈现细节。有了Template之后,Control自己可以不决定该如何进行界面呈现,因为相关的工作推给了Template。Template则可以动态修改,并且可以有很多来源,给Control的界面呈现带来了很多灵活性。如果你使用过Javascript编程,或许会比较容易理解Template。JavaScript中一个函数的参数可以是一个值,也可以是另外一个函数。如果传入的是一个函数,那么通过执行那个函数来获取真正的参数。通过OnApplyTemplate和GetTemplateChild可以对Template应用之前和应用之后做一些干预和检视。

如果通过Control.Template设置一个新的Template的话,并不会立即生效。所以可能需要显示得调用ApplyTemplate使其生效。参考GetTemplateChild returns null when dependency property has RelativeSource binding

你可以通过Control.Template来直接为Control设置一个Template,或者也可以定义一个样式,让这个样式拥有和Control相同的隐性样式键名,让后在此样式中定义Tempalte。参考Quickstart: Control templates

Control有三个重要的派生类型,分别是ContentControlUserControlItemsControl,这三者都和内容组织相关。

先谈ItemsControl,它是用来处理列表的,也就是说它的内容是一个元素列表。可以通过ItemTemplate来指定一个DataTemplate来决定如何显示列表中的元素。然后还可以通过设置一个ItemsPanelTemplate来决定列表中的元素是如何组织在一起的。你可能还记得Panel是从FrameworkElement派生出来的,主要功能是组织多个子元素。

ContentControlItemsControl不同,ItemsControl只能有一个子元素作为其内容,然后可以通过ContentTemplate设置一个DataTemplate来控制内容的显示。ContentControl具有一个ContentTemplateSelector部属,可以设置一个DataTemplateSelector来决定采用那个模板。

UserControl则和ContentControl类似,只能由一个子元素作为其内容。但是UserControl没有为内容定义Template。如何显示内容则需要User(也就是开发者)决定。开发者需要制作一个XAML文件来控制内容显示。自定义控件常常从UserControl派生,这样可以通过关联的XAML文件来自定义界面呈现。如果直接从Control继承,因为没有关联的XAML文件,以外在XAML中定义的内容需要在代码中体现,会比较繁琐。但是省去了XAML解析的过程,性能会好一点。

值得注意的是UserControl只有一个派生类,就是常见的Page类型。也就是XAML应用中的MainPage的类型。

不管时什么类型的Control,都暴露在Windows.UI.Xaml.Controls命名空间。那些用来支持Control的具体实现的,则暴露在Windows.UI.Xaml.Controls.Primitives命名空间。

其他参考

(本篇完)

Categories

Tags

comments powered by Disqus