Множественные привязки Xamarin.Forms

Множественные привязки позволяют присоединять коллекцию объектов Binding к одному свойству целевого объекта привязки. Они создаются с помощью класса MultiBinding, который вычисляет все объекты Binding и возвращает одно значение через экземпляр IMultiValueConverter, предоставляемый приложением. Кроме того, MultiBinding повторно вычисляет все объекты Binding при изменении каких-либо привязанных данных.

Класс MultiBinding определяет следующие свойства:

  • Bindings типа IList<BindingBase> — представление коллекции объектов Binding в экземпляре MultiBinding.
  • Converter типа IMultiValueConverter — представление преобразователя исходных значений в целевое значение или из целевого значения.
  • ConverterParameter типа object — представление необязательного параметра для передачи в Converter.

Bindings — это свойство содержимого класса MultiBinding. Поэтому его не нужно явно задавать из XAML.

Кроме того, класс MultiBinding наследует следующие свойства от класса BindingBase:

  • FallbackValue типа object — представление значения, используемого, когда множественная привязка не может вернуть значение.
  • Mode типа BindingMode — указатель направления потока данных множественной привязки.
  • StringFormat типа string — определение способа форматирования результата множественной привязки, если он отображается в виде строки.
  • TargetNullValue типа object — представление значения, используемого в целевом объекте, когда значение источника равно null.

Класс MultiBinding должен использовать IMultiValueConverter, чтобы создать значение для целевого объекта привязки на основе значений привязок в коллекции Bindings. Например, Color можно вычислить на основе значений красного, синего и зеленого цветов (все они могут быть значениями одних и тех же или разных объектов источника привязки). Когда значение перемещается из целевого объекта в источники, значение целевого свойства преобразуется в набор значений, которые передаются обратно в привязки.

Внимание

Отдельные привязки в коллекции Bindings могут иметь собственные преобразователи величин.

Значение свойства Mode определяет возможности MultiBinding и используется в качестве режима привязки для всех привязок в коллекции, если только отдельная привязка не переопределяет свойство. Например, если для свойства Mode объекта MultiBinding задано значение TwoWay, все привязки в коллекции считаются TwoWay, если только для одной из привязок явно не задано другое значение Mode.

Определение IMultiValueConverter

Интерфейс IMultiValueConverter позволяет применять пользовательскую логику к MultiBinding. Чтобы связать преобразователь с MultiBinding, создайте класс, реализующий интерфейс IMultiValueConverter, а затем реализуйте методы Convert и ConvertBack.

public class AllTrueMultiConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values == null || !targetType.IsAssignableFrom(typeof(bool)))
        {
            return false;
            // Alternatively, return BindableProperty.UnsetValue to use the binding FallbackValue
        }

        foreach (var value in values)
        {
            if (!(value is bool b))
            {
                return false;
                // Alternatively, return BindableProperty.UnsetValue to use the binding FallbackValue
            }
            else if (!b)
            {
                return false;
            }
        }
        return true;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        if (!(value is bool b) || targetTypes.Any(t => !t.IsAssignableFrom(typeof(bool))))
        {
            // Return null to indicate conversion back is not possible
            return null;
        }

        if (b)
        {
            return targetTypes.Select(t => (object)true).ToArray();
        }
        else
        {
            // Can't convert back from false because of ambiguity
            return null;
        }
    }
}

Метод Convert преобразует исходные значения в значение для целевого объекта привязки. Xamarin.Forms вызывает этот метод при распространении значений от исходных привязок в целевой объект привязки. Этот метод принимает четыре аргумента:

  • values типа object[] — массив значений, создаваемый исходными привязками в MultiBinding.
  • targetType типа Type — тип целевого свойства привязки.
  • parameter типа object — используемый параметр преобразователя.
  • culture типа CultureInfo — язык и региональные параметры, используемые в преобразователе.

Метод Convert возвращает object — представление преобразованного значения. Этот метод должен возвращать следующее:

  • BindableProperty.UnsetValue — указывает, что преобразователь не создал значение и что привязка будет использовать FallbackValue.
  • Binding.DoNothing — указывает Xamarin.Forms не выполнять никаких действий, например не передавать значение в целевой объект привязки или не использовать FallbackValue.
  • null — указывает, что преобразователь не может выполнить преобразование и что привязка будет использовать TargetNullValue.

Внимание

