Xamarin.Forms 绑定值转换器Xamarin.Forms Binding Value Converters

下载示例 下载示例Download Sample Download the sample

一般情况下数据绑定会将数据从源属性传递到目标属性,某些情况下则从目标属性传递到源属性。Data bindings usually transfer data from a source property to a target property, and in some cases from the target property to the source property. 当源和目标属性都是同一类型,或当一个类型可以隐式转换为另一种类型时,这类传递都是非常简单的。This transfer is straightforward when the source and target properties are of the same type, or when one type can be converted to the other type through an implicit conversion. 如果不是这种情况,则必须执行类型转换。When that is not the case, a type conversion must take place.

字符串格式设置一文已介绍如何使用数据绑定的 StringFormat 属性将任意类型转换为字符串 。In the String Formatting article, you saw how you can use the StringFormat property of a data binding to convert any type into a string. 对于其他类型的转换,需要在类中编写一些专门的代码以实现 IValueConverter 接口。For other types of conversions, you need to write some specialized code in a class that implements the IValueConverter interface. (通用 Windows 平台在 Windows.UI.Xaml.Data 命名空间中包含一个名为 IValueConverter 的类似的类,但此 IValueConverterXamarin.Forms 命名空间中。)实现 IValueConverter 的类被称为“值转换器”,但它们通常也被称为“绑定转换器”或“绑定值转换器” 。(The Universal Windows Platform contains a similar class named IValueConverter in the Windows.UI.Xaml.Data namespace, but this IValueConverter is in the Xamarin.Forms namespace.) Classes that implement IValueConverter are called value converters, but they are also often referred to as binding converters or binding value converters.

IValueConverter 接口The IValueConverter Interface

假设想要定义源属性为类型 int 但目标属性为 bool 的数据绑定。Suppose you want to define a data binding where the source property is of type int but the target property is a bool. 想要此数据绑定在整数源等于 0 时生成 false 值,在其他情况则生成 trueYou want this data binding to produce a false value when the integer source is equal to 0, and true otherwise.

可以使用实现 IValueConverter 接口的类做到这一点:You can do this with a class that implements the IValueConverter interface:

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 属性。You set an instance of this class to the Converter property of the Binding class or to the Converter property of the Binding markup extension. 此类成为数据绑定的一部分。This class becomes part of the data binding.

当数据在 OneWayTwoWay 绑定中由源移动到目标时,将调用 Convert 方法。The Convert method is called when data moves from the source to the target in OneWay or TwoWay bindings. value 是来自数据绑定源的对象或值。The value parameter is the object or value from the data-binding source. 该方法必须返回数据绑定目标类型的值。The method must return a value of the type of the data-binding target. 此处所示的方法将 value 参数强制转换为 int,然后将其与 0 比较并得到 bool 返回值。The method shown here casts the value parameter to an int and then compares it with 0 for a bool return value.

当数据在 TwoWayOneWayToSource 绑定中由目标移动到源时,将调用 ConvertBack 方法。The ConvertBack method is called when data moves from the target to the source in TwoWay or OneWayToSource bindings. ConvertBack执行相反的转换:它假定 value 参数是来自目标的 bool,然后将其转换为源的 int 返回值。ConvertBack performs the opposite conversion: It assumes the value parameter is a bool from the target, and converts it to an int return value for the source.

如果数据绑定还包括 StringFormat 设置,则在结果格式化为字符串之前调用值转换器。If the data binding also includes a StringFormat setting, the value converter is invoked before the result is formatted as a string.

数据绑定演示示例中的启用按钮页面演示了如何在数据绑定中使用此值转换器 。The Enable Buttons page in the Data Binding Demos sample demonstrates how to use this value converter in a data binding. IntToBoolConverter 在页面的资源字典中实例化。The IntToBoolConverter is instantiated in the page's resource dictionary. 然后使用 StaticResource 标记扩展引用它以在两个数据绑定中设置 Converter 属性。It is then referenced with a StaticResource markup extension to set the Converter property in two data bindings. 在页面中的多个数据绑定中共享数据转换器是很常见的:It is very common to share data converters among multiple data bindings on the page:

