绑定值转换器

Browse sample.浏览示例

.NET Multi-platform App UI (.NET MAUI) 数据绑定通常将数据从源属性传输到目标属性,在某些情况下则从目标属性传输到源属性。 当源和目标属性都是同一类型,或当一个类型可以隐式转换为另一种类型时,这类传递都是非常简单的。 如果不是这种情况,则必须执行类型转换。

字符串格式设置一文中,你已了解如何使用数据绑定的 StringFormat 属性将任何类型转换为字符串。 对于其他类型的转换,需要在类中编写一些专门的代码以实现 IValueConverter 接口。 实现 IValueConverter 的类被称为“值转换器”,但它们通常也被称为“绑定转换器”或“绑定值转换器”

绑定值转换器

假设想要定义源属性为类型 int 但目标属性为 bool 的数据绑定。 想要此数据绑定在整数源等于 0 时生成 false 值,在其他情况则生成 true。 可以使用实现 IValueConverter 接口的类来达成此目的:

public class IntToBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (int)value != 0;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (bool)value ? 1 : 0;
    }
}

然后,将此类的实例设置为 Binding 类的 Converter 属性或 Binding 标记扩展的 Converter 属性。 此类成为数据绑定的一部分。

当数据在 OneWayTwoWay 绑定中由源移动到目标时,将调用 Convert 方法。 value 是来自数据绑定源的对象或值。 该方法必须返回数据绑定目标类型的值。 此处所示的方法将 value 参数强制转换为 int,然后将其与 0 比较并得到 bool 返回值。

当数据在 TwoWayOneWayToSource 绑定中由目标移动到源时,将调用 ConvertBack 方法。 ConvertBack 执行相反的转换:它假定 value 参数是来自目标的 bool,然后将其转换为源的 int 返回值。

注意

如果数据绑定还包括 StringFormat 设置,则在将结果格式化为字符串之前调用值转换器。

以下示例演示如何在数据绑定中使用此值转换器:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.EnableButtonsPage"
             Title="Enable Buttons">
    <ContentPage.Resources>
        <local:IntToBoolConverter x:Key="intToBool" />
    </ContentPage.Resources>

    <StackLayout Padding="10, 0">
        <Entry x:Name="entry1"
               Text=""
               Placeholder="enter search term"
               VerticalOptions="Center" />
        <Button x:DataType="Entry"
                Text="Search"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                IsEnabled="{Binding Source={x:Reference entry1},
                                    Path=Text.Length,
                                    Converter={StaticResource intToBool}}" />
        <Entry x:Name="entry2"
               Text=""
               Placeholder="enter destination"
               VerticalOptions="Center" />
        <Button x:DataType="Entry"
                Text="Submit"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                IsEnabled="{Binding Source={x:Reference entry2},
                                    Path=Text.Length,
                                    Converter={StaticResource intToBool}}" />
    </StackLayout>
</ContentPage>

在此示例中,在页面的资源字典中实例化 IntToBoolConverter。 然后,使用 StaticResource 标记扩展引用它,以在两个数据绑定中设置 Converter 属性。 在页面上的多个数据绑定间共享数据转换器是很常见的: 如果在应用程序的多个页面中使用了值转换器,则可以在应用程序级资源字典中将其实例化。

此示例演示 Button 根据用户键入 Entry 视图中的文本执行操作时的常见需求。 将每个 EntryText 属性初始化为空字符串,因为默认情况下 Text 属性为 null,在这种情况下,数据绑定将不起作用。 如果用户没有在 Entry 中键入任何内容,则应禁用 Button。 每个 Button 都包含其 IsEnabled 属性的数据绑定。 数据绑定源是相应 EntryText 属性的 Length 属性。 如果 Length属性不是 0,则值转换器返回 true 并启用 Button

Enable buttons.

注意

如果知道值转换器将仅用于 OneWay 绑定,则 ConvertBack 方法只能返回 null

上面显示的 Convert 方法假定 value 参数的类型为 int,并且返回值的类型必须为 bool。 同样,ConvertBack 方法假设 value 参数的类型为 bool,返回值为 int。 如果不是这种情况,将发生运行时异常。

可以将值转换器编写得更加通用化且可以接受多种不同类型的数据。 ConvertConvertBack 方法可以使用带有 value 参数的 asis 运算符,或者可以在该参数上调用 GetType 以确定其类型,然后再执行适当的操作。 每个方法的返回值的预期类型由 targetType 参数给出。 有时,值转换器与不同目标类型的数据绑定一起使用。 在这种情况下,值转换器可以使用 targetType 参数为正确的类型执行转换。

如果正在执行对于不同区域性会有所不同的转换,请使用 culture 参数实现此目的。

绑定转换器属性

值转换器类可以具有属性和泛型参数。 以下值转换器针对目标将源中的 bool 转换为类型为 T 的对象:

public class BoolToObjectConverter<T> : IValueConverter
{
    public T TrueObject { get; set; }
    public T FalseObject { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (bool)value ? TrueObject : FalseObject;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return ((T)value).Equals(TrueObject);
    }
}

