Xamarin.Forms Binding Path
In all the previous data-binding examples, the Path
property of the Binding
class (or the Path
property of the Binding
markup extension) has been set to a single property. It's actually possible to set Path
to a sub-property (a property of a property), or to a member of a collection.
For example, suppose your page contains a TimePicker
:
<TimePicker x:Name="timePicker">
The Time
property of TimePicker
is of type TimeSpan
, but perhaps you want to create a data binding that references the TotalSeconds
property of that TimeSpan
value. Here's the data binding:
{Binding Source={x:Reference timePicker},
Path=Time.TotalSeconds}
The Time
property is of type TimeSpan
, which has a TotalSeconds
property. The Time
and TotalSeconds
properties are simply connected with a period. The items in the Path
string always refer to properties and not to the types of these properties.
That example and several others are shown in the Path Variations page:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:globe="clr-namespace:System.Globalization;assembly=netstandard"
x:Class="DataBindingDemos.PathVariationsPage"
Title="Path Variations"
x:Name="page">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="FontSize" Value="Large" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Margin="10, 0">
<TimePicker x:Name="timePicker" />
<Label Text="{Binding Source={x:Reference timePicker},
Path=Time.TotalSeconds,
StringFormat='{0} total seconds'}" />
<Label Text="{Binding Source={x:Reference page},
Path=Content.Children.Count,
StringFormat='There are {0} children in this StackLayout'}" />
<Label Text="{Binding Source={x:Static globe:CultureInfo.CurrentCulture},
Path=DateTimeFormat.DayNames[3],
StringFormat='The middle day of the week is {0}'}" />
<Label>
<Label.Text>
<Binding Path="DateTimeFormat.DayNames[3]"
StringFormat="The middle day of the week in France is {0}">
<Binding.Source>
<globe:CultureInfo>
<x:Arguments>
<x:String>fr-FR</x:String>
</x:Arguments>
</globe:CultureInfo>
</Binding.Source>
</Binding>
</Label.Text>
</Label>
<Label Text="{Binding Source={x:Reference page},
Path=Content.Children[1].Text.Length,
StringFormat='The second Label has {0} characters'}" />
</StackLayout>
</ContentPage>
In the second Label
, the binding source is the page itself. The Content
property is of type StackLayout
, which has a Children
property of type IList<View>
, which has a Count
property indicating the number of children.
Paths with Indexers
The binding in the third Label
in the Path Variations pages references the CultureInfo
class in the System.Globalization
namespace:
<Label Text="{Binding Source={x:Static globe:CultureInfo.CurrentCulture},
Path=DateTimeFormat.DayNames[3],
StringFormat='The middle day of the week is {0}'}" />
The source is set to the static CultureInfo.CurrentCulture
property, which is an object of type CultureInfo
. That class defines a property named DateTimeFormat
of type DateTimeFormatInfo
that contains a DayNames
collection. The index selects the fourth item.
The fourth Label
does something similar but for the culture associated with France. The Source
property of the binding is set to CultureInfo
object with a constructor:
<Label>
<Label.Text>
<Binding Path="DateTimeFormat.DayNames[3]"
StringFormat="The middle day of the week in France is {0}">
<Binding.Source>
<globe:CultureInfo>
<x:Arguments>
<x:String>fr-FR</x:String>
</x:Arguments>
</globe:CultureInfo>
</Binding.Source>
</Binding>
</Label.Text>
</Label>
See Passing Constructor Arguments for more details on specifying constructor arguments in XAML.
Finally, the last example is similar to the second, except that it references one of the children of the StackLayout
:
<Label Text="{Binding Source={x:Reference page},
Path=Content.Children[1].Text.Length,
StringFormat='The first Label has {0} characters'}" />
That child is a Label
, which has a Text
property of type String
, which has a Length
property. The first Label
reports the TimeSpan
set in the TimePicker
, so when that text changes, the final Label
changes as well.
Here's the program running:
Debugging Complex Paths
Complex path definitions can be difficult to construct: You need to know the type of each sub-property or the type of items in the collection to correctly add the next sub-property, but the types themselves do not appear in the path. One good technique is to build up the path incrementally and look at the intermediate results. For that last example, you could start with no Path
definition at all:
<Label Text="{Binding Source={x:Reference page},
StringFormat='{0}'}" />
That displays the type of the binding source, or DataBindingDemos.PathVariationsPage
. You know PathVariationsPage
derives from ContentPage
, so it has a Content
property:
<Label Text="{Binding Source={x:Reference page},
Path=Content,
StringFormat='{0}'}" />
The type of the Content
property is now revealed to be Xamarin.Forms.StackLayout
. Add the Children
property to the Path
and the type is Xamarin.Forms.ElementCollection'1[Xamarin.Forms.View]
, which is a class internal to Xamarin.Forms, but obviously a collection type. Add an index to that and the type is Xamarin.Forms.Label
. Continue in this way.
As Xamarin.Forms processes the binding path, it installs a PropertyChanged
handler on any object in the path that implements the INotifyPropertyChanged
interface. For example, the final binding reacts to a change in the first Label
because the Text
property changes.
If a property in the binding path does not implement INotifyPropertyChanged
, any changes to that property will be ignored. Some changes could entirely invalidate the binding path, so you should use this technique only when the string of properties and sub-properties never become invalid.