Основные привязки Xamarin.Forms
Привязка данных 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
поворачивается соответствующим образом:
Страница Базовая привязка 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
.
Выполнение программы может быть немного неожиданным:
На экране 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
:
В следующей статье вы увидите, как режим привязки может изменить поток данных между целевым и исходным объектами.