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


Часть 4. Основы привязки данных

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

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

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

Целевое свойство должно быть привязываемым свойством, что означает, что целевой объект должен быть производным от BindableObject. В интерактивной Xamarin.Forms документации указывается, какие свойства являются привязываемыми свойствами. Свойство Label , связанное Text с привязываемым свойством TextProperty.

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

Однако при определении привязок данных в XAML существует несколько способов установки BindingContext целевого объекта. Иногда он устанавливается из файла кода программной части, иногда с помощью StaticResource x:Static расширения разметки, а иногда и содержимого BindingContext тегов элементов свойства.

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

Привязки представления к представлению

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

Вот XAML-файл, содержащий Slider и два Label представления, одно из которых повернуто Slider значением, а другой — значением Slider :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SliderBindingsPage"
             Title="Slider Bindings Page">

    <StackLayout>
        <Label Text="ROTATION"
               BindingContext="{x:Reference Name=slider}"
               Rotation="{Binding Path=Value}"
               FontAttributes="Bold"
               FontSize="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Slider x:Name="slider"
                Maximum="360"
                VerticalOptions="CenterAndExpand" />

        <Label BindingContext="{x:Reference slider}"
               Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
               FontAttributes="Bold"
               FontSize="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

x:Name Содержит Slider атрибут, на который ссылается два Label представления с помощью x:Reference расширения разметки.

Расширение x:Reference привязки определяет свойство с именем Name указанного элемента в данном случае slider. Однако класс, определяющий x:Reference расширение разметки, ReferenceExtension также определяет ContentProperty атрибут для Name, что означает, что оно не является явным образом обязательным. Только для разнообразия, первый x:Reference включает "Name=", но второй не:

BindingContext="{x:Reference Name=slider}"
…
BindingContext="{x:Reference slider}"

Расширение Binding разметки может иметь несколько свойств, как и BindingBase Binding класс. PathBinding Значение ContentProperty имеет значение, но часть "Path=" расширения разметки может быть опущена, если путь является первым элементом Binding в расширении разметки. Первый пример имеет "Path=", но второй пример не указывает на это:

Rotation="{Binding Path=Value}"
…
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"

Все свойства могут находиться в одной строке или разделены на несколько строк:

Text="{Binding Value,
               StringFormat='The angle is {0:F0} degrees'}"

Сделайте все, что удобно.

Обратите внимание на StringFormat свойство во втором Binding расширении разметки. В Xamarin.Formsпривязках не выполняются неявные преобразования типов, и если необходимо отобразить нестроковый объект в виде строки, необходимо предоставить преобразователь типов или использовать StringFormat. Вне сцены статический String.Format метод используется для реализации StringFormat. Это потенциально проблема, так как спецификации форматирования .NET включают фигурные скобки, которые также используются для разделителя расширений разметки. Это создает риск запутать средство синтаксического анализа XAML. Чтобы избежать этого, поместите всю строку форматирования в одинарные кавычки:

Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"

Вот запущенная программа:

Привязки представления к представлению

Режим привязки

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

Решение этих и других проблем включает Mode свойство, которое задается элементом BindingMode перечисления:

  • Default
  • OneWay — значения передаются из источника в целевой объект
  • OneWayToSource — значения передаются из целевого объекта в источник
  • TwoWay — значения передаются обоими способами между источником и целевым объектом.
  • OneTime — данные идут из источника в целевой объект, но только при BindingContext изменении

В следующей программе демонстрируется одно общее использование режимов привязки OneWayToSource и TwoWay режимов привязки. Четыре Slider представления предназначены для управления Scaleсвойствами RotateRotateXи RotateY свойствами объекта Label. Во-первых, кажется, что эти четыре свойства должны быть целевыми Sliderобъектами привязки Label данных, так как каждая из них задается. Тем не менее, может BindingContext Label быть только один объект, и существует четыре разных ползунка.

По этой причине все привязки задаются, казалось бы, обратно: BindingContext для каждого из четырех ползунков задано значение , и привязки задаются Labelв Value свойствах ползунка. С помощью OneWayToSource и режимов эти Value свойства могут задавать исходные свойства, которые являются свойствами Scale, RotateRotateXи RotateY свойствами:LabelTwoWay

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             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 представлений — OneWayToSourceэто означает, что Slider значение вызывает изменение свойства его BindingContext, которое является Label именованным label. Эти три Slider представления вызывают изменения Rotateв свойствах RotateXи RotateY свойствах Labelобъекта .

Однако привязка Scale свойства имеет значение TwoWay. Это связано с тем, что Scale свойство имеет значение по умолчанию 1, и использование TwoWay привязки приводит Slider к тому, что начальное значение должно быть задано в 1, а не 0. Если эта привязка была OneWayToSource, Scale свойство изначально будет иметь значение 0 из Slider значения по умолчанию. Не Label будет видимым, и это может вызвать некоторую путаницу для пользователя.

Обратная привязка

Примечание.

Класс VisualElement также имеет ScaleX и ScaleY свойства, которые масштабируется VisualElement на оси x и y соответственно.

Привязки и коллекции

Ничего не иллюстрирует силу привязок XAML и данных лучше, чем шаблон.ListView

ListViewItemsSource определяет свойство типа IEnumerableи отображает элементы в этой коллекции. Эти элементы могут быть объектами любого типа. По умолчанию ListView использует ToString метод каждого элемента для отображения этого элемента. Иногда это просто то, что требуется, ToString но во многих случаях возвращает только полное имя класса объекта.

Однако элементы в ListView коллекции можно отображать любым способом, который требуется использовать с помощью шаблона, который включает класс, производный от Cell. Шаблон клонируется для каждого элемента в ListViewпривязках данных, установленных на шаблоне, передается отдельным клонам.

