WPF中的XAML是如何转换成对象的?

起因

最近有遇到有小伙伴在实现TreeView不同层级使用不同数据模板时,遇到了一些问题。

经过查阅资料,我提供了两种解决方案。

第一种是使用TemplateSelector,这种方式可以根据ViewModel设置不同的数据模板。

第二种是根据数据动态创建数据模板。

这两种解决方案都是基于后台代码的,但这位小伙伴他执着于在XAML中实现。

我跟他说XAML的实现和后台代码实现是一样的,都会转换成对应的对象的,XAML相当于一种助记符,它内部其实还是.cs代码。但是我也拿不出证据来证明这个。

我记得以前在哪看到过,XAML最终都是会转换为.cs代码的,但我一下也想不起来了。

干脆就直接查一查WPF的内部实现好了,看看XAML是如何转换为对象的。

InitializeComponent

平常我们在WPF中创建一个窗口时,都会在构造函数里看到有个默认的函数InitializeComponent();

让我们来看一看它的内部

复制代码
 1  /// <summary>
 2  /// InitializeComponent
 3  /// </summary>
 4  [System.Diagnostics.DebuggerNonUserCodeAttribute()]
 5  [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
 6  public void InitializeComponent() {
 7      if (_contentLoaded) {
 8          return;
 9      }
10      _contentLoaded = true;
11      System.Uri resourceLocater = new System.Uri("/WpfApp25;component/mainwindow.xaml", System.UriKind.Relative);
12      
13      #line 1 "..\..\MainWindow.xaml"
14      System.Windows.Application.LoadComponent(this, resourceLocater);
15      
16      #line default
17      #line hidden
18  }

可以看到最核心的一句是

复制代码
1 System.Windows.Application.LoadComponent(this, resourceLocater);

这一句的作用是加载位于指定统一资源标识符的 XAML 文件 (URI) 。它会将Uri指定的XAML转换为Window对象,我们接着往下看。

Application启动WPF程序的过程

Application 类是封装WPF应用程序的一个类,可以把它理解为一个壳,然后通过**Application.Run()**函数启动WPF应用程序并打开指定的窗口。

平常我们在创建WPF程序时,系统已经为我们定义好了一个Application 类,在App.xaml 中,并指定了StartupUri 属性(指定StartupUri属性后不需要再调用Application.Run函数了,Application内部会将窗口创建出来并显示

复制代码
1 <Application x:Class="WpfApp25.App"
2              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4              xmlns:local="clr-namespace:WpfApp25"
5             StartupUri="MainWindow.xaml">
6     <Application.Resources>
7          
8     </Application.Resources>
9 </Application>

在Application类内部会调用DoStartup函数时,会将指定Uri的XAML转换为Window对象。

然后再设置这个窗口的Visibility为Visible,就将窗口显示了出来。

为了更清晰的了解整个过程,我们创建了一个startup 类,并在里面添加了一个Main 函数,同时将工程的启动对象设置我们自己创建的startup类。

这里我们手动创建了一个Application 类对象,并通过Application 类的LoadComponent 函数将XAML 加载并转换为Window 对象,再通过Application.Run函数启动整个WPF程序

复制代码
 1    public class startup
 2    {
 3        [STAThread]
 4        public static void Main(string[] args)
 5        {
 6            Application application = new Application();
 7            System.Uri resourceLocater = new System.Uri("/WpfApp25;component/MainWindow.xaml", System.UriKind.Relative);
 8            var obj  =  System.Windows.Application.LoadComponent(resourceLocater);
10            application.Run(obj as Window);
11        }
12    }

到这里我们应该很清晰的看到系统是通过Application.LoadComponent函数将XAML读取,并转换成相应的类。

Application.LoadComponent的内部

我们往下查找,可以看到关键的步骤是一个LoadBamlStreamWithSyncInfo函数,在前面部分代码中,将XAML转换成一个流,然后再通过这个函数读取流

复制代码
 1 [SecurityCritical]
 2 [SecurityTreatAsSafe]
 3 internal static object LoadComponent(Uri resourceLocator, bool bSkipJournaledProperties)
 4 {
 5     Uri resolvedUri = BindUriHelper.GetResolvedUri(BaseUriHelper.PackAppBaseUri, resourceLocator);
 6     PackagePart resourceOrContentPart = GetResourceOrContentPart(resolvedUri);
 7     ContentType contentType = new ContentType(resourceOrContentPart.ContentType);
 8     Stream stream = resourceOrContentPart.GetStream();
 9     ParserContext parserContext = new ParserContext();
10     parserContext.BaseUri = resolvedUri;
11     parserContext.SkipJournaledProperties = bSkipJournaledProperties;
12     if (MimeTypeMapper.BamlMime.AreTypeAndSubTypeEqual(contentType))
13     {
14         return LoadBamlStreamWithSyncInfo(stream, parserContext);
15     }
16     if (MimeTypeMapper.XamlMime.AreTypeAndSubTypeEqual(contentType))
17     {
18         return XamlReader.Load(stream, parserContext);
19     }
20     throw new Exception(SR.Get("ContentTypeNotSupported", contentType.ToString()));
21 }

LoadBamlStreamWithSyncInfo 的内部调用了XamlReader.LoadBaml函数

复制代码
 1 internal static object LoadBamlStreamWithSyncInfo(Stream stream, ParserContext pc)
 2 {
 3     object obj = null;
 4     if (s_NestedBamlLoadInfo == null)
 5     {
 6         s_NestedBamlLoadInfo = new Stack<NestedBamlLoadInfo>();
 7     }
 8     NestedBamlLoadInfo item = new NestedBamlLoadInfo(pc.BaseUri, stream, pc.SkipJournaledProperties);
 9     s_NestedBamlLoadInfo.Push(item);
10     try
11     {
12         return XamlReader.LoadBaml(stream, pc, null, closeStream: true);
13     }
14     finally
15     {
16         s_NestedBamlLoadInfo.Pop();
17         if (s_NestedBamlLoadInfo.Count == 0)
18         {
19             s_NestedBamlLoadInfo = null;
20         }
21     }
22 }

XamlReader.LoadBaml的内部调用如下

这里有两个关键步骤

1、创建了一个System.Windows.Baml2006.Baml2006ReaderInternal对象,将前面的流通过参数传了进去

2、调用WpfXamlLoader.LoadBaml函数,将System.Windows.Baml2006.Baml2006ReaderInternal对象传进去。

复制代码
 1 [SecurityCritical]
 2 [SecurityTreatAsSafe]
 3 internal static object LoadBaml(Stream stream, ParserContext parserContext, object parent, bool closeStream)
 4 {
 5     object obj = null;
 6     EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordPerf | EventTrace.Keyword.KeywordXamlBaml, EventTrace.Event.WClientParseBamlBegin, parserContext.BaseUri);
 7     if (TraceMarkup.IsEnabled)
 8     {
 9         TraceMarkup.Trace(TraceEventType.Start, TraceMarkup.Load);
10     }
11     try
12     {
13         IStreamInfo streamInfo = stream as IStreamInfo;
14         if (streamInfo != null)
15         {
16             parserContext.StreamCreatedAssembly = streamInfo.Assembly;
17         }
18         Baml2006ReaderSettings baml2006ReaderSettings = CreateBamlReaderSettings();
19         baml2006ReaderSettings.BaseUri = parserContext.BaseUri;
20         baml2006ReaderSettings.LocalAssembly = streamInfo.Assembly;
21         if (baml2006ReaderSettings.BaseUri == null || string.IsNullOrEmpty(baml2006ReaderSettings.BaseUri.ToString()))
22         {
23             baml2006ReaderSettings.BaseUri = BaseUriHelper.PackAppBaseUri;
24         }
25         Baml2006ReaderInternal xamlReader = new Baml2006ReaderInternal(stream, new Baml2006SchemaContext(baml2006ReaderSettings.LocalAssembly), baml2006ReaderSettings, parent);
26         Type type = null;
27         if (streamInfo.Assembly != null)
28         {
29             try
30             {
31                 type = XamlTypeMapper.GetInternalTypeHelperTypeFromAssembly(parserContext);
32             }
33             catch (Exception ex)
34             {
35                 if (CriticalExceptions.IsCriticalException(ex))
36                 {
37                     throw;
38                 }
39             }
40         }
41         if (type != null)
42         {
43             XamlAccessLevel xamlAccessLevel = XamlAccessLevel.AssemblyAccessTo(streamInfo.Assembly);
44             XamlLoadPermission xamlLoadPermission = new XamlLoadPermission(xamlAccessLevel);
45             xamlLoadPermission.Assert();
46             try
47             {
48                 obj = WpfXamlLoader.LoadBaml(xamlReader, parserContext.SkipJournaledProperties, parent, xamlAccessLevel, parserContext.BaseUri);
49             }
50             finally
51             {
52                 CodeAccessPermission.RevertAssert();
53             }
54         }
55         else
56         {
57             obj = WpfXamlLoader.LoadBaml(xamlReader, parserContext.SkipJournaledProperties, parent, null, parserContext.BaseUri);
58         }
59         if (obj is DependencyObject dependencyObject)
60         {
61             dependencyObject.SetValue(BaseUriHelper.BaseUriProperty, baml2006ReaderSettings.BaseUri);
62         }
63         if (obj is Application application)
64         {
65             application.ApplicationMarkupBaseUri = GetBaseUri(baml2006ReaderSettings.BaseUri);
66         }
67     }
68     finally
69     {
70         if (TraceMarkup.IsEnabled)
71         {
72             TraceMarkup.Trace(TraceEventType.Stop, TraceMarkup.Load, obj);
73         }
74         EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordPerf | EventTrace.Keyword.KeywordXamlBaml, EventTrace.Event.WClientParseBamlEnd, parserContext.BaseUri);
75         if (closeStream)
76         {
77             stream?.Close();
78         }
79     }
80     return obj;
81 }