<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.EnableButtonsPage"
             Title="Enable Buttons">
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:IntToBoolConverter x:Key="intToBool" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout Padding="10, 0">
        <Entry x:Name="entry1"
               Text=""
               Placeholder="enter search term"
               VerticalOptions="CenterAndExpand" />

        <Button Text="Search"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                IsEnabled="{Binding Source={x:Reference entry1},
                                    Path=Text.Length,
                                    Converter={StaticResource intToBool}}" />

        <Entry x:Name="entry2"
               Text=""
               Placeholder="enter destination"
               VerticalOptions="CenterAndExpand" />

        <Button Text="Submit"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                IsEnabled="{Binding Source={x:Reference entry2},
                                    Path=Text.Length,
                                    Converter={StaticResource intToBool}}" />
    </StackLayout>
</ContentPage>

如果在应用程序的多个页面中使用值转换器,则可以在 App.xaml 文件的资源字典中将其实例化 。If a value converter is used in multiple pages of your application, you can instantiate it in the resource dictionary in the App.xaml file.

启用按钮页面演示了当 Button 基于用户在 Entry 视图中键入的文本执行操作时的常见需求 。The Enable Buttons page demonstrates a common need when a Button performs an operation based on text that the user types into an Entry view. 如果用户没有在 Entry 中键入任何内容,则应禁用 ButtonIf nothing has been typed into the Entry, the Button should be disabled. 每个 Button 都包含其 IsEnabled 属性的数据绑定。Each Button contains a data binding on its IsEnabled property. 数据绑定源是相应 EntryText 属性的 Length 属性。The data-binding source is the Length property of the Text property of the corresponding Entry. 如果 Length属性不是 0,则值转换器返回 true 并启用 ButtonIf that Length property is not 0, the value converter returns true and the Button is enabled:

启用按钮Enable Buttons

请注意,每个 Entry 中的 Text 属性都将初始化为空字符串。Notice that the Text property in each Entry is initialized to an empty string. 默认情况下 Text 属性为 null,且在此情况下不会运行数据绑定。The Text property is null by default, and the data binding will not work in that case.

某些值转换器是专门为特定应用程序编写的,而其他值转换器已通用化。Some value converters are written specifically for particular applications, while others are generalized. 如果知道值转换器将仅用于 OneWay 绑定,则 ConvertBack 方法只能返回 nullIf you know that a value converter will only be used in OneWay bindings, then the ConvertBack method can simply return null.

上面所示的 Convert 方法隐式假设 value 参数的类型为 int,返回值的类型必须为 boolThe Convert method shown above implicitly assumes that the value argument is of type int and the return value must be of type bool. 同样,ConvertBack 方法假设 value 参数的类型为 bool,返回值为 intSimilarly, the ConvertBack method assumes that the value argument is of type bool and the return value is int. 如果不是这种情况,将发生运行时异常。If that is not the case, a runtime exception will occur.

可以将值转换器编写得更加通用化且可以接受多种不同类型的数据。You can write value converters to be more generalized and to accept several different types of data. ConvertConvertBack 方法可以使用带有 value 参数的 asis 运算符,或者可以在该参数上调用 GetType 以确定其类型,然后再执行适当的操作。The Convert and ConvertBack methods can use the as or is operators with the value parameter, or can call GetType on that parameter to determine its type, and then do something appropriate. 每个方法的返回值的预期类型由 targetType 参数给出。The expected type of each method's return value is given by the targetType parameter. 有时,值转换器与不同目标类型的数据绑定一起使用;值转换器可以使用 targetType 参数来执行正确类型的转换。Sometimes, value converters are used with data bindings of different target types; the value converter can use the targetType argument to perform a conversion for the correct type.

如果正在执行对于不同区域性会有所不同的转换,请使用 culture 参数实现此目的。If the conversion being performed is different for different cultures, use the culture parameter for this purpose. ConvertConvertBackparameter 参数稍后将在本文中介绍。The parameter argument to Convert and ConvertBack is discussed later in this article.

绑定转换器属性Binding Converter Properties

