I'm a long-time C# programmer but I'm completely new to WPF and XAML. I can find plenty of tutorials that say "this is how to achieve this specific thing" but not "this is why you do this to achieve this specific thing". I'm seriously struggling to understand the meaning of various syntax in XAML.
In this case, what do curly braces in attributes actually mean? What do they get translated to in terms of code? How do I reason about them? How do they get interpreted? Why does there seem to be multiple syntaxes (Binding="{Binding someProperty}"
vs Binding="{Binding path=someProperty}"
)?
I must be missing something obvious, but I've spent literally days reading tutorials, watching tutorials, even fighting my way through the immensely dry and difficult-to-understand Microsoft documentation and I still can't seem to figure it out.
Let me try to illustrate where I'm getting stuck.
For example, say I was given this:
int result = SomeUnknownFunction(42, 79);
I don't have to know what SomeUnknownFunction
does to reason about what's happening here. I can say, ok, the first part defines a new variable of type int
, the second part runs some function with two inputs, and the equals sign assigns the result of that function to the variable.
Now say I was given this:
<TextBox Text="{Binding Name}">
I can say, ok, when the XAML parser gets to this bit, the C# code it spits out creates an object instance of type TextBox
. That class has (or inherits) a property called Text
, and we're setting it to... uh... some magic syntax inside curly braces...
This is where I'm stuck. I think some part of this is called a "markup extension" (which is the most incredibly generic and meaningless name possible), but I can't even figure out if that means "the curly braces and everything inside" or just "the Binding part". Sometimes there's an equals sign inside the curly braces, sometimes not. What's the difference? The word Binding
is a... something? Is it a function and we're passing in Name
, and it returns a new Binding object that is somehow assigned to the Binding
property?
They say the best way to get a correct answer on the internet is to post an incorrect one, so let's give that a go.
I'll use this as an example:
<TextBox x:Name="MyTextBox" Text="Some default text :)"/>
<Label Content="{Binding Text, ElementName=MyTextBox}"/>
This binds the Content
property of the Label
class to the Text
property of the TextBox
class, giving us this result:
When the XAML compiler runs, <Label ... />
will be translated into code that generates an instance of the Label
class at runtime. We can set properties on that instance from XAML. Normally, when you set a property using the Attribute syntax, the string within the double-quotes is exactly what the property will be set to. For instance:
<Label Content="Binding Text, ElementName=MyTextBox"/>
Since we didn't use curly braces, that string will be taken literally as the value to assign to the Content
property:
This is what we've learnt so far:
If we add the curly braces, now the XAML compiler treats it differently. The word before the first space in the curly braces is now treated as the name of a Markup Extension. Markup Extensions are classes inheriting from the MarkupExtension
class.
In this case, the first part of the text in the curly braces is Binding
. By convention, Markup Extension classes are usually named with "Extension" on the end, like BindingExtension
1. XAML is smart and allows you to save some typing and be a little less verbose by leaving out the Extension
part of the class name when typing the name of a Markup Extension in curly braces.
So now we know what the curly braces and first part of the string inside those curly braces mean, but what about the rest? Why is there sometimes an equals sign and sometimes not?
After the text Binding
there's a space, and the rest of the string is treated as comma-separated inputs to the Markup Extension. How does that work in practice? Well, if the first inputs have no equals sign they'll be passed into the Markup Extension class' constructor as strings.2
In our example, the Binding
class would be created with a parameter value of Text
. If you have a look at the source for the Binding
class, you can see there's two constructors. The one that takes a string sets the Path
property to the value of that string:
/// <summary>
/// Default constructor.
/// </summary>
public Binding() {}
/// <summary>
/// Convenience constructor. Sets most fields to default values.
/// </summary>
/// <param name="path">source path </param>
public Binding(string path)
{
if (path != null)
{
if (System.Windows.Threading.Dispatcher.CurrentDispatcher == null)
throw new InvalidOperationException(); // This is actually never called since CurrentDispatcher will throw if null.
Path = new PropertyPath(path, (object[])null);
}
}
After the class has been instantiated using whatever constructor parameters you supplied, it then treats the remaining equal-sign-separated inputs as key-value pairs. The key is the name of a public property on the Markup Extension and the value is the what that public property is set to.
If you again look at the Binding
class' source, you'll see there's a public property called ElementName
. In our example, ElementName
is being set to the literal string value of MyTextBox
.
The curly braces tell the XAML compiler to treat the contents of those curly braces as the name of a class inheriting from MarkupExtension
, followed by a space, followed by the constructor parameter values (if any), followed by key-value pairs that represent the names of public properties and their values.
The property's value will then be calculated by calling the ProvideValue
method on the class extending MarkupExtension
.
1. The Binding
class is an exception to this rule, and is literally just called Binding
without the "Extension" part.
2. I'm not sure what happens if there's no matching constructor.
Disclaimer: I have absolutely no clue what I'm talking about. Please correct this if it's wrong!