We generally set the binding source using the FrameworkElement.DataContext property. All UI controls extend the FrameworkElement class, so we can set our binding sources on any of them. This must be set for a binding to work, although it can be specified in the Path property, or inherited from ancestor controls, so it does not have to be explicitly set. Take a look at this simple example, which assumes that a suitable binding source has been correctly set on the parent control:
<StackPanel> <TextBlock DataContext="{Binding User}" Text="{Binding Name}" /> <TextBlock DataContext="{Binding User}" Text="{Binding Age}" /> </StackPanel>
Here, we set the binding source of the first TextBlock to a User object and the path to the Name property from that source. The second is set likewise, but with the binding source path pointing to the Age property instead. Note that we have set the DataContext property to a User object on each TextBox control individually.
While this is perfectly valid XAML, you can imagine how tiresome it would be to do this on every control that we want to data bind to in a large form. As such, we tend to take advantage of the fact that the DataContext property can inherit its value from any of its ancestor controls. In this way, we can simplify this code by setting the DataContext on the parent control instead:
<StackPanel DataContext="{Binding User}"> <TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding Age}" /> </StackPanel>
In fact, when developing each Window or UserControl, it is customary to set the DataContext on these top-level controls, so that every contained control will have access to the same binding source. This is why we create a View Model for each Window or UserControl and specify that each View Model is responsible for providing all of the data and functionality that its related View requires.
There are a few alternative ways of specifying a binding source, other than setting the DataContext property. One way is to use the Source property of the binding and this enables us to explicitly override the binding source that is inherited from the parent DataContext, if one was set. Using the Source property, we are also able to data bind to resources, as we saw in our View Model Locator example, or static values, as shown in the following snippet:
<TextBlock Text="{Binding Source={x:Static System:DateTime.Today}, Mode=OneTime, StringFormat='{}© {0:yyyy} CompanyName'}" />
Another way involves the use of the RelativeSource property of the binding. Using this incredibly useful property of type RelativeSource, we can specify that we want to use the target control, or a parent of that control as the binding source.
It also enables us to override the binding source from the DataContext and is often essential when trying to data bind to View Model properties from DataTemplate elements. Let's adjust the earlier DataTemplate for our User Data Model to output a property from its normal DataContext that is set by the DataTemplate, and one from the View Model that is set as the DataContext of the parent control, using the AncestorType property of the RelativeSource class:
<DataTemplate DataType="{x:Type DataModels:User}"> <StackPanel> <TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding DataContext.UserCount, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Views:UserView}}}" /> </StackPanel> </DataTemplate>
Note that setting the Mode property, that specifies the relative position of the binding source compared to the binding target, is optional here. Using the AncestorType property implicitly sets the Mode property to the FindAncestor instance, so we can declare the same binding without it, like this:
<TextBlock Text="{Binding DataContext.UserCount, RelativeSource={RelativeSource AncestorType={x:Type Views:UserView}}}" />
The Mode property is of the RelativeSourceMode enumeration type, which has four members. We've already seen an example of one instance, the FindAncestor member, although this can be extended using the related RelativeSource.AncestorLevel property, which specifies which level of ancestor in which to look for the binding source. This property is only really useful if a control has multiple ancestors of the same type, as in this following simplified example:
<StackPanel Tag="Outer"> ... <StackPanel Orientation="Horizontal" Tag="Inner"> <TextBlock Text="{Binding Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type StackPanel}, AncestorLevel=2}}" /> ... </StackPanel> </StackPanel>
The TextBox in this example will output the word "Outer" at runtime because we have declared that the binding source should be the second ancestor of type StackPanel. If the AncestorLevel property had been set to one or omitted from the binding, then the TextBox would output the word "Inner" at runtime.
The next RelativeSourceMode enumeration instance is Self, which specifies that the binding source is the same object as the binding target. Note that when using the RelativeSource.Self property, the Mode property is implicitly set to the Self instance. We could use this property to data bind one property of a UI control to another, as in this following example, which sets the control's width value to its Height property to ensure that it remains a square regardless of the width:
<Rectangle Height="{Binding ActualWidth, RelativeSource={RelativeSource Self}}" Fill="Red" />
The RelativeSource.TemplatedParent property is only used to access the properties of controls from inside a ControlTemplate. The templated parent refers to the object that has the ControlTemplate applied to it. When using the TemplatedParent property, the Mode property is implicitly set to the TemplatedParent instance of the RelativeSourceMode enumeration. Let's see an example:
<ControlTemplate x:Key="ProgressBar" TargetType="{x:Type ProgressBar}"> ... <TextBlock Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}}" /> ... </ControlTemplate>
In this example, the templated parent is the instance of the ProgressBar that will have this template applied to it and so, using the TemplatedParent property, we are able to access the various properties of the ProgressBar class from within the ControlTemplate. Furthermore, any binding source that is data bound to the Value property of the templated parent will also be data bound to the Text property of this internal TextBox element.
Moving on to the final RelativeSource property, PreviousData is only really useful when defining a DataTemplate for items in a collection. It is used to set the previous item in the collection as the binding source. While not often used, there can be situations where we need to compare values between neighboring items in a collection and we'll see a full example of this later in this chapter.
Although a far simpler option, the ElementName property of the Binding class also enables us to override the binding source set by the DataContext. It is used to data bind the property of one UI control to either the property of another control, or another property on the same control. The only requirement to use this property is that we need to name the element that we want to data bind to in our current control. Let's see an example:
<StackPanel Orientation="Horizontal" Margin="20"> <CheckBox Name="Checkbox" Content="Service" Margin="0,0,10,0" /> <TextBox Text="{Binding Service}" Visibility="{Binding IsChecked, ElementName=Checkbox, Converter={StaticResource BoolToVisibilityConverter}}" /> </StackPanel>
In this example, we have a CheckBox element and a TextBlock element. The Visibility property of the TextBlock element is data bound to the IsChecked property of the CheckBox element and we make use of the BoolToVisibilityConverter class that we saw earlier to convert the bool value to a Visibility instance. Therefore, when the user checks the CheckBox element, the TextBlock element will become visible.
The ElementName property can also be used as a shortcut to access the parent control's DataContext. If we name our View This for example, then we can use the ElementName property from within a data template to data bind to a property from the parent View Model:
<DataTemplate DataType="{x:Type DataModels:User}"> <StackPanel> <TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding DataContext.UserCount, ElementName=This}" /> </StackPanel> </DataTemplate>
When specifying these alternative binding sources, it is important to know that we can only use one of these three different methods at once. If we were to set more than one of the binding Source, RelativeSource, or ElementName properties, then an exception would be thrown from the binding.