绑定值转换器
您现在知道如何使用StringFormat将任何绑定源对象转换为字符串。但是其他数据转换呢?也许您正在使用Slider作为绑定源,但目标是期望整数而不是双精度。或者您可能希望将Switch的值显示为文本,但您想要“是”和“否”而不是“真”和“假”。
这个工作的工具是一个类 - 通常是一个非常小的类 - 非正式地称为值转换器或(有时)绑定转换器。更正式地说,这样的类实现了IValueConverter接口。此接口在Xamarin.Forms命名空间中定义,但它类似于Microsoft基于XAML的环境中提供的接口。
示例:有时应用程序需要根据条目中文本的存在启用或禁用Button。也许按钮标记为保存,条目是文件名。或者按钮标记为发送,条目包含邮件收件人。除非条目包含至少一个文本字符,否则不应启用Button。
有几种方法可以完成这项工作。在后面的章节中,您将看到数据触发器如何执行它(并且还可以对条目中的文本执行有效性检查)。但是对于本章,让我们用一个值转换器来做。
数据绑定目标是Button的IsEnabled属性。该属性属于bool类型。绑定源是Entry的Text属性,或者更确切地说是Text属性的Length属性。该Length属性的类型为int。值转换器需要将int等于0转换为bool为false,将int转换为bool为true。代码很简单。我们只需要将它包装在一个实现IValueConverter的类中。
这是Xamarin.FormsBook.Toolkit库中的该类,包含using指令。 IValueConverter接口由两个方法组成,名为Convert和ConvertBack,具有相同的参数。您可以根据需要使类成为通用类或专用类:
using System;
using System.Globalization;
using Xamarin.Forms;
namespace Xamarin.FormsBook.Toolkit
{
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;
}
}
}
当您在数据绑定中包含此类时 - 您将很快看到如何执行此操作 - 只要值从源传递到目标,就会调用Convert方法。
Convert的value参数是要转换的数据绑定源的值。您可以使用GetType来确定其类型,或者您可以假设它始终是特定类型。在此示例中,假定value参数的类型为int,因此转换为int不会引发异常。更复杂的值转换器可以执行更多有效性检查。
targetType是数据绑定目标属性的类型。多功能值转换器可以使用此参数来定制不同目标类型的转换。 Convert方法应返回与此targetType匹配的对象或值。这个特殊的Convert方法假设targetType是bool。
参数参数是一个可选的转换参数,您可以将其指定为Binding类的属性。 (您将在第18章“MVVM”中看到一个示例。)
最后,如果您需要执行特定于文化的转换,则最后一个参数是您应该使用的CultureInfo对象。
此特定Convert方法的主体假定value为int,并且该方法返回bool,如果该整数非零,则为true。
ConvertBack方法仅针对TwoWay或OneWayToSource绑定调用。对于ConvertBack方法,value参数是target的值,targetType参数实际上是source属性的类型。如果您知道将永远不会调用ConvertBack方法,则可以简单地忽略所有参数并从中返回null或0。使用一些值转换器,实现ConvertBack主体实际上是不可能的,但有时它非常简单(如本例所示)。
在代码中使用值转换器时,将转换器的实例设置为Binding的Converter属性。您可以选择通过设置Binding的ConverterParameter属性将参数传递给值转换器。
如果绑定也具有StringFormat,则值转换器返回的值是格式化为字符串的值。
通常,在XAML文件中,您需要在Resources字典中实例化值转换器,然后使用StaticResource在Binding表达式中引用它。值转换器不应该保持状态,因此可以在多个绑定之间共享。
这是使用值转换器的ButtonEnabler程序:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="ButtonEnabler.ButtonEnablerPage"
Padding="10, 50, 10, 0">
<ContentPage.Resources>
<ResourceDictionary>
<toolkit:IntToBoolConverter x:Key="intToBool" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Spacing="20">
<Entry x:Name="entry"
Text=""
Placeholder="text to enable button" />
<Button Text="Save or Send (or something)"
FontSize="Medium"
HorizontalOptions="Center"
IsEnabled="{Binding Source={x:Reference entry},
Path=Text.Length,
Converter={StaticResource intToBool}}" />
</StackLayout>
</ContentPage>
IntToBoolConverter在Resources字典中实例化,并作为Button的IsEnabled属性上设置的Binding中的嵌套标记扩展引用。
请注意,Text属性在Entry标记中显式初始化为空字符串。 默认情况下,Text属性为null,这意味着Text.Length的绑定Path设置不会产生有效值。
您可能还记得以前的章节中,仅在XAML中引用的Xamarin.FormsBook.Toolkit库中的类不足以建立从应用程序到库的链接。 因此,ButtonEnabler中的App构造函数调用Toolkit.Init:
public class App : Application
{
public App()
{
Xamarin.FormsBook.Toolkit.Toolkit.Init();
MainPage = new ButtonEnablerPage();
}
__
}
本章中使用Xamarin.Forms Book.Toolkit库的所有程序中都会出现类似的代码。
屏幕截图确认除非条目包含一些文本,否则不会启用Button:
如果您只使用值转换器的一个实例,则无需将其存储在“资源”字典中。 您可以在Binding标记中实例化它,使用target属性的property-element标签和Binding的Converter属性:
<Button Text="Save or Send (or something)"
FontSize="Large"
HorizontalOptions="Center">
<Button.IsEnabled>
<Binding Source="{x:Reference entry}"
Path="Text.Length">
<Binding.Converter>
<toolkit:IntToBoolConverter />
</Binding.Converter>
</Binding>
</Button.IsEnabled>
</Button>
有时,值转换器可以方便地定义几个简单的属性。 例如,假设您要为Switch的两个设置显示一些文本,但您不想使用“True”和“False”,并且您不希望将替代值硬编码到值转换器中。 这是一个BoolToStringConverter,它包含两个文本字符串的公共属性:
namespace Xamarin.FormsBook.Toolkit
{
public class BoolToStringConverter : IValueConverter
{
public string TrueText { set; get; }
public string FalseText { set; get; }
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (bool)value ? TrueText : FalseText;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return false;
}
}
}
Convert方法的主体是微不足道的:它只是根据布尔值参数在两个字符串之间进行选择。
类似的值转换器将布尔值转换为两种颜色之一:
namespace Xamarin.FormsBook.Toolkit
{
public class BoolToColorConverter : IValueConverter
{
public Color TrueColor { set; get; }
public Color FalseColor { set; get; }
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (bool)value ? TrueColor : FalseColor;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return false;
}
}
}
SwitchText程序为两个不同的字符串对实例化BoolToStringConverter转换器两次:一次在Resources字典中,然后在Binding.Converter属性元素标记内。 最终Label的两个属性受BoolToStringConverter和BoolToColorConverter的影响,它们基于Switch的相同IsToggled属性:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="SwitchText.SwitchTextPage"
Padding="10, 0">
<ContentPage.Resources>
<ResourceDictionary>
<toolkit:BoolToStringConverter x:Key="boolToString"
TrueText="Let's do it"
FalseText="Not now" />
<Style TargetType="Label">
<Setter Property="FontSize" Value="Medium" />
<Setter Property="VerticalOptions" Value="Center" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<!-- First Switch with text. -->
<StackLayout Orientation="Horizontal"
VerticalOptions="CenterAndExpand">
<Label Text="Learn more?" />
<Switch x:Name="switch1"
VerticalOptions="Center" />
<Label Text="{Binding Source={x:Reference switch1},
Path=IsToggled,
Converter={StaticResource boolToString}}"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<!-- Second Switch with text. -->
<StackLayout Orientation="Horizontal"
VerticalOptions="CenterAndExpand">
<Label Text="Subscribe?" />
<Switch x:Name="switch2"
VerticalOptions="Center" />
<Label Text="{Binding Source={x:Reference switch2},
Path=IsToggled,
Converter={StaticResource boolToString}}"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<!-- Third Switch with text and color. -->
<StackLayout Orientation="Horizontal"
VerticalOptions="CenterAndExpand">
<Label Text="Leave page?" />
<Switch x:Name="switch3"
VerticalOptions="Center" />
<Label HorizontalOptions="FillAndExpand">
<Label.Text>
<Binding Source="{x:Reference switch3}"
Path="IsToggled">
<Binding.Converter>
<toolkit:BoolToStringConverter TrueText="Yes"
FalseText="No" />
</Binding.Converter>
</Binding>
</Label.Text>
<Label.TextColor>
<Binding Source="{x:Reference switch3}"
Path="IsToggled">
<Binding.Converter>
<toolkit:BoolToColorConverter TrueColor="Green"
FalseColor="Red" />
</Binding.Converter>
</Binding>
</Label.TextColor>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>
使用两个相当简单的绑定转换器,Switch现在可以显示两种状态所需的任何文本,并可以使用自定义颜色为该文本着色:
既然您已经看过BoolToStringConverter和BoolToColorConverter,您能否将该技术推广到任何类型的对象? 这是Xamarin.FormsBook.Toolkit库中的通用BoolToObjectConverter:
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 ? this.TrueObject : this.FalseObject;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return ((T)value).Equals(this.TrueObject);
}
}
下一个示例使用此类。