Xamarin Binding

Автор:

learn.microsoft.com

Привязка данных (binding) в Xamarin.Forms связывает пару свойств между двумя объектами, по крайней мере один из которых обычно является объектом пользовательского интерфейса. Эти два объекта называются целевым и исходным:

  • Целевой объект – это объект (и свойство), на который устанавливается привязка данных.
  • Источник – это объект (и свойство), на который ссылается привязка данных.

Это различие иногда может быть несколько запутанным: в простейшем случае данные перетекают от источника к цели, что означает, что значение целевого свойства устанавливается на основе значения свойства источника. Однако в некоторых случаях данные могут идти от цели к источнику или в обоих направлениях. Чтобы избежать путаницы, следует помнить, что целевым всегда является объект, на который устанавливается привязка данных, даже если он не получает, а предоставляет данные.

Связывание с контекстом привязки

Хотя привязки данных обычно задаются исключительно в XAML, полезно посмотреть на привязки данных в коде. Страница Basic Code Binding содержит XAML-файл с Label и Slider:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            x:Class="DataBindingDemos.BasicCodeBindingPage"
            Title="Basic Code Binding">
   <StackLayout Padding="10, 0">
       <Label x:Name="label"
              Text="TEXT"
              FontSize="48"
              HorizontalOptions="Center"
              VerticalOptions="CenterAndExpand" />
 
       <Slider x:Name="slider"
               Maximum="360"
               VerticalOptions="CenterAndExpand" />
   </StackLayout>
</ContentPage>

Ползунок (Slider) установлен в диапазоне от 0 до 360. Цель этой программы – вращать Label, используя Slider.

Без привязки данных можно было бы установить событие ValueChanged для Slider в обработчик события, который обращается к свойству Value объекта Slider и устанавливает это значение в свойство Rotation метки. Привязка данных автоматизирует эту работу, и необходимость в обработчике события и коде внутри него отпадает.

Привязку можно установить для экземпляра любого класса, производного от BindableObject, к которым относятся производные Element, VisualElement, View и View. Привязка всегда устанавливается на целевом объекте. Привязка ссылается на исходный объект. Для установки привязки данных используются следующие два члена целевого класса:

  • Свойство BindingContext задает объект-источник.
  • Метод SetBinding задает целевое свойство и свойство источника.

В данном примере Label является объектом привязки, а Slider – источником привязки. Изменения в источнике Slider влияют на вращение целевого объекта Label. Данные перетекают от источника к цели.

Метод SetBinding, определенный BindableObject, имеет аргумент типа BindingBase, от которого происходит класс Binding, но существуют и другие методы SetBinding, определенные классом BindableObjectExtensions. В файле с выделенным кодом примера Basic Code Binding используется более простой метод расширения SetBinding из этого класса.

public partial class BasicCodeBindingPage : ContentPage
{
   public BasicCodeBindingPage()
   {
       InitializeComponent();
 
       label.BindingContext = slider;
       label.SetBinding(Label.RotationProperty, "Value");
   }
}

Объект Label является объектом привязки, поэтому именно на нем устанавливается это свойство и вызывается метод. Свойство BindingContext указывает на источник привязки, которым является слайдер.

Метод SetBinding вызывается на объекте привязки, но при этом указывается как целевое свойство, так и свойство источника. Целевое свойство задается в виде объекта BindableProperty: Label.RotationProperty. Свойство-источник задается в виде строки и указывает на свойство Value объекта Slider.

Метод SetBinding раскрывает одно из важнейших правил привязки данных: целевое свойство должно быть подкреплено связываемым свойством.

Это правило подразумевает, что целевой объект должен быть экземпляром класса, производного от BindableObject.

Для свойства source, которое задается в виде строки, такого правила нет. Для доступа к реальному свойству используется рефлексия. Однако в данном конкретном случае свойство Value также поддерживается связываемым свойством.

Код можно несколько упростить: связываемое свойство RotationProperty определяется VisualElement и наследуется Label и ContentPage, поэтому имя класса в вызове SetBinding не требуется:

label.SetBinding(RotationProperty, "Value");

Однако включение имени класса является хорошим напоминанием о целевом объекте.

При манипулировании Label метка поворачивается соответствующим образом:

поворот метки

