Xamarin.Forms 多绑定

借助多绑定,可以将 Binding 对象的集合附加到一个绑定目标属性。 它们是使用 MultiBinding 类创建的,此类计算其所有 Binding 对象,并通过应用程序提供的 IMultiValueConverter 实例返回一个值。 此外,当任何绑定数据发生更改时,MultiBinding 会重新计算其所有 Binding 对象。

MultiBinding 类定义以下属性:

  • Bindings:类型为 IList<BindingBase>,表示 MultiBinding 实例内 Binding 对象的集合。
  • Converter:类型为 IMultiValueConverter,表示用于在源值和目标值之间来回转换的转换器。
  • ConverterParameter:类型为 object,表示要传递给 Converter 的可选参数。

由于 Bindings 属性是 MultiBinding 类的内容属性,因此不需要通过 XAML 显式设置。

此外,MultiBinding 类还从 BindingBase 类继承了以下属性:

  • FallbackValue:类型为 object,表示在多绑定无法返回值时使用的值。
  • Mode:类型为 BindingMode,指明多绑定的数据流方向。
  • StringFormat:类型为 string,指定如何设置显示为字符串的多绑定结果的格式。
  • TargetNullValue:类型为 object,表示当源值为 null 时在目标中使用的值。

MultiBinding 必须使用 IMultiValueConverter 以根据 Bindings 集合中的绑定值来生成绑定目标的值。 例如,可以使用 red、blue 和 green 值计算 Color,这些值可以来自相同或不同的绑定源对象。 当值从目标移动到源时,目标属性值被转换为一组值,这些值反馈回绑定。

重要

Bindings 集合中的各个绑定都可以有自己的值转换器。

Mode 属性的值决定了 MultiBinding 的功能,并用作集合中所有绑定的绑定模式,除非某个绑定替代了此属性。 例如,如果 MultiBinding 对象的 Mode 属性设置为 TwoWay,那么集合中的所有绑定都会被视为 TwoWay,除非你在其中一个绑定上显式设置了不同的 Mode 值。

定义 IMultiValueConverter

使用 IMultiValueConverter 接口,可以将自定义逻辑应用于 MultiBinding。 若要将转换器与 MultiBinding 相关联,请先创建实现 IMultiValueConverter 接口的类,再实现 ConvertConvertBack 方法:

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 不要执行任何操作。 例如,指示 Xamarin.Forms 不要将值传输到绑定目标,或不要使用 FallbackValue
  • null:指明转换器无法执行转换,绑定将使用 TargetNullValue

重要

Convert 方法接收 BindableProperty.UnsetValueMultiBinding 必须定义其 FallbackValue 属性。 同样,从 Convert 方法接收 nullMultiBinding 必须定义其 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 绑定。 因此,如果用户取消选中 CheckBox(即将源绑定值设置为 CheckBox.IsChecked 属性的值),就会执行 AllTrueMultiConverter 实例的 ConvertBack 方法。

等效 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}"
});

重要

采用复合字符串格式的参数数量不得超过 MultiBinding 中子 Binding 对象的数量。

如果同时设置了 ConverterStringFormat 属性,转换器先应用于数据值,然后应用 StringFormat

若要详细了解 Xamarin.Forms 中的字符串格式设置,请参阅 Xamarin.Forms 字符串格式设置

提供回退值

通过定义在绑定过程失败时使用的回退值,可以提高数据绑定的可靠性。 为此,可以视需要选择在 MultiBinding 对象上定义 FallbackValueTargetNullValue 属性。

IMultiValueConverter 实例的 Convert 方法返回 BindableProperty.UnsetValue(指明转换器没有生成值)时,MultiBinding 就会使用其 FallbackValue。 当 IMultiValueConverter 实例的 Convert 方法返回 null(指明转换器无法执行转换)时,MultiBinding 就会使用其 TargetNullValue

若要详细了解绑定回退,请参阅 Xamarin.Forms 绑定回退

嵌套 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,前提是内部 MultiBinding 对象中的所有 Binding 对象的计算结果都为 true,或外部 MultiBinding 对象中的 Binding 对象的计算结果为 true。 否则,CheckBox.IsChecked 属性设置为 false

在 MultiBinding 中使用 RelativeSource 绑定

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 相对绑定模式用于从控件模板内绑定到模板应用到的运行时对象实例。 ExpanderControlTemplate 的根元素)将 BindingContext 设置为模板应用到的运行时对象实例。 因此,Expander 及其子级根据 CardViewExpander 对象的属性来解析自己的绑定表达式和 Binding 对象。 MultiBinding 使用 AllTrueMultiConverter 实例将 Expander.IsVisible 属性设置为 true,前提是两个 Binding 对象的计算结果为 true。 否则,Expander.IsVisible 属性设置为 false

有关相对绑定的详细信息,请参阅 Xamarin.Forms 相对绑定。 有关控件模板的详细信息,请参阅 Xamarin.Forms 控件模板