Основы привязки данных
Привязки данных пользовательского интерфейса приложений .NET (.NET MAUI) позволяют связать свойства двух объектов, чтобы изменение в одном из них приводило к изменению другого. Это очень ценное средство, и хотя привязки данных можно определить полностью в коде, XAML предоставляет сочетания клавиш и удобство.
Привязки данных
Привязки данных соединяют свойства двух объектов, называемых источником и целевым объектом. В коде необходимо выполнить два шага:
- Свойство
BindingContext
целевого объекта должно быть задано исходному объекту. - Метод
SetBinding
(часто используемый в сочетании сBinding
классом) должен вызываться в целевом объекте, чтобы привязать свойство этого объекта к свойству исходного объекта.
Целевое свойство должно быть привязываемым свойством, что означает, что целевой объект должен быть производным от BindableObject. Свойство Label, например Text
, связано с привязываемым свойством TextProperty
.
В XAML необходимо также выполнить те же два шага, которые требуются в коде, за исключением того, что Binding
расширение разметки занимает место SetBinding
вызова и Binding
класса. Однако при определении привязок данных в XAML существует несколько способов установки BindingContext
целевого объекта. Иногда он устанавливается из файла кода программной части, иногда с помощью StaticResource
x:Static
расширения разметки, а иногда и содержимого BindingContext
тегов элементов свойства.
Привязки представления
Привязки данных можно определить для связывания свойств двух представлений на одной странице. В этом случае вы устанавливаете BindingContext
целевой объект с помощью x:Reference
расширения разметки.
В следующем примере содержится Slider и два Label представления, одно из которых повернуто Slider значением, а другой — значением Slider :
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderBindingsPage"
Title="Slider Bindings Page">
<StackLayout>
<Label Text="ROTATION"
BindingContext="{x:Reference slider}"
Rotation="{Binding Path=Value}"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="Center" />
<Label BindingContext="{x:Reference slider}"
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
x:Name
Содержит Slider атрибут, на который ссылается два Label представления с помощью x:Reference
расширения разметки. Расширение x:Reference
привязки определяет свойство с именем Name
указанного элемента в данном случае slider
. Однако класс, определяющий x:Reference
расширение разметки, ReferenceExtension
также определяет ContentProperty
атрибут для Name
, что означает, что оно не является явным образом обязательным.
Расширение Binding
разметки может иметь несколько свойств, как и BindingBase
Binding
класс. Path
Binding
Значение ContentProperty
имеет значение, но часть "Path=" расширения разметки может быть опущена, если путь является первым элементом Binding
в расширении разметки.
Второе Binding
расширение разметки StringFormat
задает свойство. В .NET MAUI привязки не выполняют неявные преобразования типов, и если необходимо отобразить нестроковый объект в виде строки, необходимо предоставить преобразователь типов или использовать StringFormat
.
Внимание
Строки форматирования должны помещаться в одинарные кавычки.
Режим привязки
Одно представление может содержать привязки данных для нескольких его свойств. Однако каждое представление может иметь только одну BindingContext
, поэтому несколько привязок данных в этом представлении должны иметь все ссылочные свойства одного и того же объекта.
Решение этих и других проблем включает Mode
свойство, которое задается элементом BindingMode
перечисления:
Default
OneWay
— значения передаются из источника в целевой объектOneWayToSource
— значения передаются из целевого объекта в источникTwoWay
— значения передаются обоими способами между источником и целевым объектом.OneTime
— данные идут из источника в целевой объект, но только приBindingContext
изменении
В следующем примере демонстрируется одно распространенное использование режимов привязки и TwoWay
режимов привязкиOneWayToSource
:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderTransformsPage"
Padding="5"
Title="Slider Transforms Page">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Scaled and rotated Label -->
<Label x:Name="label"
Text="TEXT"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<!-- Slider and identifying Label for Scale -->
<Slider x:Name="scaleSlider"
BindingContext="{x:Reference label}"
Grid.Row="1" Grid.Column="0"
Maximum="10"
Value="{Binding Scale, Mode=TwoWay}" />
<Label BindingContext="{x:Reference scaleSlider}"
Text="{Binding Value, StringFormat='Scale = {0:F1}'}"
Grid.Row="1" Grid.Column="1"
VerticalTextAlignment="Center" />
<!-- Slider and identifying Label for Rotation -->
<Slider x:Name="rotationSlider"
BindingContext="{x:Reference label}"
Grid.Row="2" Grid.Column="0"
Maximum="360"
Value="{Binding Rotation, Mode=OneWayToSource}" />
<Label BindingContext="{x:Reference rotationSlider}"
Text="{Binding Value, StringFormat='Rotation = {0:F0}'}"
Grid.Row="2" Grid.Column="1"
VerticalTextAlignment="Center" />
<!-- Slider and identifying Label for RotationX -->
<Slider x:Name="rotationXSlider"
BindingContext="{x:Reference label}"
Grid.Row="3" Grid.Column="0"
Maximum="360"
Value="{Binding RotationX, Mode=OneWayToSource}" />
<Label BindingContext="{x:Reference rotationXSlider}"
Text="{Binding Value, StringFormat='RotationX = {0:F0}'}"
Grid.Row="3" Grid.Column="1"
VerticalTextAlignment="Center" />
<!-- Slider and identifying Label for RotationY -->
<Slider x:Name="rotationYSlider"
BindingContext="{x:Reference label}"
Grid.Row="4" Grid.Column="0"
Maximum="360"
Value="{Binding RotationY, Mode=OneWayToSource}" />
<Label BindingContext="{x:Reference rotationYSlider}"
Text="{Binding Value, StringFormat='RotationY = {0:F0}'}"
Grid.Row="4" Grid.Column="1"
VerticalTextAlignment="Center" />
</Grid>
</ContentPage>
В этом примере четыре Slider представления предназначены для управления свойствами Scale
RotateX
Rotate
и RotateY
свойствами объекта.Label Во-первых, кажется, что эти четыре свойства должны быть целевыми Sliderобъектами привязки Label данных, так как каждая из них задается. Тем не менее, может BindingContext
Label быть только один объект, и существует четыре разных ползунка. По этой причине BindingContext
для каждого из четырех ползунков задано Labelзначение , а привязки задаются в Value
свойствах ползунка. Используя OneWayToSource
и режимы, эти Value
свойства могут задавать исходные свойства, которые являются Scale
свойствами , Rotate
RotateX
и RotateY
свойствами объектаLabel.TwoWay
Привязки для трех Slider представлений — OneWayToSource
это означает, что Slider значение вызывает изменение свойства его BindingContext
, которое является Label именованным label
. Эти три Slider представления вызывают изменения RotateY
Rotate
RotateX
в свойствах :Label
Однако привязка Scale
свойства имеет значение TwoWay
. Это связано с тем, что Scale
свойство имеет значение по умолчанию 1, и использование TwoWay
привязки приводит Slider к тому, что начальное значение должно быть задано в 1, а не 0. Если эта привязка была OneWayToSource
, Scale
свойство изначально будет иметь значение 0 из Slider значения по умолчанию. Не Label будет видно.
Примечание.
Класс VisualElement также имеет ScaleX
и ScaleY
свойства, которые масштабируется VisualElement на оси x и y соответственно.
Привязки и коллекции
ListViewItemsSource
определяет свойство типа IEnumerable
и отображает элементы в этой коллекции. Эти элементы могут быть объектами любого типа. По умолчанию ListView использует ToString
метод каждого элемента для отображения этого элемента. Иногда это просто то, что требуется, ToString
но во многих случаях возвращает только полное имя класса объекта.
Однако элементы в ListView коллекции можно отображать любым способом, который требуется использовать с помощью шаблона, который включает класс, производный от Cell. Шаблон клонируется для каждого элемента в ListViewпривязках данных, установленных на шаблоне, передается отдельным клонам. Пользовательские ячейки можно создать для элементов с помощью ViewCell класса.
ListView Может отобразить список всех именованных цветов, доступных в .NET MAUI, с помощью NamedColor
класса:
using System.Reflection;
using System.Text;
namespace XamlSamples
{
public class NamedColor
{
public string Name { get; private set; }
public string FriendlyName { get; private set; }
public Color Color { get; private set; }
// Expose the Color fields as properties
public float Red => Color.Red;
public float Green => Color.Green;
public float Blue => Color.Blue;
public static IEnumerable<NamedColor> All { get; private set; }
static NamedColor()
{
List<NamedColor> all = new List<NamedColor>();
StringBuilder stringBuilder = new StringBuilder();
// Loop through the public static fields of the Color structure.
foreach (FieldInfo fieldInfo in typeof(Colors).GetRuntimeFields())
{
if (fieldInfo.IsPublic &&
fieldInfo.IsStatic &&
fieldInfo.FieldType == typeof(Color))
{
// Convert the name to a friendly name.
string name = fieldInfo.Name;
stringBuilder.Clear();
int index = 0;
foreach (char ch in name)
{
if (index != 0 && Char.IsUpper(ch))
{
stringBuilder.Append(' ');
}
stringBuilder.Append(ch);
index++;
}
// Instantiate a NamedColor object.
NamedColor namedColor = new NamedColor
{
Name = name,
FriendlyName = stringBuilder.ToString(),
Color = (Color)fieldInfo.GetValue(null)
};
// Add it to the collection.
all.Add(namedColor);
}
}
all.TrimExcess();
All = all;
}
}
}
Каждый NamedColor
объект имеет Name
и FriendlyName
свойства типа, Color
свойство типаstring
Color, а также Red
Green
свойства и Blue
свойства. Кроме того, NamedColor
статический конструктор создает IEnumerable<NamedColor>
коллекцию, содержащую NamedColor
объекты, соответствующие полям типа Color класса Colors , и назначает его общедоступному статическому All
свойству.
Настройка статического NamedColor.All
свойства для ItemsSource
объекта ListView может быть достигнута с помощью x:Static
расширения разметки:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
Title="ListView Demo Page">
<ListView ItemsSource="{x:Static local:NamedColor.All}" />
</ContentPage>
Результат устанавливает, что элементы имеют тип XamlSamples.NamedColor
:
Чтобы определить шаблон для элементов, ItemTemplate
необходимо задать значение, которое ссылается DataTemplate на ViewCellобъект. Должен ViewCell определить макет одного или нескольких представлений для отображения каждого элемента:
<ListView ItemsSource="{x:Static local:NamedColor.All}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding FriendlyName}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Примечание.
Источником привязки для ячеек и дочерних ячеек является ListView.ItemsSource
коллекция.
В этом примере Label элемент задается свойством View объекта ViewCell. ViewCell.View
Теги не нужны, так как View свойство является свойством содержимогоViewCell. В этом XAML отображается FriendlyName
свойство каждого NamedColor
объекта:
Шаблон элемента можно развернуть для отображения дополнительных сведений и фактического цвета:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamlSamples"
x:Class="XamlSamples.ListViewDemoPage"
Title="ListView Demo Page">
<ContentPage.Resources>
<x:Double x:Key="boxSize">50</x:Double>
<x:Int32 x:Key="rowHeight">60</x:Int32>
<local:FloatToIntConverter x:Key="intConverter" />
</ContentPage.Resources>
<ListView ItemsSource="{x:Static local:NamedColor.All}"
RowHeight="{StaticResource rowHeight}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="5, 5, 0, 5"
Orientation="Horizontal"
Spacing="15">
<BoxView WidthRequest="{StaticResource boxSize}"
HeightRequest="{StaticResource boxSize}"
Color="{Binding Color}" />
<StackLayout Padding="5, 0, 0, 0"
VerticalOptions="Center">
<Label Text="{Binding FriendlyName}"
FontAttributes="Bold"
FontSize="14" />
<StackLayout Orientation="Horizontal"
Spacing="0">
<Label Text="{Binding Red,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
<Label Text="{Binding Green,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat=', G={0:X2}'}" />
<Label Text="{Binding Blue,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat=', B={0:X2}'}" />
</StackLayout>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
Преобразователи значений привязки
В предыдущем примере XAML отображаются отдельные Red
свойства Green
и Blue
свойства каждого NamedColor
. Эти свойства имеют тип float
и диапазон от 0 до 1. Если вы хотите отобразить шестнадцатеричные значения, нельзя просто использовать StringFormat
с спецификацией форматирования X2. Это работает только для целых чисел и, кроме того, float
значения должны быть умножены на 255.
Эту проблему можно решить с помощью преобразователя значений, который также называется преобразователем привязки. Это класс, реализующий IValueConverter интерфейс, который означает, что он имеет два метода с именем Convert
и ConvertBack
. Метод Convert
вызывается при передаче значения из источника в целевой объект. Метод ConvertBack
вызывается для передачи из целевого объекта в OneWayToSource
источник или TwoWay
привязки:
using System.Globalization;
namespace XamlSamples
{
public class FloatToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
float multiplier;
if (!float.TryParse(parameter as string, out multiplier))
multiplier = 1;
return (int)Math.Round(multiplier * (float)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
float divider;
if (!float.TryParse(parameter as string, out divider))
divider = 1;
return ((float)(int)value) / divider;
}
}
}
Примечание.
Метод ConvertBack
не играет роль в этом примере, так как привязки являются единственным способом от источника к целевому объекту.
Привязка ссылается на преобразователь привязки со свойством Converter
. Преобразователь привязки также может принимать параметр, указанный свойством ConverterParameter
. Для некоторой универсальности это способ указания умножения. Преобразователь привязки проверяет параметр преобразователя для допустимого float
значения.
Преобразователь создается в словаре ресурсов страницы, чтобы его можно было совместно использовать между несколькими привязками:
<local:FloatToIntConverter x:Key="intConverter" />
Три привязки данных ссылались на один экземпляр:
<Label Text="{Binding Red,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
Шаблон элемента воспроизводит цвет, понятное имя и значения RGB:
Может ListView обрабатывать изменения, которые динамически происходят в базовых данных, но только при определенных действиях. Если коллекция элементов, назначенных ItemsSource
свойству ListView изменений во время выполнения, используйте ObservableCollection<T> класс для этих элементов. ObservableCollection<T>реализует интерфейс и установит INotifyCollectionChanged
обработчик событияCollectionChanged
.ListView
Если свойства элементов сами изменяются во время выполнения, элементы в коллекции должны реализовывать INotifyPropertyChanged
интерфейс и сигнализировать об изменениях значений свойств с помощью PropertyChanged
события.
Следующие шаги
Привязки данных обеспечивают мощный механизм связывания свойств между двумя объектами на странице или между визуальными объектами и базовыми данными. Но когда приложение начинает работать с источниками данных, популярный шаблон архитектуры приложений начинает появляться в качестве полезной парадигмы.