Страница Basic Xaml Binding идентична странице Basic Code Binding, за исключением того, что вся привязка данных задается в XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            x:Class="DataBindingDemos.BasicXamlBindingPage"
            Title="Basic XAML Binding">
   <StackLayout Padding="10, 0">
       <Label Text="TEXT"
              FontSize="80"
              HorizontalOptions="Center"
              VerticalOptions="CenterAndExpand"
              BindingContext="{x:Reference Name=slider}"
              Rotation="{Binding Path=Value}" />
 
       <Slider x:Name="slider"
               Maximum="360"
               VerticalOptions="CenterAndExpand" />
   </StackLayout>
</ContentPage>

Как и в коде, привязка данных устанавливается на целевом объекте, которым является Label. При этом задействованы два расширения разметки XAML. Их можно сразу узнать по фигурным скобкам-разделителям:

  • Расширение разметки x:Reference необходимо для ссылки на объект-источник, которым является слайдер с именем slider.
  • Расширение разметки Binding связывает свойство Rotation ярлыка со свойством Value слайдера.

Расширение разметки x:Reference поддерживается классом ReferenceExtension, а Binding – классом BindingExtension. Как следует из префиксов пространства имен XML, x:Reference является частью спецификации XAML 2009, а Binding – частью Xamarin.Forms. Обратите внимание, что внутри фигурных скобок нет кавычек.

При задании BindingContext легко забыть о расширении разметки x:Reference. Часто можно ошибочно установить свойство непосредственно на имя источника привязки, например, так:

BindingContext="slider"

Но это неправильно. Эта разметка устанавливает свойство BindingContext для объекта string, символы которого пишутся как "slider"!

Обратите внимание, что свойство source задается свойством Path расширения BindingExtension, которое соответствует свойству Path класса Binding.

Разметку, показанную на странице Basic XAML Binding, можно упростить. Для таких расширений XAML-разметки, как x:Reference и Binding, могут быть определены атрибуты свойства content, что означает отсутствие необходимости указывать имя свойства. Свойство Name является контентным свойством x:Reference, а свойство Path – контентным свойством Binding, что означает, что их можно исключить из выражений:

<Label Text="TEXT"
      FontSize="80"
      HorizontalOptions="Center"
      VerticalOptions="CenterAndExpand"
      BindingContext="{x:Reference slider}"
      Rotation="{Binding Value}" />

Связывание без контекста привязки

Свойство BindingContext является важным компонентом привязки данных, однако оно не всегда необходимо. Вместо этого исходный объект может быть указан в вызове SetBinding или в расширении разметки Binding.

Это демонстрируется в примере Alternative Code Binding. XAML-файл аналогичен примеру Basic Code Binding, за исключением того, что Label определен для управления свойством Scale метки Label. Поэтому здесь задается диапазон от -2 до 2:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            x:Class="DataBindingDemos.AlternativeCodeBindingPage"
            Title="Alternative Code Binding">
   <StackLayout Padding="10, 0">
       <Label x:Name="label"
              Text="TEXT"
              FontSize="40"
              HorizontalOptions="Center"
              VerticalOptions="CenterAndExpand" />
 
       <Slider x:Name="slider"
               Minimum="-2"
               Maximum="2"
               VerticalOptions="CenterAndExpand" />
   </StackLayout>
</ContentPage>

Файл с выделенным кодом устанавливает привязку с помощью метода SetBinding, определенного BindableObject. Аргументом является конструктор класса Binding:

public partial class AlternativeCodeBindingPage : ContentPage
{
   public AlternativeCodeBindingPage()
   {
       InitializeComponent();
 
       label.SetBinding(Label.ScaleProperty, new Binding("Value", source: slider));
   }
}

Конструктор Binding имеет 6 параметров, поэтому исходный параметр задается именованным аргументом. Аргументом является объект slider.

Запуск этой программы может вызвать некоторое удивление:

перемещение метки на экране

На экране iOS слева показано, как выглядит экран при первом появлении страницы. Где находится ярлык?

Проблема заключается в том, что Slider имеет начальное значение 0. Это приводит к тому, что свойство Scale ярлыка также устанавливается в 0, а не в 1, как по умолчанию. В результате ярлык становится изначально невидимым. Как показано на снимке экрана Android, можно манипулировать слайдером, чтобы ярлык снова появился, но его первоначальное исчезновение вызывает дискомфорт.

Примечание. Класс VisualElement также определяет свойства ScaleX и ScaleY, которые могут по-разному масштабировать VisualElement в горизонтальном и вертикальном направлениях.