值转换器类可以具有属性和泛型参数。Value converter classes can have properties and generic parameters. 此特定值转换器将 bool 从源转换为目标的 T 类型对象:This particular value converter converts a bool from the source to an object of type T for the target:

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

    public T FalseObject { set; get; }

    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 视图的值 。The Switch Indicators page demonstrates how it can be used to display the value of a Switch view. 虽然将值转换器实例化为资源字典中的资源很常见,但此页面演示了另一种选择:每个值转换器都在 Binding.Converter 属性元素标记之间实例化。Although it's common to instantiate value converters as resources in a resource dictionary, this page demonstrates an alternative: Each value converter is instantiated between Binding.Converter property-element tags. x:TypeArguments 指示泛型参数,并将 TrueObjectFalseObject 都设置为该类型的对象:The x:TypeArguments indicates the generic argument, and TrueObject and FalseObject are both set to objects of that type:

<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.SwitchIndicatorsPage"
             Title="Switch Indicators">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Label">
                <Setter Property="FontSize" Value="18" />
                <Setter Property="VerticalOptions" Value="Center" />
            </Style>

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

    <StackLayout Padding="10, 0">
        <StackLayout Orientation="Horizontal"
                     VerticalOptions="CenterAndExpand">
            <Label Text="Subscribe?" />
            <Switch x:Name="switch1" />
            <Label>
                <Label.Text>
                    <Binding 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="CenterAndExpand">
            <Label Text="Allow popups?" />
            <Switch x:Name="switch2" />
            <Label>
                <Label.Text>
                    <Binding 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 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="CenterAndExpand">
            <Label Text="Learn more?" />
            <Switch x:Name="switch3" />
            <Label FontSize="18"
                   VerticalOptions="Center">
                <Label.Style>
                    <Binding 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 对象。In the last of the three Switch and Label pairs, the generic argument is set to Style, and entire Style objects are provided for the values of TrueObject and FalseObject. 这些替代了资源字典中设置的 Label 的隐式样式,因此该样式中的属性被显式分配给 LabelThese override the implicit style for Label set in the resource dictionary, so the properties in that style are explicitly assigned to the Label. 切换 Switch 会导致相应的 Label 对此更改作出反应:Toggling the Switch causes the corresponding Label to reflect the change:

切换指示器Switch Indicators

它也可以使用 Triggers 在基于其他视图的用户界面中实现类似的更改。It's also possible to use Triggers to implement similar changes in the user-interface based on other views.

绑定转换器参数Binding Converter Parameters

Binding 类定义 ConverterParameter 属性,Binding 标记扩展也定义 ConverterParameter 属性。The Binding class defines a ConverterParameter property, and the Binding markup extension also defines a ConverterParameter property. 如果设置此属性,则该值将作为 parameter 参传递到 ConvertConvertBack 方法。If this property is set, then the value is passed to the Convert and ConvertBack methods as the parameter argument. 即使值转换器实例在多个数据绑定之间共享,ConverterParameter 也可能不同,执行的转换就会有所不同。Even if the instance of the value converter is shared among several data bindings, the ConverterParameter can be different to perform somewhat different conversions.

使用颜色选择程序演示了 ConverterParameter 的用法。The use of ConverterParameter is demonstrated with a color-selection program. 在这种情况下,RgbColorViewModel 有三个分别名为 RedGreenBlue 的类型 double 属性,用于构造 Color 值:In this case, the RgbColorViewModel has three properties of type double named Red, Green, and Blue that it uses to construct a Color value:

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

    public event PropertyChangedEventHandler PropertyChanged;

    public double Red
    {
        set
        {
            if (color.R != value)
            {
                Color = new Color(value, color.G, color.B);
            }
        }
        get
        {
            return color.R;
        }
    }

    public double Green
    {
        set
        {
            if (color.G != value)
            {
                Color = new Color(color.R, value, color.B);
            }
        }
        get
        {
            return color.G;
        }
    }

    public double Blue
    {
        set
        {
            if (color.B != value)
            {
                Color = new Color(color.R, color.G, value);
            }
        }
        get
        {
            return color.B;
        }
    }

    public Color 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);
            }
        }
        get
        {
            return color;
        }
    }

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

RedGreenBlue 的属性范围介于 0 和 1 之间。The Red, Green, and Blue properties range between 0 and 1. 但是,你可能更愿意将组件显示为两位数的十六进制值。However, you might prefer that the components be displayed as two-digit hexadecimal values.