以下示例演示如何使用此转换器来显示 Switch 视图的值。 虽然在资源字典中将值转换器实例化为资源很常见,但此页面演示了另一种替代方法。 此处的每个值转换器都在 Binding.Converter 属性元素标记之间实例化。 x:TypeArguments 指示泛型参数,并将 TrueObjectFalseObject 都设置为该类型的对象:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.SwitchIndicatorsPage"
             Title="Switch Indicators">
    <ContentPage.Resources>
        <Style TargetType="Label">
            <Setter Property="FontSize" Value="18" />
            <Setter Property="VerticalOptions" Value="Center" />
        </Style>

        <Style TargetType="Switch">
            <Setter Property="VerticalOptions" Value="Center" />
        </Style>
    </ContentPage.Resources>

    <StackLayout Padding="10, 0">
        <StackLayout Orientation="Horizontal"
                     VerticalOptions="Center">
            <Label Text="Subscribe?" />
            <Switch x:Name="switch1" />
            <Label>
                <Label.Text>
                    <Binding x:DataType="Switch"
                             Source="{x:Reference switch1}"
                             Path="IsToggled">
                        <Binding.Converter>
                            <local:BoolToObjectConverter x:TypeArguments="x:String"
                                                         TrueObject="Of course!"
                                                         FalseObject="No way!" />
                        </Binding.Converter>
                    </Binding>
                </Label.Text>
            </Label>
        </StackLayout>

        <StackLayout Orientation="Horizontal"
                     VerticalOptions="Center">
            <Label Text="Allow popups?" />
            <Switch x:Name="switch2" />
            <Label>
                <Label.Text>
                    <Binding x:DataType="Switch"
                             Source="{x:Reference switch2}"
                             Path="IsToggled">
                        <Binding.Converter>
                            <local:BoolToObjectConverter x:TypeArguments="x:String"
                                                         TrueObject="Yes"
                                                         FalseObject="No" />
                        </Binding.Converter>
                    </Binding>
                </Label.Text>
                <Label.TextColor>
                    <Binding x:DataType="Switch"
                             Source="{x:Reference switch2}"
                             Path="IsToggled">
                        <Binding.Converter>
                            <local:BoolToObjectConverter x:TypeArguments="Color"
                                                         TrueObject="Green"
                                                         FalseObject="Red" />
                        </Binding.Converter>
                    </Binding>
                </Label.TextColor>
            </Label>
        </StackLayout>

        <StackLayout Orientation="Horizontal"
                     VerticalOptions="Center">
            <Label Text="Learn more?" />
            <Switch x:Name="switch3" />
            <Label FontSize="18"
                   VerticalOptions="Center">
                <Label.Style>
                    <Binding x:DataType="Switch"
                             Source="{x:Reference switch3}"
                             Path="IsToggled">
                        <Binding.Converter>
                            <local:BoolToObjectConverter x:TypeArguments="Style">
                                <local:BoolToObjectConverter.TrueObject>
                                    <Style TargetType="Label">
                                        <Setter Property="Text" Value="Indubitably!" />
                                        <Setter Property="FontAttributes" Value="Italic, Bold" />
                                        <Setter Property="TextColor" Value="Green" />
                                    </Style>
                                </local:BoolToObjectConverter.TrueObject>

                                <local:BoolToObjectConverter.FalseObject>
                                    <Style TargetType="Label">
                                        <Setter Property="Text" Value="Maybe later" />
                                        <Setter Property="FontAttributes" Value="None" />
                                        <Setter Property="TextColor" Value="Red" />
                                    </Style>
                                </local:BoolToObjectConverter.FalseObject>
                            </local:BoolToObjectConverter>
                        </Binding.Converter>
                    </Binding>
                </Label.Style>
            </Label>
        </StackLayout>
    </StackLayout>
</ContentPage>

在此示例中,在三个 SwitchLabel 对中的最后一个中,将泛型参数设置为 Style,并为 TrueObjectFalseObject 的值提供整个 Style 对象。 这些替代了资源字典中设置的 Label 的隐式样式,因此该样式中的属性被显式分配给 Label。 切换 Switch 会导致相应的 Label 对此更改作出反应:

Switch indicators.

注意

还可以使用触发器基于其他视图在用户界面中实现更改。 有关详细信息,请参阅触发器

绑定转换器参数

Binding 类定义 ConverterParameter 属性,Binding 标记扩展也定义 ConverterParameter 属性。 如果设置此属性,则该值将作为 parameter 参传递到 ConvertConvertBack 方法。 即使值转换器实例在多个数据绑定之间共享,ConverterParameter 也可能不同,执行的转换也会有所不同。

使用颜色选择程序可以演示 ConverterParameter 属性的用法。 以下示例显示 RgbColorViewModel,它有三个用于构造 Color 值且类型为 float 的属性,分别名为 RedGreenBlue