На странице Альтернативная привязка XAML показана эквивалентная привязка полностью в XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            x:Class="DataBindingDemos.AlternativeXamlBindingPage"
            Title="Alternative XAML Binding">
   <StackLayout Padding="10, 0">
       <Label Text="TEXT"
              FontSize="40"
              HorizontalOptions="Center"
              VerticalOptions="CenterAndExpand"
              Scale="{Binding Source={x:Reference slider},
                              Path=Value}" />
 
       <Slider x:Name="slider"
               Minimum="-2"
               Maximum="2"
               VerticalOptions="CenterAndExpand" />
   </StackLayout>
</ContentPage>

Теперь в расширении разметки Binding заданы два свойства, Source и Path, разделенные запятой. При желании они могут располагаться в одной строке:

Scale="{Binding Source={x:Reference slider}, Path=Value}" />

Свойство Source устанавливается во встроенное расширение разметки x:Reference, которое в остальном имеет тот же синтаксис, что и свойство BindingContext. Обратите внимание, что внутри фигурных скобок нет кавычек, а оба свойства должны быть разделены запятой.

Свойство содержимого расширения разметки Binding является Path, но часть Path= расширения разметки может быть исключена только в том случае, если она является первым свойством в выражении. Чтобы исключить часть Path=, необходимо поменять местами эти два свойства:

Scale="{Binding Value, Source={x:Reference slider}}" />

Хотя расширения разметки XAML обычно разграничиваются фигурными скобками, они также могут быть выражены в виде элементов объекта:

<Label Text="TEXT"
      FontSize="40"
      HorizontalOptions="Center"
      VerticalOptions="CenterAndExpand">
   <Label.Scale>
       <Binding Source="{x:Reference slider}"
                Path="Value" />
   </Label.Scale>
</Label>

Теперь свойства Source и Path представляют собой обычные атрибуты XAML: Значения заключаются в кавычки, а сами атрибуты не разделяются запятой. Расширение разметки x:Reference также может стать элементом объекта:

<Label Text="TEXT"
      FontSize="40"
      HorizontalOptions="Center"
      VerticalOptions="CenterAndExpand">
   <Label.Scale>
       <Binding Path="Value">
           <Binding.Source>
               <x:Reference Name="slider" />
           </Binding.Source>
       </Binding>
   </Label.Scale>
</Label>

Такой синтаксис встречается нечасто, но иногда он необходим, когда речь идет о сложных объектах.

В приведенных примерах свойство BindingContext и свойство Source свойства Binding устанавливаются в расширение разметки x:Reference для ссылки на другое представление на странице. Эти два свойства имеют тип Object, и их можно установить на любой объект, который содержит свойства, подходящие для источников привязки.

Наследование контекста привязки

В этой статье вы увидели, что указать объект-источник можно с помощью свойства BindingContext или свойства Source объекта Binding. Если оба свойства заданы, то свойство Source объекта Binding имеет приоритет над свойством BindingContext.

Свойство BindingContext обладает чрезвычайно важной характеристикой: установка свойства BindingContext наследуется через визуальное дерево.

Как вы увидите, это может быть очень удобно для упрощения выражений связывания, а в некоторых случаях – особенно в сценариях Model-View-ViewModel (MVVM) – необходимо.

Пример Binding Context Inheritance представляет собой простую демонстрацию наследования контекста привязки:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            x:Class="DataBindingDemos.BindingContextInheritancePage"
            Title="BindingContext Inheritance">
   <StackLayout Padding="10">
 
       <StackLayout VerticalOptions="FillAndExpand"
                    BindingContext="{x:Reference slider}">
 
           <Label Text="TEXT"
                  FontSize="80"
                  HorizontalOptions="Center"
                  VerticalOptions="EndAndExpand"
                  Rotation="{Binding Value}" />
 
           <BoxView Color="#800000FF"
                    WidthRequest="180"
                    HeightRequest="40"
                    HorizontalOptions="Center"
                    VerticalOptions="StartAndExpand"
                    Rotation="{Binding Value}" />
       </StackLayout>
 
       <Slider x:Name="slider"
               Maximum="360" />
 
   </StackLayout>
</ContentPage>

Свойство BindingContext в StackLayout установлено на объект слайдера. Этот контекст привязки наследуется Label и BoxView, у которых свойства Rotation установлены на свойство Value для объекта Slider:

ротация метки

Материалы по теме