XAML在运行时如何解释和执行?

18

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

我首先想到的是使用反射创建对象,设置它们的属性等。但也许我错了?

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

为了让我的问题更加明确,让我们来讨论一个简短的例子:

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

因此,解析器看到需要创建一个Button对象,将其Margin属性设置为10并将其内容设置为“OK”。这是如何完成的?通过使用反射(加上TypeConverters等)吗?

1个回答

30
您可能会发现在您的 .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 文件)、另一个对象实例等构造。相对于基于 XML 的格式,BAML 更紧凑,通常解析速度更快。
补充说明:在您添加的示例中,您问解析器如何看到需要创建一个 Button 对象并设置 Margin。简短的答案是:这取决于用于读取 XAML 流的模式上下文。
XAML 解析器使用自己的类型系统,其中至少有两个实现:
  • 基于反射和System.ComponentModel的标准CLR类型系统;
  • A WPF类型系统扩展#1,包括对依赖属性和路由事件的特殊支持。
  • 大致上,在我的XAML语言规范记忆中,以下是发生的情况:

    1. XAML解析器遇到Button类型的StartObject节点,(对于标准WPF名称空间映射),它将解析为System.Windows.Controls.Button。这告诉解析器需要创建一个Button对象的实例,通过反射调用其默认构造函数来完成此操作。
    2. 接下来的节点是StartMember节点,成员名为Margin。WPF模式上下文的类型模型将把它解析为Margin依赖属性。
    3. 接着是一个Value节点,它告诉解析器要设置一个值"10"(一个字符串)。解析器发现属性类型Thickness与字符串值不兼容。它查询其类型系统,看看Margin属性上是否存在[ValueSerializer]属性,以转换字符串;没有这样的属性。它检查Margin属性是否有[TypeConverter]属性;同样,它找不到。它在Thickness类型本身上查找[TypeConverter]属性,并找到一个,它指示解析器使用ThicknessConverter将字符串值转换为Thickness。然后它这样做了。由于Margin是依赖属性,因此它使用SetValue()API来设置属性值;如果它是CLR属性,则会使用反射或PropertyDescriptor
    4. EndMember节点告诉解析器结束属性赋值。
    解析器遇到一个 Value 节点,其内容为"OK"。解析器知道它正在构建一个复杂对象,因此该内容无法代表整个对象。然后解析器会在 Button 及其超类型上查找 [ContentProperty] 属性;它在 ContentControl 上找到了该属性,这表明该值应用于设置 Content 属性(该属性将被解析为对应的依赖属性)。由于 Content 是一个 object,所以它直接分配了该 string 值(再次使用 SetValue())。
    下一个节点是 EndObject,告诉解析器已经完成处理 Button 对象。
    请注意,我使用术语“解析器”来简化事情。实际上,这些都不是在解析阶段发生的(如果“解析”阶段甚至存在的话)。您可能认为的“解析”阶段实际上只是构造XAML节点流。声明对象的创建和/或填充实际上是通过将该流馈送到 XamlObjectWriter 中进行的,它只是将XAML节点写入对象的 XamlWriter 的实现(而不是XML文档或BAML流)。从高层次来看,只有两件事情在发生:
    1. XamlReader 将某些东西转换为 XAML 节点流。
    2. XamlWriter 将 XAML 节点流转换为某些东西。
    对于复杂的 XAML 资源,编译时构建任务将 XamlXmlReader 的输出导入到 BamlWriter 中以“编译”XAML。运行时,BamlReader 的输入将被导入到 XamlObjectWriter 中以创建或“修正”根对象。

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


    Mike,谢谢你的好答案。你的附加说明正是我需要的。因此,通过WPF引擎构建对象树涉及大量使用反射,在我们的微型示例中 - 构建一个Button对象,找出要使用哪个TypeConverter,构建一个TypeConverter,找出Margin是一个依赖属性等。对吧? - WpfNewbie
    1
    是的,在XAML类型系统中有很多反射正在进行。 - Mike Strobel

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接