Путь привязки Xamarin.Forms

Во всех предыдущих примерах привязки данных в свойстве Path класса Binding (или свойстве Path расширения разметки Binding) указывалось одно свойство. В действительности Path может указывать на вложенное свойство (свойство свойства) или элемент коллекции.

Например, предположим, что страница содержит объект TimePicker:

<TimePicker x:Name="timePicker">

Свойство Time класса TimePicker имеет тип TimeSpan, но, возможно, вам нужно создать привязку данных, которая ссылается на свойство TotalSeconds этого значения TimeSpan. Вот эта привязка данных:

{Binding Source={x:Reference timePicker},
         Path=Time.TotalSeconds}

Свойство Time имеет тип TimeSpan, который имеет свойство TotalSeconds. Свойства Time и TotalSeconds соединяются точкой. Элементы в строке Path всегда ссылаются на свойства, а не на их типы.

Этот и еще ряд примеров представлены на странице Path Variations (Варианты пути):

<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>

Во втором элементе Label источником привязки является сама страница. Свойство Content имеет тип StackLayout, который имеет свойство Children типа IList<View>. Этот тип, в свою очередь, имеет свойство Count, в котором указывается число дочерних объектов.

Пути с индексаторами

Привязка в третьем элементе Label на странице Path Variations ссылается на класс CultureInfo в пространстве имен System.Globalization:

<Label Text="{Binding Source={x:Static globe:CultureInfo.CurrentCulture},
                      Path=DateTimeFormat.DayNames[3],
                      StringFormat='The middle day of the week is {0}'}" />

В качестве источника задано статическое свойство CultureInfo.CurrentCulture, представляющее собой объект типа CultureInfo. В этом классе определено свойство DateTimeFormat типа DateTimeFormatInfo, содержащее коллекцию DayNames. Индекс выбирает четвертый элемент.

Четвертый элемент Label выполняет схожие действия, но для французского языка и региональных параметров. Свойству Source привязки присваивается объект CultureInfo с конструктором:

<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>

Дополнительные сведения об указании аргументов конструктора в XAML см. в разделе Передача аргументов конструктора.

Наконец, последний пример аналогичен второму, за тем исключением, что он ссылается на один из дочерних элементов StackLayout:

<Label Text="{Binding Source={x:Reference page},
                      Path=Content.Children[1].Text.Length,
                      StringFormat='The first Label has {0} characters'}" />

Этот дочерний объект представляет собой элемент Label, который имеет свойство Text типа String, имеющего свойство Length. Первый элемент Label сообщает значение TimeSpan, заданное в TimePicker, поэтому при изменении текста итоговый элемент Label также меняется.

Вот работающая программа:

Варианты путей

Отладка сложных путей

Составлять определения сложных путей может быть непросто: нужно знать тип каждого вложенного свойства или элементов в коллекции, чтобы правильно добавить следующее вложенное свойство. Однако сами типы в пути отсутствуют. Один из приемов — формировать путь поэтапно, проверяя промежуточные результаты. В последнем примере можно было начать без определения Path:

<Label Text="{Binding Source={x:Reference page},
                      StringFormat='{0}'}" />

Этот код отображает тип источника привязки (DataBindingDemos.PathVariationsPage). Известно, что страница PathVariationsPage является производной ContentPage, поэтому она имеет свойство Content:

<Label Text="{Binding Source={x:Reference page},
                      Path=Content,
                      StringFormat='{0}'}" />

Теперь видно, что свойство Content имеет тип Xamarin.Forms.StackLayout. Добавьте свойство Children в Path, и будет ясно, что его тип — Xamarin.Forms.ElementCollection'1[Xamarin.Forms.View]. Это внутренний класс Xamarin.Forms, однако очевидно, что он представляет собой коллекцию. Добавьте индекс. Тип — Xamarin.Forms.Label. Продолжайте тем же образом.

Когда Xamarin.Forms обрабатывает путь привязки, для каждого объекта, реализующего интерфейс PropertyChanged, в пути устанавливается обработчик INotifyPropertyChanged. Например, последняя привязка реагирует на изменение в первом элементе Label, так как меняется свойство Text.

Если свойство в пути привязки не реализует интерфейс INotifyPropertyChanged, изменения этого свойства игнорируются. Некоторые изменения могут сделать путь привязки полностью недействительным, поэтому этот прием следует использовать, только если строка из свойств и вложенных свойств не может стать недействительной.