Объект MultiBinding, принимающий BindableProperty.UnsetValue из метода Convert, должен определять его свойство FallbackValue. Точно так же объект MultiBinding, принимающий null из метода Convert, должен определять его свойство TargetNullValue.

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

  • value типа object — значение, созданное целевым объектом привязки.
  • targetTypes типа Type[] — массив типов для преобразования. Длина массива указывает количество и типы значений, которые, как предполагается, метод будет возвращать.
  • parameter типа object — используемый параметр преобразователя.
  • culture типа CultureInfo — язык и региональные параметры, используемые в преобразователе.

Метод ConvertBack возвращает массив значений типа object[], преобразованных из целевых в исходные. Этот метод должен возвращать следующее:

  • BindableProperty.UnsetValue в позиции i — указывает, что преобразователь не может предоставить значение для исходной привязки по индексу i и что для него не задано значение.
  • Binding.DoNothing в позиции i — указывает, что для исходной привязки не должно быть задано значение в индексе i.
  • null — указывает, что преобразователь не может выполнить преобразование или что он не поддерживает преобразование в этом направлении.

Использование IMultiValueConverter

IMultiValueConverter используется путем создания экземпляра в словаре ресурсов. Затем на него добавляется ссылка с помощью расширения разметки StaticResource для установки свойства MultiBinding.Converter.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.MultiBindingConverterPage"
             Title="MultiBinding Converter demo">

    <ContentPage.Resources>
        <local:AllTrueMultiConverter x:Key="AllTrueConverter" />
        <local:InverterConverter x:Key="InverterConverter" />
    </ContentPage.Resources>

    <CheckBox>
        <CheckBox.IsChecked>
            <MultiBinding Converter="{StaticResource AllTrueConverter}">
                <Binding Path="Employee.IsOver16" />
                <Binding Path="Employee.HasPassedTest" />
                <Binding Path="Employee.IsSuspended"
                         Converter="{StaticResource InverterConverter}" />
            </MultiBinding>
        </CheckBox.IsChecked>
    </CheckBox>
</ContentPage>    

В этом примере объект MultiBinding использует экземпляр AllTrueMultiConverter, чтобы задать для свойства CheckBox.IsChecked значение true, если трем объектам Binding присваивается значение true. В противном случае свойству CheckBox.IsChecked присваивается значение false.

По умолчанию свойство CheckBox.IsChecked использует привязку TwoWay. Таким образом, метод ConvertBack экземпляра AllTrueMultiConverter выполняется, когда пользователь не использует CheckBox, задавая для значений привязки к источнику значение свойства CheckBox.IsChecked.

Ниже приведен эквивалентный код на C#:

public class MultiBindingConverterCodePage : ContentPage
{
    public MultiBindingConverterCodePage()
    {
        BindingContext = new GroupViewModel();

        CheckBox checkBox = new CheckBox();
        checkBox.SetBinding(CheckBox.IsCheckedProperty, new MultiBinding
        {
            Bindings = new Collection<BindingBase>
            {
                new Binding("Employee1.IsOver16"),
                new Binding("Employee1.HasPassedTest"),
                new Binding("Employee1.IsSuspended", converter: new InverterConverter())
            },
            Converter = new AllTrueMultiConverter()
        });

        Title = "MultiBinding converter demo";
        Content = checkBox;
    }
}

Строки формата

MultiBinding может форматировать результат множественной привязки, отображаемый в виде строки, со свойством StringFormat. Для этого свойства можно задать стандартную строку форматирования .NET с заполнителями, которая определяет способ форматирования результата множественной привязки.

<Label>
    <Label.Text>
        <MultiBinding StringFormat="{}{0} {1} {2}">
            <Binding Path="Employee1.Forename" />
            <Binding Path="Employee1.MiddleName" />
            <Binding Path="Employee1.Surname" />
        </MultiBinding>
    </Label.Text>
</Label>

В этом примере свойство StringFormat объединяет три привязанных значения в одну строку, отображаемую Label.

Ниже приведен эквивалентный код на C#:

Label label = new Label();
label.SetBinding(Label.TextProperty, new MultiBinding
{
    Bindings = new Collection<BindingBase>
    {
        new Binding("Employee1.Forename"),
        new Binding("Employee1.MiddleName"),
        new Binding("Employee1.Surname")
    },
    StringFormat = "{0} {1} {2}"
});

Внимание

Число параметров в формате составной строки не должно превышать число дочерних Binding объектов в MultiBinding.

При установке свойств Converter и StringFormat сначала к значению данных применяется преобразователь, а затем применяется StringFormat.

См. сведения в статье Форматирование строк Xamarin.Forms.

Предоставление резервных значений

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

MultiBinding будет использовать FallbackValue, когда метод Convert экземпляра IMultiValueConverter возвращает BindableProperty.UnsetValue. Это означает, что преобразователь не создал значение. MultiBinding будет использовать TargetNullValue, когда метод Convert экземпляра IMultiValueConverter возвращает null. Это означает, что преобразователь не может выполнить преобразование.

