Основные привязки Xamarin.Forms

Download Sample Скачайте пример

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

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

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

Привязки с контекстом привязки

Хотя привязки данных обычно указываются полностью в XAML, полезно посмотреть привязки данных в коде. Страница Базовая привязка кода содержит файл 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 объекта Label. Привязка данных автоматизирует эту задачу — обработчик событий и код в ней больше не нужны.

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

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

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

Метод SetBinding, определенный объектом BindableObject, имеет аргумент типа BindingBase, из которого производится класс Binding, но есть другие методы SetBinding, определенные классом BindableObjectExtensions. Файл с выделенным кодом в примере базовой привязки кода использует более простой метод расширения SetBinding из этого класса.

public partial class BasicCodeBindingPage : ContentPage
{
    public BasicCodeBindingPage()
    {
        InitializeComponent();

        label.BindingContext = slider;
        label.SetBinding(Label.RotationProperty, "Value");
    }
}

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

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

Метод SetBinding раскрывает одно из самых важных правил привязки данных:

целевое свойство должно поддерживаться свойством, подходящим для привязки.

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

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

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

label.SetBinding(RotationProperty, "Value");

Но лучше включить имя класса, чтобы помнить о целевом объекте.

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

Basic Code Binding

Страница Базовая привязка Xaml идентична странице Базовая привязка кода, но она определяет всю привязку данных в 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 с именем slider.
  • Расширения разметки Binding связывает свойство Rotation объекта Label со свойством Value объекта Slider.

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

Легко забыть расширения разметки x:Reference при задании BindingContext. Нередко разработчики ошибочно задают свойство непосредственно для имени источника привязки следующим образом.

BindingContext="slider"

Это неправильно. Эта разметка задает свойство BindingContext для объекта string, в котором символы складываются в слово slider.

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

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

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

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

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

Это показано в примере Альтернативной привязки кода. Файл XAML аналогичен примеру Базовая привязка кода, только объект Slider определен для управления свойством Scale объекта Label. По этой причине Slider устанавливается диапазон от –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 параметров, поэтому параметр source указан с именованным аргументом. Аргумент является объектом slider.

Выполнение программы может быть немного неожиданным:

Alternative Code Binding

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

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

Из следующей статьи вы узнаете, как избежать этой проблемы с помощью инициализации объекта Slider из значения по умолчанию свойства Scale.

Примечание.

Класс 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 для расширения разметки x:Static, чтобы ссылаться на значение статического свойства или поля, или расширения разметки StaticResource, чтобы ссылаться на объект, хранящийся в словаре ресурсов, или непосредственно для объекта, который обычно (но не всегда) является экземпляром модели представления.

Свойство BindingContext также может быть задано для объекта Binding, чтобы свойства Source и Path объекта Binding определяли контекст привязки.

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

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

Свойство BindingContext имеет очень важную характеристику:

настройка свойства BindingContext наследуется через визуальное дерево.

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

Пример Наследование контекста привязки — это простая демонстрация наследования контекста привязки:

<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 устанавливается для объекта slider. Этот контекст привязки наследуется объектами Label и BoxView, оба из которых имеют свойства Rotation, зависящие от свойства Value объекта Slider:

Binding Context Inheritance

В следующей статье вы увидите, как режим привязки может изменить поток данных между целевым и исходным объектами.

Другие видео о Xamarin см. на Channel 9 и YouTube.