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

2024-08-24 09:28
文章标签 对象 转换成 wpf xaml

本文主要是介绍WPF中的XAML是如何转换成对象的?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

起因

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

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

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

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

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

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

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

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

InitializeComponent

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

让我们来看一看它的内部

 1  /// <summary>2  /// InitializeComponent3  /// </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 startup2    {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

这篇关于WPF中的XAML是如何转换成对象的?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1102084

相关文章

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

SpringMVC高效获取JavaBean对象指南

《SpringMVC高效获取JavaBean对象指南》SpringMVC通过数据绑定自动将请求参数映射到JavaBean,支持表单、URL及JSON数据,需用@ModelAttribute、@Requ... 目录Spring MVC 获取 JavaBean 对象指南核心机制:数据绑定实现步骤1. 定义 Ja

Python打印对象所有属性和值的方法小结

《Python打印对象所有属性和值的方法小结》在Python开发过程中,调试代码时经常需要查看对象的当前状态,也就是对象的所有属性和对应的值,然而,Python并没有像PHP的print_r那样直接提... 目录python中打印对象所有属性和值的方法实现步骤1. 使用vars()和pprint()2. 使

MySQL JSON 查询中的对象与数组技巧及查询示例

《MySQLJSON查询中的对象与数组技巧及查询示例》MySQL中JSON对象和JSON数组查询的详细介绍及带有WHERE条件的查询示例,本文给大家介绍的非常详细,mysqljson查询示例相关知... 目录jsON 对象查询1. JSON_CONTAINS2. JSON_EXTRACT3. JSON_TA

C#之List集合去重复对象的实现方法

《C#之List集合去重复对象的实现方法》:本文主要介绍C#之List集合去重复对象的实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C# List集合去重复对象方法1、测试数据2、测试数据3、知识点补充总结C# List集合去重复对象方法1、测试数据

Spring中管理bean对象的方式(专业级说明)

《Spring中管理bean对象的方式(专业级说明)》在Spring框架中,Bean的管理是核心功能,主要通过IoC(控制反转)容器实现,下面给大家介绍Spring中管理bean对象的方式,感兴趣的朋... 目录1.Bean的声明与注册1.1 基于XML配置1.2 基于注解(主流方式)1.3 基于Java

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一

golang 对象池sync.Pool的实现

《golang对象池sync.Pool的实现》:本文主要介绍golang对象池sync.Pool的实现,用于缓存和复用临时对象,以减少内存分配和垃圾回收的压力,下面就来介绍一下,感兴趣的可以了解... 目录sync.Pool的用法原理sync.Pool 的使用示例sync.Pool 的使用场景注意sync.

SpringBoot项目中Redis存储Session对象序列化处理

《SpringBoot项目中Redis存储Session对象序列化处理》在SpringBoot项目中使用Redis存储Session时,对象的序列化和反序列化是关键步骤,下面我们就来讲讲如何在Spri... 目录一、为什么需要序列化处理二、Spring Boot 集成 Redis 存储 Session2.1