См. сведения в статье Резервные значения привязок Xamarin.Forms.

Вложение объектов Nest MultiBinding

MultiBinding объекты могут быть вложенными, чтобы при вычислении нескольких объектов MultiBinding значение возвращалось через экземпляр IMultiValueConverter.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.NestedMultiBindingPage"
             Title="Nested MultiBinding demo">

    <ContentPage.Resources>
        <local:AllTrueMultiConverter x:Key="AllTrueConverter" />
        <local:AnyTrueMultiConverter x:Key="AnyTrueConverter" />
        <local:InverterConverter x:Key="InverterConverter" />
    </ContentPage.Resources>

    <CheckBox>
        <CheckBox.IsChecked>
            <MultiBinding Converter="{StaticResource AnyTrueConverter}">
                <MultiBinding Converter="{StaticResource AllTrueConverter}">
                    <Binding Path="Employee.IsOver16" />
                    <Binding Path="Employee.HasPassedTest" />
                    <Binding Path="Employee.IsSuspended" Converter="{StaticResource InverterConverter}" />                        
                </MultiBinding>
                <Binding Path="Employee.IsMonarch" />
            </MultiBinding>
        </CheckBox.IsChecked>
    </CheckBox>
</ContentPage>

В этом примере объект MultiBinding использует свой экземпляр AnyTrueMultiConverter, чтобы задать для свойства CheckBox.IsChecked значение true, если всем объектам Binding во внутреннем объекте MultiBinding присваивается значение true или если объекту Binding во внешнем объекте MultiBinding присваивается значение true. В противном случае свойству CheckBox.IsChecked присваивается значение false.

Использование привязки RelativeSource в MultiBinding

Объект MultiBinding поддерживает относительные привязки, которые позволяют задать источник привязки относительно положения целевого объекта привязки.

<ContentPage ...
             xmlns:local="clr-namespace:DataBindingDemos"
             xmlns:xct="clr-namespace:Xamarin.CommunityToolkit.UI.Views;assembly=Xamarin.CommunityToolkit">
    <ContentPage.Resources>
        <local:AllTrueMultiConverter x:Key="AllTrueConverter" />

        <ControlTemplate x:Key="CardViewExpanderControlTemplate">
            <xct:Expander BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
                          IsExpanded="{Binding IsExpanded, Source={RelativeSource TemplatedParent}}"
                          BackgroundColor="{Binding CardColor}">
                <xct:Expander.IsVisible>
                    <MultiBinding Converter="{StaticResource AllTrueConverter}">
                        <Binding Path="IsExpanded" />
                        <Binding Path="IsEnabled" />
                    </MultiBinding>
                </xct:Expander.IsVisible>
                <xct:Expander.Header>
                    <Grid>
                        <!-- XAML that defines Expander header goes here -->
                    </Grid>
                </xct:Expander.Header>
                <Grid>
                    <!-- XAML that defines Expander content goes here -->
                </Grid>
            </xct:Expander>
        </ControlTemplate>
    </ContentPage.Resources>

    <StackLayout>
        <controls:CardViewExpander BorderColor="DarkGray"
                                   CardTitle="John Doe"
                                   CardDescription="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla elit dolor, convallis non interdum."
                                   IconBackgroundColor="SlateGray"
                                   IconImageSource="user.png"
                                   ControlTemplate="{StaticResource CardViewExpanderControlTemplate}"
                                   IsEnabled="True"
                                   IsExpanded="True" />
    </StackLayout>
</ContentPage>

Примечание.

Элемент управления Expander теперь входит в набор инструментов Xamarin Community Toolkit.

В этом примере режим относительной привязки TemplatedParent используется для привязки из шаблона элемента управления к экземпляру объекта среды выполнения, к которому применяется шаблон. Expander — это корневой элемент ControlTemplate, у которого есть BindingContext с заданным экземпляром объекта среды выполнения, к которому применяется шаблон. Следовательно, Expander с дочерними элементами разрешают свои выражения привязки и объекты Binding для свойств объекта CardViewExpander. MultiBinding использует экземпляр AllTrueMultiConverter, чтобы задать для свойства Expander.IsVisible значение true, если двум объектам Binding присваивается значение true. В противном случае свойству Expander.IsVisible присваивается значение false.

Дополнительные сведения об относительных привязках Xamarin.Forms см. в этой статье. Дополнительные сведения о шаблонах элементов управления см. в разделе Шаблоны элементов управления Xamarin.Forms.