Очень часто необходимо создать пользовательскую ячейку для этих элементов с помощью ViewCell класса. Этот процесс несколько беспорядок в коде, но в XAML он становится очень простым.

В проект XamlSamples входит класс NamedColor. Каждый NamedColor объект имеет Name и FriendlyName свойства типа string, а Color также свойство типа Color. Кроме того, NamedColor имеет 141 статических полей Color типа, соответствующих цветам, определенным в Xamarin.FormsColor классе. Статический IEnumerable<NamedColor> конструктор создает коллекцию, содержащую NamedColor объекты, соответствующие этим статическим полям, и назначает его общедоступному статическому All свойству.

Настройка статического NamedColor.All свойства ItemsSource для объекта ListView просто с помощью x:Static расширения разметки:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             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:

Привязка к коллекции

Это не так много информации, но ListView прокручиваемая и выборная.

Чтобы определить шаблон для элементов, необходимо разбить ItemTemplate свойство как элемент свойства и задать для него значение DataTemplate, которое затем ссылается на ViewCellобъект. View Для свойства ViewCell можно определить макет одного или нескольких представлений для отображения каждого элемента. Ниже приведен простой пример:

<ListView ItemsSource="{x:Static local:NamedColor.All}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <ViewCell.View>
                    <Label Text="{Binding FriendlyName}" />
                </ViewCell.View>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Примечание.

Источником привязки для ячеек и дочерних ячеек является ListView.ItemsSource коллекция.

Элемент Label присваивается View свойству объекта ViewCell. ViewCell.View(Теги не нужны, так как View свойство является свойством содержимого ViewCell.) Эта разметка отображает FriendlyName свойство каждого NamedColor объекта:

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

Гораздо лучше. Теперь все, что необходимо, заключается в том, чтобы спрутить шаблон элемента с дополнительными сведениями и фактическим цветом. Для поддержки этого шаблона в словаре ресурсов страницы определены некоторые значения и объекты:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples"
             x:Class="XamlSamples.ListViewDemoPage"
             Title="ListView Demo Page">

    <ContentPage.Resources>
        <ResourceDictionary>
            <OnPlatform x:Key="boxSize"
                        x:TypeArguments="x:Double">
                <On Platform="iOS, Android, UWP" Value="50" />
            </OnPlatform>

            <OnPlatform x:Key="rowHeight"
                        x:TypeArguments="x:Int32">
                <On Platform="iOS, Android, UWP" Value="60" />
            </OnPlatform>

            <local:DoubleToIntConverter x:Key="intConverter" />

        </ResourceDictionary>
    </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="Medium" />

                            <StackLayout Orientation="Horizontal"
                                         Spacing="0">
                                <Label Text="{Binding Color.R,
                                       Converter={StaticResource intConverter},
                                       ConverterParameter=255,
                                       StringFormat='R={0:X2}'}" />

                                <Label Text="{Binding Color.G,
                                       Converter={StaticResource intConverter},
                                       ConverterParameter=255,
                                       StringFormat=', G={0:X2}'}" />

                                <Label Text="{Binding Color.B,
                                       Converter={StaticResource intConverter},
                                       ConverterParameter=255,
                                       StringFormat=', B={0:X2}'}" />
                            </StackLayout>
                        </StackLayout>
                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

Обратите внимание, что используется для OnPlatform определения размера BoxView и высоты ListView строк. Хотя значения для всех платформ одинаковы, разметка может быть легко адаптирована для других значений для точной настройки дисплея.

Преобразователи значений привязки

В предыдущем файле ListView Demo XAML отображаются отдельные RGи B свойства Xamarin.FormsColor структуры. Эти свойства имеют тип double и диапазон от 0 до 1. Если вы хотите отобразить шестнадцатеричные значения, нельзя просто использовать StringFormat с спецификацией форматирования X2. Это работает только для целых чисел и, кроме того, double значения должны быть умножены на 255.

Эта небольшая проблема была решена с помощью преобразователя значений, также называемого преобразователем привязки. Это класс, реализующий IValueConverter интерфейс, который означает, что он имеет два метода с именем Convert и ConvertBack. Метод Convert вызывается при передаче значения из источника в целевой объект. ConvertBack Метод вызывается для передачи из целевого объекта в OneWayToSource источник или TwoWay привязки:

using System;
using System.Globalization;
using Xamarin.Forms;

namespace XamlSamples
{
    class DoubleToIntConverter : IValueConverter
    {
        public object Convert(object value, Type targetType,
                              object parameter, CultureInfo culture)
        {
            double multiplier;

            if (!Double.TryParse(parameter as string, out multiplier))
                multiplier = 1;

            return (int)Math.Round(multiplier * (double)value);
        }

        public object ConvertBack(object value, Type targetType,
                                  object parameter, CultureInfo culture)
        {
            double divider;

            if (!Double.TryParse(parameter as string, out divider))
                divider = 1;

            return ((double)(int)value) / divider;
        }
    }
}

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

Привязка ссылается на преобразователь привязки со свойством Converter . Преобразователь привязки также может принимать параметр, указанный свойством ConverterParameter . Для некоторой универсальности это способ указания умножения. Преобразователь привязки проверяет параметр преобразователя для допустимого double значения.

Преобразователь создается в словаре ресурсов, чтобы его можно было совместно использовать для нескольких привязок:

<local:DoubleToIntConverter x:Key="intConverter" />

Три привязки данных ссылались на один экземпляр. Обратите внимание, что Binding расширение разметки содержит внедренное StaticResource расширение разметки:

<Label Text="{Binding Color.R,
                      Converter={StaticResource intConverter},
                      ConverterParameter=255,
                      StringFormat='R={0:X2}'}" />

Вот результат:

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

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

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

Итоги

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