WpfXamlLoad.LoadBaml函数的内部调用如下

这一步有两个关键步骤

1、创建一个System.Xaml.XamlObjectWriter对象

2、调用TransformNodes函数。在TransformNodes函数内部,将System.Windows.Baml2006.Baml2006ReaderInternal对象读取的内容进行转换,根据特定规则,将数据重新写入到System.Xaml.XamlObjectWriter对象中

复制代码
 1 private static object Load(System.Xaml.XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, bool skipJournaledProperties, object rootObject, XamlObjectWriterSettings settings, Uri baseUri)
 2 {
 3     XamlObjectWriter xamlObjectWriter = null;
 4     MS.Internal.Xaml.Context.XamlContextStack<WpfXamlFrame> stack = new MS.Internal.Xaml.Context.XamlContextStack<WpfXamlFrame>(() => new WpfXamlFrame());
 5     int persistId = 1;
 6     
 7     xamlObjectWriter = ((writerFactory == null) ? new XamlObjectWriter(xamlReader.SchemaContext, settings) : writerFactory.GetXamlObjectWriter(settings));
 8     IXamlLineInfo xamlLineInfo = null;
 9     try
10     {
11         xamlLineInfo = xamlReader as IXamlLineInfo;
12         IXamlLineInfoConsumer xamlLineInfoConsumer = xamlObjectWriter;
13         bool shouldPassLineNumberInfo = false;
14         if (xamlLineInfo != null && xamlLineInfo.HasLineInfo && xamlLineInfoConsumer != null && xamlLineInfoConsumer.ShouldProvideLineInfo)
15         {
16             shouldPassLineNumberInfo = true;
17         }
18         IStyleConnector styleConnector = rootObject as IStyleConnector;
19         TransformNodes(xamlReader, xamlObjectWriter, onlyLoadOneNode: false, skipJournaledProperties, shouldPassLineNumberInfo, xamlLineInfo, xamlLineInfoConsumer, stack, styleConnector);
20         xamlObjectWriter.Close();
21         return xamlObjectWriter.Result;
22     }
23     catch (Exception ex)
24     {
25         if (CriticalExceptions.IsCriticalException(ex) || !XamlReader.ShouldReWrapException(ex, baseUri))
26         {
27             throw;
28         }
29         XamlReader.RewrapException(ex, xamlLineInfo, baseUri);
30         return null;
31     }
32 }

