What happens internally between loading XAML (or BAML) and receiving the root object (Window, for example)?
What first pops up in my mind is that Reflecton is used to create objects, set their properties and so on. But maybe I am wrong?
Maybe someone can explain how XAML/BAML is parsed and executed at runtime or give a link to a good article with the explanation?
To make my question a little bit more clear, let's discuss the short example:
<Button Margin="10">OK</Button>
So, the parser sees that a Button object needs to be created, that its Margin property has to be set to 10 and its content has to be set to "OK". How is that done? By using Reflection (plus TypeConverters, etc)?
The WPF markup compiler will create a partial class for any compiled XAML file, by deriving a class from the root element type. When you provide code-behind that also defines the same partial class, the resulting code is combined within the same namespace and class of the compiled app.
XAML stands for Extensible Application Markup Language. It's a simple and declarative language based on XML. In XAML, it very easy to create, initialize, and set properties of objects with hierarchical relations.
XAML is a new descriptive programming language developed by Microsoft to write user interfaces for next-generation managed applications. XAML is the language to build user interfaces for Windows and Mobile applications that use Windows Presentation Foundation (WPF), UWP, and Xamarin Forms.
You may notice in your .xaml.cs
files that the classes backing your compiled XAML files are marked as partial
classes. A XAML build task generates a second .cs
file with another partial class section containing an implementation of the IComponentConnector.InitializeComponent()
method, which is called by the default constructor in the code behind. This method basically runs through the XAML (which is actually in BAML form at this point) and uses it to "fix up" the newly created object, as opposed to creating a new object from the XAML source, which is what would happen if you were to use a XamlReader
to load or parse an object.
So, when you instantiate a new compiled XAML object (e.g., a UserControl
), whatever code precedes the call to InitializeComponent()
in the constructor will execute. Then, all the properties and event handlers set in the XAML file will be processed during the call to InitializeComponent()
, after which the constructor resumes. This can be useful to know, as you may want to ensure that certain properties get set before or after the XAML file is processed.
As to how the XAML is parsed, it is essentially read as a stream of XAML nodes representing property assignments, object declarations, etc., which are performed in order by the services in System.Xaml
. This node stream is based on a common object model, which may have been constructed from a BAML stream, an XML document (e.g., a loose .xaml
file), another object instance, etc. BAML, being more compact than the XML-based format, is generally faster to parse.
Addendum: In the example you added, you ask how the parser sees that a Button
object needs to be created and the Margin
set. The short answer is: it depends. Specifically, it depends on the schema context used to read the XAML stream.
The XAML parser uses its own type system, of which there are at least two implementations:
System.ComponentModel
;This is, based on my recollection of the XAML Language Spec, approximately what happens:
StartObject
node for the Button
type, which (for the standard WPF namespace mappings) resolves to System.Windows.Controls.Button
. This tells the parser it needs to create an instance of a Button
object, which it does by invoking its default constructor via reflection.StartMember
node, with a member name of Margin
. The type model for the WPF schema context will resolve this to the Margin
dependency property.Value
node comes next, which tells the parser to set a value of "10"
(a string). The parser sees that the property type Thickness
is incompatible with the string value. It consults its type system to see if a [ValueSerializer]
attribute exists on the Margin
property, which could be used to convert the string; no such attribute exists. It checks the property for a [TypeConverter]
attribute; again, it finds none. It looks for a [TypeConverter]
attribute on the Thickness
type itself and finds one, which instructs it to use a ThicknessConverter
to convert the string value into a Thickness
. It does so. Since Margin
is a dependency property, it uses the SetValue()
API to set the property value; if it were a CLR property, it would use reflection or a PropertyDescriptor
.EndMember
node tells the parser to end the property assignment.Value
node with the content "OK"
. The parser knows it is constructing a complex object, so the content cannot represent the entire object. It looks for a [ContentProperty]
attribute on Button
and its supertypes; it finds one on ContentControl
, which indicates that the value should be used to set the Content
property (which gets resolved to the corresponding dependency property). Content
is an object
, so it assigns the string
value directly (again, using SetValue()
).EndObject
, which tells the parser it has finished processing the Button
object.Note that I used the term "parser" to simplify things. Truthfully, none of this happens in the parsing stage (if a "parsing" stage even exists). What you may think of as the "parsing" stage is just the construction of a stream of XAML nodes. The creation and/or population of the declared object(s) actually happens by feeding that stream into a XamlObjectWriter
, which is simply the implementation of XamlWriter
which writes XAML nodes to an object (as opposed to an XML document or a BAML stream). At the high level, there are only two things happening:
XamlReader
transforms something into stream of XAML nodes.XamlWriter
transforms a stream of XAML nodes into something.In the case of a compled XAML resource, a compile-time build task pipes the output of a XamlXmlReader
into a BamlWriter
to "compile" the XAML. At runtime, the input of a BamlReader
is piped into an XamlObjectWriter
to create or "fix up" the root object.
Once you understand all of this, you may start to recognize XAML as a powerful serialization and persistence format, as opposed to merely a language for building UIs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With