1. 前言
IValueConverter是用于数据绑定的强大的武器,它用于Value在Binding Source和Binding Target之间的转换。本文将介绍IValueConverter的用法及一些常用的实现。
2. 为什么要使用IValueConverter
假设有如下的类TestResult:
public class TestResult{ public bool Passed { get; set; }}
UI需要通过Passed这个属性决定显示结果的文字颜色为红色或绿色,一般初学者最常见的做法是修改TestResult类,添加一个和Passed相关的属性:
public class TestResult{ public bool Passed { get; set; } public Brush TestResultBrush { get { if (Passed) return new SolidColorBrush(Colors.Red); else return new SolidColorBrush(Colors.Green); } }}
然后在XAML上绑定到这个属性:
<TextBlock Text="Score : 60" Foreground="{Binding TestResultBrush}"/>
另一种做法是直接才Code Behind为TextBlock更改Foreground:
var testResult = DataContext as TestResult;if (testResult != null){ if (testResult.Passed) ResultElement.Foreground = new SolidColorBrush(Colors.Red); else ResultElement.Foreground = new SolidColorBrush(Colors.Green);}
两种做法都不够优雅,可以指出一大堆问题:破坏了TestResult的结构,违反了开放封闭原则,令UI和数据太过耦合,太多Hard Code等。
这种情况通常都可以使用IValueConverter处理。在Binding中,IValueConverter可以用于数据呈现前将它转换成新的目标值,实现IValueConverter需要执行以下步骤:
- 创建一个实现了IValueConverter接口的类类;
- 实现
public object Convert(object value, Type targetType, object parameter, string language)
方法,该方法将数据转换为新目标值; - 实现
public object ConvertBack(object value, Type targetType, object parameter, string language)
,该方法执行反向转换,只有使用双向绑定才需要实现这个方法。
在这个例子里,IValueConverter的目的是将bool类型的Passed转换成Brush,实现如下:
public class BoolToBrushConverter : IValueConverter{ public object Convert(object value, Type targetType, object parameter, string language) { if (value is bool passed) return new SolidColorBrush(passed ? Colors.Green : Colors.Red); return null; } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); }}
在XAML中使用这个Convnerter需要先将它定义为Resource,然后Binding中指定Converter到这个已定义的Resource:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.Resources> <local:BoolToBrushConverter x:Key="BoolToBrushConverter"/> </Grid.Resources> <TextBlock Text="Score : 60" Foreground="{Binding Passed,Converter={StaticResource BoolToBrushConverter}}"/></Grid>
3. BoolToValueConverter
在XAML漫长的历史里,IValueConverter也诞生了各种奇怪的技巧,其中最常用的是BoolToValueConverter。
public class BoolToValueConverter : IValueConverter{ public object Convert(object value, Type targetType, object parameter, string language) { if (value == null || (bool) value == false) return DependencyProperty.UnsetValue; return parameter; } public object ConvertBack(object value, Type targetType, object parameter, string language) { return Equals(value, parameter); }}
BoolToValueConverter灵活使用了Binding中ConverterParameter和FallbackValue两个参数,常常用于解决IValueConverter中HardCode的问题。在Binding中,FallbackValue指明了如果Binding没法返回任何值时使用的值,在IValueConverter中返回DependencyProperty.UnsetValue即告诉Binding要使用FallbackValue的值。
使用BoolToValueConverter解决了上述例子的Hard Code的问题,在XAML中使用如下:
<Grid> <Grid.Resources> <local:BoolToValueConverter x:Key="BoolToValueConverter"/> </Grid.Resources> <TextBlock Text="Score : 60" Foreground="{Binding Passed,Converter={StaticResource BoolToValueConverter},ConverterParameter=Green,FallbackValue=Red}"/></Grid>
4. BoolToObjectConverter
需要注意的是上面XAML中Green和Red都只是字符串,它们最终能被解析成SolidColorBrush是由于TypeConveter的支持,也就是说上述XAML语法只能用于TypeConverter支持的数据类型,而且这种写法还是太过HardCode。如果要支持复杂类型或者对应本地化等问题,可以将ConverterParameter和FallbackValue绑定到StaticResource :
<Grid.Resources> <SolidColorBrush Color="Green" x:Key="PassedBrush"/> <SolidColorBrush Color="Red" x:Key="FailedBrush"/> <local:BoolToValueConverter x:Key="BoolToValueConverter"/></Grid.Resources><TextBlock Text="Score : 60" Foreground="{Binding Passed,Converter={StaticResource BoolToValueConverter},ConverterParameter={StaticResource PassedBrush},FallbackValue={StaticResource FailedBrush}}"/>
虽然看上去是很灵活,但如果有大量返回同样值的BoolToValueConverter将会使XAML产生大量冗余。UWP Community Toolkit提供了一些常用的IValueConverter实现,其中最常用的是BoolToObjectConverter。BoolToObjectConverter和BoolToValueConverter功能类似,但它提供了public object TrueValue { get; set; }
和public object FalseValue { get; set; }
两个属性,而且这两个属性是依赖属性,可以使用绑定为其赋值。使用如下:
<Grid.Resources> <SolidColorBrush Color="Green" x:Key="PassedBrush"/> <SolidColorBrush Color="Red" x:Key="FailedBrush"/> <converters:BoolToObjectConverter x:Key="BoolToObjectConverter" TrueValue="{StaticResource PassedBrush}" FalseValue="{StaticResource FailedBrush}"/></Grid.Resources><TextBlock Text="Score : 60" Foreground="{Binding Passed,Converter={StaticResource BoolToObjectConverter}}"/>
5. BoolToVisibilityConverter
UWP Community Toolkit中提供了另一个常用的Converter:BoolToVisibilityConverter。这个Converter只是简单地继承了BoolToObjectConverter,并且为TrueValue和FalseValue设置了默认值:
public BoolToVisibilityConverter(){ TrueValue = Visibility.Visible; FalseValue = Visibility.Collapsed;}
BoolToVisibilityConverter虽然简单,但确实好用。不过从1607以后就不需要这个Converter了,微软是这样说的:
从 Windows 10 版本 1607 开始,XAML 框架向 Visibility 转换器提供内置布尔值。 转换器将 true 映射到 Visible 枚举值并将 false 映射到 Collapsed,以便你可以将 Visibility 属性绑定到布尔值,而无需创建转换器。 若要使用内置转换器,你的应用的最低目标 SDK 版本必须为 14393 或更高版本。
但有时候反而需要True对应Collapsed,于是现在是另一个常用Converter - BoolNegationConverter登上历史舞台的时候了:
<StackPanel > <StackPanel.Resources> <converters:BoolNegationConverter x:Key="BoolNegationConverter" /> </StackPanel.Resources> <TextBlock Text="Passed" Foreground="Green" Visibility="{Binding Passed}"/> <TextBlock Text="Failed" Foreground="Red" Visibility="{Binding Passed,Converter={StaticResource BoolNegationConverter}}"/></StackPanel>
6. StringFormatConverter
UWP的Binding缺少了StringFormat,这对Binding产生了很大影响,为弥补这个缺陷,可以使用UWP Community Toolkit中的StringFormatConverter。它的代码也十分简单(其实这才是ConverterParameter的正确用法):
public object Convert(object value, Type targetType, object parameter, string language){ if (value == null) { return null; } if (parameter == null) { return value; } return string.Format((string)parameter, value);}
在XAML中使用如下:
<TextBlock Text="{Binding DoubleValue,Converter={StaticResource StringFormatConverter},ConverterParameter='{}{0:N2}'}"/><TextBlock Text="{Binding DoubleValue,Converter={StaticResource StringFormatConverter},ConverterParameter='There are {0:N0} Items'}"/>
结果如下:
除了弥补StringFormat的功能,StringFormatConverter还有其它的应用场景。
** TestModel.CS **
public IEnumerable<ClickMode> ClickModes => new List<ClickMode> { ClickMode.Hover, ClickMode.Press, ClickMode.Release };
<ListBox ItemsSource="{Binding ClickModes}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding }" /> </DataTemplate> </ListBox.ItemTemplate></ListBox><ListBox ItemsSource="{Binding ClickModes}"/>
在WPF中,以上XAML都可以正常呈现,而在UWP中,以上XAML显示如下:
这种情况可以使用StringFormatConverter显示枚举的名称:
<ListBox ItemsSource="{Binding ClickModes}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Converter={StaticResource StringFormatConverter}}" /> </DataTemplate> </ListBox.ItemTemplate></ListBox>
可以说对UWP来说StringFormatConverter十分必要。
7. language参数
public object Convert(object value, Type targetType, object parameter, string language)
方法中的参数language通常用于本地化,例如可以创建一个DateTimeValueConverter:
public class DateTimeValueConverter : IValueConverter{ public object Convert(object value, Type targetType, object parameter, string language) { if (value is DateTime dateTime) { var culture = new CultureInfo(language); return dateTime.ToString(culture.DateTimeFormat); } return value; } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); }}
<TextBlock Text="{Binding Date,Converter={StaticResource DateTimeValueConverter},ConverterLanguage=en-US}"/><TextBlock Text="{Binding Date,Converter={StaticResource DateTimeValueConverter},ConverterLanguage=zh-CN}"/>
结果如下:
8. targetType参数
targetType参数指转换后的目标类型,使用这个参数可以实现一个简单的Value Converter:
public class ValueConverter : IValueConverter{ public object Convert(object value, Type targetType, object parameter, string language) { return System.Convert.ChangeType(value, targetType); } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); }}
虽然代码简单,但它可以解决不少问题,例如 了解TypeConverter 这篇文章里提到的不能在XAML中使用decimal的问题。IValueConverter要起作用依赖于BindingSource,而在XAML中虽然很多东西都可以用来做BindingSource,例如用元素自己的Tag:
<local:MyContentControl Tag="10.01" Amount="{Binding Converter={StaticResource ValueConverter},Path=Tag,RelativeSource={RelativeSource Mode=Self}}"/>
或者Resources中的字符串:
<Grid.Resources> <x:String x:Key="DecimalString">10.01</x:String></Grid.Resources><local:MyContentControl Amount="{Binding Source={StaticResource DecimalString},Converter={StaticResource ValueConverter}}"/>
或者更进一步写一个字符串的包装类:
public class StringWrapper{ public string this[string key] {
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。