Поделиться через


Основы привязки данных

Просмотрите пример. Обзор примера

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

Привязки данных

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

  1. Свойство BindingContext целевого объекта должно быть задано исходному объекту.
  2. Метод 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 класс. PathBinding Значение 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 представления предназначены для управления свойствами ScaleRotateXRotateи RotateY свойствами объекта.Label Во-первых, кажется, что эти четыре свойства должны быть целевыми Sliderобъектами привязки Label данных, так как каждая из них задается. Тем не менее, может BindingContext Label быть только один объект, и существует четыре разных ползунка. По этой причине BindingContext для каждого из четырех ползунков задано Labelзначение , а привязки задаются в Value свойствах ползунка. Используя OneWayToSource и режимы, эти Value свойства могут задавать исходные свойства, которые являются Scaleсвойствами , RotateRotateXи RotateY свойствами объектаLabel.TwoWay

Привязки для трех Slider представлений — OneWayToSourceэто означает, что Slider значение вызывает изменение свойства его BindingContext, которое является Label именованным label. Эти три Slider представления вызывают изменения RotateY RotateRotateXв свойствах :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 свойство типаstringColor, а также RedGreenсвойства и 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 объекта:

Привязка к коллекции с помощью DataTemplate.

Шаблон элемента можно развернуть для отображения дополнительных сведений и фактического цвета:

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

Привязка к коллекции с помощью DataTemplate и преобразователя.

Может ListView обрабатывать изменения, которые динамически происходят в базовых данных, но только при определенных действиях. Если коллекция элементов, назначенных ItemsSource свойству ListView изменений во время выполнения, используйте ObservableCollection<T> класс для этих элементов. ObservableCollection<T>реализует интерфейс и установит INotifyCollectionChanged обработчик событияCollectionChanged.ListView

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

Следующие шаги

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