若要在 XAML 中将这些显示为十六进制值,它们必须乘以 255,转换为整数,然后使用 StringFormat 属性中的“X2”规范格式化。To display these as hexadecimal values in XAML, they must be multiplied by 255, converted to an integer, and then formatted with a specification of "X2" in the StringFormat property. 前两个任务(乘以 255 并转换为整数)可以由值转换器处理。The first two tasks (multiplying by 255 and converting to an integer) can be handled by the value converter. 若要尽可能提高值转换器的通用性,可以使用 ConverterParameter 属性指定乘法因数,这意味着它将作为 parameter 参数进入 ConvertConvertBack 方法:To make the value converter as generalized as possible, the multiplication factor can be specified with the ConverterParameter property, which means that it enters the Convert and ConvertBack methods as the parameter argument:

public class DoubleToIntConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (int)Math.Round((double)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 double)
            return (double)parameter;

        else if (parameter is int)
            return (int)parameter;

        else if (parameter is string)
            return double.Parse((string)parameter);

        return 1;
    }
}

Convertdouble 转换为 int,同时乘以 parameter 值;ConvertBack 将整数 value 参数除以 parameter 并返回 double 结果。The Convert converts from a double to int while multiplying by the parameter value; the ConvertBack divides the integer value argument by parameter and returns a double result. (在下面显示的程序中,值转换器仅用于和字符串格式设置的连接中,因此不使用 ConvertBack。)(In the program shown below, the value converter is used only in connection with string formatting, so ConvertBack is not used.)

parameter 参数的类型可能会有所不同,具体取决于数据绑定是在代码中还是在 XAML 中定义的。The type of the parameter argument is likely to be different depending on whether the data binding is defined in code or XAML. 如果 BindingConverterParameter 属性是在代码中设置的,则很可能设置为数字值:If the ConverterParameter property of Binding is set in code, it's likely to be set to a numeric value:

binding.ConverterParameter = 255;

ConverterParameter 属性的类型为 Object,因此 C# 编译器将文字 255 解释为整数,并将该属性设置为该值。The ConverterParameter property is of type Object, so the C# compiler interprets the literal 255 as an integer, and sets the property to that value.

但是,在 XAML 中,ConverterParameter 很可能设置为这样:In XAML, however, the ConverterParameter is likely to be set like this:

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

255 看起来是一个数字,但因为 ConverterParameter 属于类型 Object,因此 XAML 分析器将 255 视为字符串。The 255 looks like a number, but because ConverterParameter is of type Object, the XAML parser treats the 255 as a string.

为此,上述的值转换器包含一个单独的 GetParameter 方法,该方法用于处理 parameter 属于类型 doubleintstring 的情况。For that reason, the value converter shown above includes a separate GetParameter method that handles cases for parameter being of type double, int, or string.

RGB 颜色选择器页面遵照两个隐式样式的定义在其资源字典中实例化 DoubleToIntConverterThe RGB Color Selector page instantiates DoubleToIntConverter in its resource dictionary following the definition of two implicit styles:

<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.RgbColorSelectorPage"
             Title="RGB Color Selector">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Slider">
                <Setter Property="VerticalOptions" Value="CenterAndExpand" />
            </Style>

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

            <local:DoubleToIntConverter x:Key="doubleToInt" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout>
        <StackLayout.BindingContext>
            <local:RgbColorViewModel Color="Gray" />
        </StackLayout.BindingContext>

        <BoxView Color="{Binding Color}"
                 VerticalOptions="FillAndExpand" />

        <StackLayout Margin="10, 0">
            <Label Text="{Binding Name}" />

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

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

            <Slider Value="{Binding Blue}" />
            <Label>
                <Label.Text>
                    <Binding Path="Blue"
                             StringFormat="Blue = {0:X2}"
                             Converter="{StaticResource doubleToInt}">
                        <Binding.ConverterParameter>
                            <x:Double>255</x:Double>
                        </Binding.ConverterParameter>
                    </Binding>
                </Label.Text>
            </Label>
        </StackLayout>
    </StackLayout>
</ContentPage>    

RedGreen 属性的值是使用 Binding 标记扩展显示的。The values of the Red and Green properties are displayed with a Binding markup extension. 但是,Blue 属性实例化 Binding 以演示如何将显式 double 值设置为 ConverterParameter 属性。The Blue property, however, instantiates the Binding class to demonstrate how an explicit double value can be set to ConverterParameter property.

结果如下:Here's the result:

RGB 颜色选择器RGB Color Selector