【问题标题】:How is XAML interpreted and executed at runtime?XAML 在运行时如何解释和执行?
【发布时间】:2013-12-11 20:07:15
【问题描述】:

在加载 XAML(或 BAML)和接收根对象(例如,Window)之间,内部会发生什么?

首先浮现在我脑海中的是 Reflecton 用于创建对象、设置它们的属性等等。但也许我错了?

也许有人可以解释 XAML/BAML 在运行时是如何被解析和执行的,或者提供一个带有解释的好文章的链接?

为了让我的问题更清楚一点,让我们讨论一下这个简短的例子:

<Button Margin="10">OK</Button>

因此,解析器看到需要创建一个 Button 对象,它的 Margin 属性必须设置为 10,其内容必须设置为“OK”。这是怎么做的?通过使用反射(加上 TypeConverters 等)?

【问题讨论】:

    标签: .net wpf xaml


    【解决方案1】:

    您可能会注意到,在您的 .xaml.cs 文件中,支持已编译 XAML 文件的类被标记为 partial 类。 XAML 构建任务生成第二个.cs 文件,其中包含另一个包含IComponentConnector.InitializeComponent() 方法实现的部分类部分,该方法由后面代码中的默认构造函数调用。此方法基本上贯穿 XAML(此时实际上是 BAML 形式)并使用它来“修复”新创建的对象,而不是从 XAML 源创建新对象,如果你是使用XamlReader 来加载或解析对象。

    因此,当您实例化一个新的已编译 XAML 对象(例如,UserControl)时,构造函数中调用 InitializeComponent() 之前的任何代码都将执行。然后,在调用InitializeComponent() 期间将处理在 XAML 文件中设置的所有属性和事件处理程序,之后构造函数将恢复。了解这一点很有用,因为您可能希望确保在处理 XAML 文件之前或之后设置某些属性。

    关于 XAML 的解析方式,它本质上被读取为表示属性分配、对象声明等的 XAML 节点流,这些由System.Xaml 中的服务按顺序执行。这个节点流基于一个通用对象模型,它可能是由一个 BAML 流、一个 XML 文档(例如,一个松散的.xaml 文件)、另一个对象实例等构成的。BAML 比 XML-基于格式,通常解析速度更快。


    附录:在您添加的示例中,您询问解析器如何看到需要创建Button 对象和Margin 设置。简短的回答是:这取决于。具体来说,它取决于用于读取 XAML 流的架构上下文。

    XAML 解析器使用自己的类型系统,其中至少有两种实现:

    1. 基于反射和System.ComponentModel的标准CLR类型系统;
    2. 一种 WPF 类型系统,通过包括对依赖属性和路由事件的特殊支持来扩展 #1。

    这是,根据我对 XAML 语言规范的回忆,大概会发生什么:

    1. XAML 解析器遇到 Button 类型的 StartObject 节点,该类型(对于标准 WPF 命名空间映射)解析为 System.Windows.Controls.Button。这告诉解析器它需要创建一个 Button 对象的实例,它通过反射调用其默认构造函数来实现。
    2. 流中的下一个节点是StartMember 节点,成员名称为Margin。 WPF 架构上下文的类型模型会将其解析为 Margin 依赖属性。
    3. 接下来是一个Value 节点,它告诉解析器设置一个值"10"(一个字符串)。解析器看到属性类型Thickness 与字符串值不兼容。它查询其类型系统以查看Margin 属性上是否存在[ValueSerializer] 属性,该属性可用于转换字符串;不存在这样的属性。它检查[TypeConverter] 属性的属性;再次,它没有找到。它在Thickness 类型本身上查找[TypeConverter] 属性并找到一个,该属性指示它使用ThicknessConverter 将字符串值转换为Thickness。它这样做了。由于Margin是一个依赖属性,所以它使用SetValue() API来设置属性值;如果它是 CLR 属性,它将使用反射或 PropertyDescriptor
    4. EndMember 节点告诉解析器结束属性分配。
    5. 解析器遇到内容为"OK"Value 节点。解析器知道它正在构造一个复杂的对象,因此内容不能代表整个对象。它在Button 及其超类型上查找[ContentProperty] 属性;它在ContentControl 上找到一个,这表明该值应该用于设置Content 属性(它被解析为相应的依赖属性)。 Contentobject,因此它直接分配 string 值(同样,使用 SetValue())。
    6. 下一个节点是EndObject,它告诉解析器它已经完成了对Button对象的处理。

    请注意,我使用“解析器”一词来简化事情。说实话,这一切都不会发生在解析阶段(如果“解析”阶段甚至存在的话)。您可能认为的“解析”阶段只是构建 XAML 节点流。声明对象的创建和/或填充实际上是通过将该流输入XamlObjectWriter 来实现的,这只是XamlWriter 的实现,它将XAML 节点写入对象(而不是XML 文档或BAML 流)。在高层次上,只有两件事发生:

    1. XamlReader 将某些内容转换为 XAML 节点流。
    2. XamlWriter 将 XAML 节点流转换成某种东西。

    对于已编译的 XAML 资源,编译时构建任务将 XamlXmlReader 的输出通过管道传输到 BamlWriter 以“编译” XAML。在运行时,BamlReader 的输入通过管道传输到 XamlObjectWriter 以创建或“修复”根对象。

    一旦您了解了所有这些,您可能会开始认识到 XAML 是一种强大的序列化和持久性格式,而不仅仅是一种用于构建 UI 的语言。

    【讨论】:

    • 迈克,谢谢你的好回答。你的附录是我所需要的。所以,通过 WPF 引擎构建对象树涉及到大量使用反射,在我们的微示例中 - 构造一个 Button 对象,找出使用哪个 TypeConverter,构造一个 TypeConverter,找出 Margin 是一个依赖属性等等。对吧?
    • 是的,在 XAML 类型系统中进行了大量的反射。
    猜你喜欢
    • 2013-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-18
    • 1970-01-01
    • 1970-01-01
    • 2018-09-21
    相关资源
    最近更新 更多