[UWP]了解IValueConverter
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]
{
get
{
return key;
}
}
}
<local:MyContentControl Amount="{Binding [10.01],Source={StaticResource StringWrapper},Converter={StaticResource ValueConverter}}"/>
9. 使用IValueConverter的其它经验
9.1 统一管理IValueConverter
由于大部分IValueConverter行为是固定的,通常我都会把常用的IValueConverter放到一个Converters.xaml,然后在App.xaml中年合并资源字典,这样不用重复写创建Converter的xaml,也避免了重复创建Converter的资源消耗:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Converters.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
9.2 格式化
Binding最让人诟病的缺点就是它的语法太长太长太长,例如以上两个TextBlock,在IDE中很难判断这它们有什么不同。很多时候我都会把XAML的格式化设置成“将每个属性分行放置”,如下图:
这样上面两个TextBlock的XAML就清晰许多了:
不过这样设置也并不全是好处,怎么设置具体还是看个人喜好和屏幕尺寸。
10. 结语
虽然IValueConverter的文章已经不少了,但还是常常见到乱来的IValueConverter实现,而且UWP的IValueConverter有一些改变,所以还是写了这篇文章。
我很想写一些常用的,或者容易用错的基础知识,但连IValueConverter都不知不觉就写得这么长了,实在没勇气写Binding的概念,何况关于Binding 已经有很多很实用的文章。
我十分清楚文章写得太长就会被“保存到Pocket”,我也想每篇文章都能在三五分钟内看完,但偏偏越基础的概念就越能写得长,而且写得简短些又会被移出博客园首页,很难把握尺度。
下一篇文章会尽量写短一些。
11. 参考
IValueConverter Interface
Binding Class
深入了解数据绑定
Converters - UWP Community Toolkit _ Microsoft Docs
[UWP]了解IValueConverter的更多相关文章
- win10 uwp 如何使用DataTemplate
这是数据模板,一般用在数组的绑定,显示数组中的元素. 假如我们有一个列表,列表里是书,包括书名.作者.还有出版,那么我们只有源信息,如何把它显示到我们的ListView,就需要DataTemplate ...
- 2019-7-29-win10-uwp-如何使用DataTemplate
title author date CreateTime categories win10 uwp 如何使用DataTemplate lindexi 2019-7-29 10:2:32 +0800 2 ...
- UWP控件与DataBind
在uwp开发中必不可少的一个环节就是各种通用的控件的开发,所以在闲暇时间汇总了一下在uwp开发中控件的几种常用写法,以及属性的几种绑定方式,有可能不全面,请大家多多包涵 :) 1.先从win10新增的 ...
- Win10 UWP开发中的重复性静态UI绘制小技巧 1
介绍 在Windows 10 UWP界面实现的过程中,有时会遇到一些重复性的.静态的界面设计.比如:画许多等距的线条,画一圈时钟型的刻度线,同特别的策略排布元素,等等. 读者可能觉得这些需求十分简单, ...
- UWP/Win10新特性系列—UserConsentVerifier
在UWP开发中,微软提供了新的用户许可验证方式-指纹(生物识别).Pin.密码验证.在爆料的新型Win10 Mobile移动设备中,会增加虹膜识别等先进的用户身份识别技术,微软现在统一了身份验证的AP ...
- [uwp开发]数据绑定那些事(1)
现在是msp候选人,是时候写点技术博客来加分了(实则是个人的心得体会). 注:以下都是个人理解,错误在所难免,欢迎批评指正 以前接触过WPF,只会简单的一些操作,现在在逐渐学习UWP(Universa ...
- [UWP]了解模板化控件(2.1):理解ContentControl
UWP的UI主要由布局容器和内容控件(ContentControl)组成.布局容器是指Grid.StackPanel等继承自Panel,可以拥有多个子元素的类.与此相对,ContentControl则 ...
- UWP: 掌握编译型绑定 x:Bind
在 UWP 开发中,我们在进行数据绑定时,除了可以使用传统的绑定 Binding,也可以使用全新的 x:Bind,由于后者是在程序编译时进行初始化操作(不同于 Binding,它是在运行时创建.初始化 ...
- [UWP]用Shape做动画
相对于WPF/Silverlight,UWP的动画系统可以说有大幅提高,不过本文无意深入讨论这些动画API,本文将介绍使用Shape做一些进度.等待方面的动画,除此之外也会介绍一些相关技巧. 1. 使 ...
随机推荐
- struts2对于处理JSON的配置
由于最近几年日益流行前后端分离模式,JSON作为数据载体也变得不可或缺.几乎所有的web框架都需要支持JSON,下面咱就一起了解下struts2是如何支持JSON的. 对于JSON的发送 这里有两种方 ...
- 鼠标相关操作(Cursor类及相关API)
Cursor.visible:属性,显示或者隐藏鼠标. Cursor.lockState = CursorLockMode.Locked:锁定鼠标到游戏窗口的中心. (CursorLockMode: ...
- 2807:两倍-poj
2807:两倍 总时间限制: 1000ms 内存限制: 65536kB 描述 给定2到15个不同的正整数,你的任务是计算这些数里面有多少个数对满足:数对中一个数是另一个数的两倍. 比如给定1 4 ...
- C语言之二分猜数字游戏
#include <stdio.h>#include <windows.h>#include<string.h>int main() { int oldprice, ...
- Azure cli使用arm创建多网卡虚拟机
登录 Azure CLI 并使用 Resource Manager 模式: azure config mode arm 在以下示例中,请将示例参数名称替换为你自己的值.示例参数名称包括 myResou ...
- mysql 系统性浅聊 myisam 存储引擎【原创】
>>思维导图 >>介绍 mysql中的存储引擎都是以插件的形式存在,目前用的最多存储引擎就是innodb和myisam.MySQL5.5.5以后(包括5.5.5)默认使用Inn ...
- Android开发之漫漫长途 番外篇——内存泄漏分析与解决
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- CCF-201312-3-最大的矩形
问题描述 试题编号: 201312-3 试题名称: 最大的矩形 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 在横轴上放了n个相邻的矩形,每个矩形的宽度是1,而第i(1 ≤ ...
- lunx命令2
测试网络 ping 查看ip ifconfig 关闭防火墙 iptables -F 干掉nginx进程 killall nginx 切换用户 su linux下连接另一台linux服务区 ssh ro ...
- 解决zabbix中文显示乱码问题
中文显示问题,图表乱码 解决办法: [root@zabbix ~]# cd /usr/share/zabbix/include/ [root@zabbix include]# vim locales. ...