public class RgbColorViewModel : INotifyPropertyChanged
{
    Color color;
    string name;

    public event PropertyChangedEventHandler PropertyChanged;

    public float Red
    {
        get { return color.Red; }
        set
        {
            if (color.Red != value)
            {
                Color = new Color(value, color.Green, color.Blue);
            }
        }
    }

    public float Green
    {
        get { return color.Green; }
        set
        {
            if (color.Green != value)
            {
                Color = new Color(color.Red, value, color.Blue);
            }
        }
    }

    public float Blue
    {
        get { return color.Blue; }
        set
        {
            if (color.Blue != value)
            {
                Color = new Color(color.Red, color.Green, value);
            }
        }
    }

    public Color Color
    {
        get { return color; }
        set
        {
            if (color != value)
            {
                color = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Red"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Green"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Blue"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));

                Name = NamedColor.GetNearestColorName(color);
            }
        }
    }

    public string Name
    {
        get { return name; }
        private set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
            }
        }
    }
}

RedGreenBlue 属性值介于 0 和 1 之间。 但是,你可能更愿意将组件显示为两位数的十六进制值。 若要在 XAML 中将这些显示为十六进制值,它们必须乘以 255,转换为整数,然后使用 StringFormat 属性中的“X2”规范格式化。 乘以 255 并转换为整数可以由值转换器执行。 若要尽可能提高值转换器的通用性,可以使用 ConverterParameter 属性指定乘法因数,这意味着它将作为 parameter 参数进入 ConvertConvertBack 方法:

public class FloatToIntConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (int)Math.Round((float)value * GetParameter(parameter));
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (int)value / GetParameter(parameter);
    }

    double GetParameter(object parameter)
    {
        if (parameter is float)
            return (float)parameter;
        else if (parameter is int)
            return (int)parameter;
        else if (parameter is string)
            return float.Parse((string)parameter);

        return 1;
    }
}

在此示例中,Convert 方法从 float 转换为 int,同时乘以 parameter 值。 ConvertBack 方法将整数 value 参数除以 parameter,并返回 float 结果。

parameter 参数的类型可能会有所不同,具体取决于是在 XAML 中还是在代码中定义数据绑定。 如果 BindingConverterParameter 属性是在代码中设置的,则很可能设置为数字值:

binding.ConverterParameter = 255;

ConverterParameter 属性的类型为 Object,因此 C# 编译器将文字 255 解释为整数,并将该属性设置为该值。

但是,在 XAML 中,ConverterParameter 可能设置为如下所示:

<Label Text="{Binding Red,
                      Converter={StaticResource doubleToInt},
                      ConverterParameter=255,
                      StringFormat='Red = {0:X2}'}" />

虽然 255 看起来像数字,但由于 ConverterParameter 的类型为 Object,因此 XAML 分析器将 255 视为字符串。 为此,值转换器包括一个单独的 GetParameter 方法,用于处理类型为 floatintstringparameter 的情况。

以下 XAML 示例在其资源字典中实例化 FloatToIntConverter

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.RgbColorSelectorPage"
             Title="RGB Color Selector"
             x:DataType="local:RgbColorViewModel">
    <ContentPage.BindingContext>
        <local:RgbColorViewModel Color="Gray" />
    </ContentPage.BindingContext>
    <ContentPage.Resources>
        <Style TargetType="Slider">
            <Setter Property="VerticalOptions" Value="Center" />
        </Style>

        <Style TargetType="Label">
            <Setter Property="HorizontalTextAlignment" Value="Center" />
        </Style>

        <local:FloatToIntConverter x:Key="floatToInt" />
    </ContentPage.Resources>

    <StackLayout Margin="20">
        <BoxView Color="{Binding Color}"
                 HeightRequest="100"
                 WidthRequest="100"
                 HorizontalOptions="Center" />
        <StackLayout Margin="10, 0">
            <Label Text="{Binding Name}" />
            <Slider Value="{Binding Red}" />
            <Label Text="{Binding Red,
                                  Converter={StaticResource floatToInt},
                                  ConverterParameter=255,
                                  StringFormat='Red = {0:X2}'}" />
            <Slider Value="{Binding Green}" />
            <Label Text="{Binding Green,
                                  Converter={StaticResource floatToInt},
                                  ConverterParameter=255,
                                  StringFormat='Green = {0:X2}'}" />
            <Slider Value="{Binding Blue}" />
            <Label>
                <Label.Text>
                    <Binding Path="Blue"
                             StringFormat="Blue = {0:X2}"
                             Converter="{StaticResource floatToInt}">
                        <Binding.ConverterParameter>
                            <x:Single>255</x:Single>
                        </Binding.ConverterParameter>
                    </Binding>
                </Label.Text>
            </Label>
        </StackLayout>
    </StackLayout>
</ContentPage>

RedGreen 属性的值是使用 Binding 标记扩展显示的。 但是,Blue 属性实例化 Binding 类,以演示如何将显式 float 值设置为 ConverterParameter 属性:

RGB color selector.