起因
最近有遇到有小伙伴在实现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 }