然后再获取System.Xaml.XamlObjectWriterResult 属性,最终得到了MainWindow对象

System.Xaml.XamlObjectWriter的内部实现这里就没去深究了,有兴趣的小伙伴可以查阅相关资料进行了解。

最后我们来看一看TransformNodes函数的内部实现

复制代码
  1 internal static void TransformNodes(System.Xaml.XamlReader xamlReader, XamlObjectWriter xamlWriter, bool onlyLoadOneNode, bool skipJournaledProperties, bool shouldPassLineNumberInfo, IXamlLineInfo xamlLineInfo, IXamlLineInfoConsumer xamlLineInfoConsumer, MS.Internal.Xaml.Context.XamlContextStack<WpfXamlFrame> stack, IStyleConnector styleConnector)
  2 {
  3     while (xamlReader.Read())
  4     {
  5         if (shouldPassLineNumberInfo && xamlLineInfo.LineNumber != 0)
  6         {
  7             xamlLineInfoConsumer.SetLineInfo(xamlLineInfo.LineNumber, xamlLineInfo.LinePosition);
  8         }
  9         switch (xamlReader.NodeType)
 10         {
 11         case System.Xaml.XamlNodeType.NamespaceDeclaration:
 12             xamlWriter.WriteNode(xamlReader);
 13             if (stack.Depth == 0 || stack.CurrentFrame.Type != null)
 14             {
 15                 stack.PushScope();
 16                 for (WpfXamlFrame wpfXamlFrame = stack.CurrentFrame; wpfXamlFrame != null; wpfXamlFrame = (WpfXamlFrame)wpfXamlFrame.Previous)
 17                 {
 18                     if (wpfXamlFrame.XmlnsDictionary != null)
 19                     {
 20                         stack.CurrentFrame.XmlnsDictionary = new XmlnsDictionary(wpfXamlFrame.XmlnsDictionary);
 21                         break;
 22                     }
 23                 }
 24                 if (stack.CurrentFrame.XmlnsDictionary == null)
 25                 {
 26                     stack.CurrentFrame.XmlnsDictionary = new XmlnsDictionary();
 27                 }
 28             }
 29             stack.CurrentFrame.XmlnsDictionary.Add(xamlReader.Namespace.Prefix, xamlReader.Namespace.Namespace);
 30             break;
 31         case System.Xaml.XamlNodeType.StartObject:
 32             WriteStartObject(xamlReader, xamlWriter, stack);
 33             break;
 34         case System.Xaml.XamlNodeType.GetObject:
 35             xamlWriter.WriteNode(xamlReader);
 36             if (stack.CurrentFrame.Type != null)
 37             {
 38                 stack.PushScope();
 39             }
 40             stack.CurrentFrame.Type = stack.PreviousFrame.Property.Type;
 41             break;
 42         case System.Xaml.XamlNodeType.EndObject:
 43             xamlWriter.WriteNode(xamlReader);
 44             if (stack.CurrentFrame.FreezeFreezable && xamlWriter.Result is Freezable freezable && freezable.CanFreeze)
 45             {
 46                 freezable.Freeze();
 47             }
 48             if (xamlWriter.Result is DependencyObject dependencyObject && stack.CurrentFrame.XmlSpace.HasValue)
 49             {
 50                 XmlAttributeProperties.SetXmlSpace(dependencyObject, stack.CurrentFrame.XmlSpace.Value ? "default" : "preserve");
 51             }
 52             stack.PopScope();
 53             break;
 54         case System.Xaml.XamlNodeType.StartMember:
 55         {
 56             if ((!xamlReader.Member.IsDirective || !(xamlReader.Member == XamlReaderHelper.Freeze)) && xamlReader.Member != XmlSpace.Value && xamlReader.Member != XamlLanguage.Space)
 57             {
 58                 xamlWriter.WriteNode(xamlReader);
 59             }
 60             stack.CurrentFrame.Property = xamlReader.Member;
 61             if (!skipJournaledProperties || stack.CurrentFrame.Property.IsDirective)
 62             {
 63                 break;
 64             }
 65             WpfXamlMember wpfXamlMember = stack.CurrentFrame.Property as WpfXamlMember;
 66             if (!(wpfXamlMember != null))
 67             {
 68                 break;
 69             }
 70             DependencyProperty dependencyProperty = wpfXamlMember.DependencyProperty;
 71             if (dependencyProperty == null || !(dependencyProperty.GetMetadata(stack.CurrentFrame.Type.UnderlyingType) is FrameworkPropertyMetadata frameworkPropertyMetadata) || !frameworkPropertyMetadata.Journal)
 72             {
 73                 break;
 74             }
 75             int num = 1;
 76             while (xamlReader.Read())
 77             {
 78                 switch (xamlReader.NodeType)
 79                 {
 80                 case System.Xaml.XamlNodeType.StartMember:
 81                     num++;
 82                     break;
 83                 case System.Xaml.XamlNodeType.StartObject:
 84                 {
 85                     XamlType type = xamlReader.Type;
 86                     XamlType xamlType = type.SchemaContext.GetXamlType(typeof(BindingBase));
 87                     XamlType xamlType2 = type.SchemaContext.GetXamlType(typeof(DynamicResourceExtension));
 88                     if (num == 1 && (type.CanAssignTo(xamlType) || type.CanAssignTo(xamlType2)))
 89                     {
 90                         num = 0;
 91                         WriteStartObject(xamlReader, xamlWriter, stack);
 92                     }
 93                     break;
 94                 }
 95                 case System.Xaml.XamlNodeType.EndMember:
 96                     num--;
 97                     if (num == 0)
 98                     {
 99                         xamlWriter.WriteNode(xamlReader);
100                         stack.CurrentFrame.Property = null;
101                     }
102                     break;
103                 case System.Xaml.XamlNodeType.Value:
104                     if (xamlReader.Value is DynamicResourceExtension)
105                     {
106                         WriteValue(xamlReader, xamlWriter, stack, styleConnector);
107                     }
108                     break;
109                 }
110                 if (num == 0)
111                 {
112                     break;
113                 }
114             }
115             break;
116         }
117         case System.Xaml.XamlNodeType.EndMember:
118         {
119             WpfXamlFrame currentFrame = stack.CurrentFrame;
120             XamlMember property = currentFrame.Property;
121             if ((!property.IsDirective || !(property == XamlReaderHelper.Freeze)) && property != XmlSpace.Value && property != XamlLanguage.Space)
122             {
123                 xamlWriter.WriteNode(xamlReader);
124             }
125             currentFrame.Property = null;
126             break;
127         }
128         case System.Xaml.XamlNodeType.Value:
129             WriteValue(xamlReader, xamlWriter, stack, styleConnector);
130             break;
131         default:
132             xamlWriter.WriteNode(xamlReader);
133             break;
134         }
135         if (onlyLoadOneNode)
136         {
137             break;
138         }
139     }
140 }

参考资料:

https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Application.cs,5374798b91523184

相关推荐
九鼎科技-Leo11 小时前
什么是 WPF 中的依赖属性?有什么作用?
windows·c#·.net·wpf
麻花201321 小时前
C#之WPF的C1FlexGrid空间的行加载事件和列事件变更处理动态加载的枚举值
开发语言·c#·wpf
lcintj21 小时前
【WPF】Prism学习(九)
学习·wpf·prism
界面开发小八哥21 小时前
界面控件DevExpress WPF中文教程:网格视图数据布局的列和卡片字段
wpf·界面控件·devexpress·ui开发·用户界面
△曉風殘月〆21 小时前
如何在WPF中嵌入其它程序
wpf
Crazy Struggle21 小时前
功能齐全的 WPF 自定义控件资源库(收藏版)
.net·wpf·ui控件库
shepherd枸杞泡茶1 天前
WPF动画
c#·.net·wpf
lcintj1 天前
【WPF】Prism学习(十)
学习·wpf·prism
wyh要好好学习1 天前
WPF数据加载时添加进度条
ui·wpf
code_shenbing1 天前
跨平台WPF框架Avalonia教程 三
前端·microsoft·ui·c#·wpf·跨平台·界面设计