起因
最近有遇到有小伙伴在实现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.XamlObjectWriter的Result属性,最终得到了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