Binding的作用就是架在Source和Target之间的桥梁,数据可以在这座桥梁的帮助下来流通。就像现实社会中桥梁需要设置安检和关卡一样,Binding这座桥上也可以设置关卡对数据进行验证,不仅如此,如果Binding两端需要不同的数据类型的时候我们还可以为数据设置转换器。
Binding用于数据有效性校验的关卡是他的ValidationRules属性,用于数据类型转换的关卡是它的Convert属性。
Binding的ValidationRules属性是Collection<ValidationRule>,从它的名称和数据类型我们可以得知可以为每个Binding设置多个数据校验条件,每一个条件是一个ValidationRule对象。ValidationRule是一个抽象类,在使用的时候我们需要创建它的派生类并实现它的Validate方法的返回值是ValidationResult类型对象,如果通过验证,就把ValidationResult对象的IsValidate属性设为true,反之,则需要将IsValidate设置为false并为其ErrorContent属性设置一个合适的消息内容(一般是字符串)。
下面这个程序的UI绘制一个TextBox和一个Slider,然后在后台C#代码中建立Binding把它们关联起来---- 以Slider为源,TextBox为目标。Slider的取值范围是0~100,也就是说我们需要验证TextBox中输入的值是不是在0~100之间。
XAML:
<span style="font-family:Microsoft YaHei;font-size:14px;"><Window x:Class="WpfApplication6.wnd641" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="wnd641" Height="200" Width="300"> <StackPanel Background="LightSlateGray"> <!--大小写敏感--> <!--Text="{Binding Path=Value, ElementName=_slider , UpdateSourceTrigger=PropertyChanged}"--> <TextBox x:Name="_txtBox" Margin="5" /> <Slider x:Name="_slider" Minimum="0" Maximum="100" Margin="5" /> </StackPanel> </Window></span>
<span style="font-family:Microsoft YaHei;font-size:14px;"> // 数据校验 public class RangValidationRule:ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { double d = 0; if(double.TryParse(value.ToString(), out d)) { if(d >=0 && d <= 100) { return(new ValidationResult(true, null)); } } return(new ValidationResult(false, "Validation Error")); } } </span>
<span style="font-family:Microsoft YaHei;font-size:14px;"> /// <summary> /// wnd641.xaml 的交互逻辑 /// </summary> public partial class wnd641 : Window { public wnd641() { InitializeComponent(); Binding binding = new Binding("Value") { Source = _slider, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, }; RangValidationRule val = new RangValidationRule(); // 目标更新时是否运行验证规则,默认来自Source的是不校验的 val.ValidatesOnTargetUpdated = true; binding.ValidationRules.Add(val); binding.NotifyOnValidationError = true; _txtBox.SetBinding(TextBox.TextProperty, binding); // 添加错误提示的路由事件 _txtBox.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(ValidationError)); } void ValidationError(object sender, RoutedEventArgs e) { if(Validation.GetErrors(_txtBox).Count > 0) { _txtBox.ToolTip = Validation.GetErrors(_txtBox)[0].ErrorContent.ToString(); } } } </span>
完成后运行程序,当输入0~100之间的值的时候程序正常显示,但是输入区间之外的值的时候TextBox会显示为红色边框,表示值是错误的,不能传值给Source。
Binding还有另外一种机制称为数据转换,当Source端指定的Path属性值和Target端指定的目标属性不一致的时候,我们可以添加数据转换器(DataConvert)。上面我们提到的问题实际上就是double和stirng类型相互转换的问题,因为处理起来比较简单,所以WPF类库就自己帮我们做了,但有些数据类型转换就不是WPF能帮我们做的了,当遇到这些情况,我们只能自己动手写Converter,方法是创建一个类并让这个类实现IValueConverter接口。
当数据从Binding的Source流向Target的时候,Convert方法将被调用;反之ConvertBack将被调用。这两个方法的参数列表一模一样:第一个参数为Object。最大限度的保证了Convert的重要性。第二个参数用于确定返回参数的返回类型。第三个参数为了将额外的参数传入方法,若需要传递多个信息,则需要将信息做为一个集合传入即可。
Binding对象的Mode属性将影响这两个方法的调用;如果Mode为TwoWay或Default行为与TwoWay一致则两个方法都有可能被调用。如果Mode是OneWay或者Default行为与OneWay一致则只有Convert方法会被调用。其它情况同理。
下面这个例子是一个Converter的综合实例,程序的用途是向玩家显示一些军用飞机的状态信息。
C#:
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace WpfApplication6 { public enum Category { Bomber, Fighter } public class Plane { public Category Category { get; set; } public string Name { get; set; } } /// <summary> /// 数据转换 /// </summary> public class CategoryToSourceCvt:IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { Category c = (Category)value; switch(c) { case Category.Bomber: return (@"E:\RefCode\C#\WPF\深入浅出WPF\第六章Binding\WpfApplication6\WpfApplication6\Bomber.png"); case Category.Fighter: return (@"E:\RefCode\C#\WPF\深入浅出WPF\第六章Binding\WpfApplication6\WpfApplication6\Fighter.png"); } return(null); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } /// <summary> /// wnd642.xaml 的交互逻辑 /// </summary> public partial class wnd642 : Window { public wnd642() { InitializeComponent(); List<Plane> _listPanel = new List<Plane>() { new Plane(){Category = Category.Fighter, Name= "F-1"}, new Plane(){Category = Category.Bomber, Name= "B-1"}, new Plane(){Category = Category.Fighter, Name= "F-2"}, }; _listBox.ItemsSource = _listPanel; } } }
<Window x:Class="WpfApplication6.wnd642" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication6" Title="wnd642" Height="200" Width="300"> <Window.Resources> <local:CategoryToSourceCvt x:Key="cts" /> </Window.Resources> <StackPanel> <ListBox x:Name="_listBox" Height="160" Margin="5"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <!--Converter使用静态资源--> <Image Source="{Binding Path=Category, Converter={StaticResource ResourceKey=cts}}" Width="20" Height="20"></Image> <TextBlock Text="{Binding Path=Name}" Width="60" Height="20" Margin="80, 0"></TextBlock> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel> </Window>
有时候UI需要显示的数据来源不止一个数据来源决定,这个时候就需要用到MultiBinding,即多路绑定。MultiBinding与Binding一样均以BindingBase为基类,也就是说,凡是能用Binding的场合都能使用MultiBinding。MutiBinding具有一个Bindings的属性,其类型是Connection<BindingBase>,通过这个属性,MultiBinding把一组Binding对象聚合起来,处在这个Binding结合中的对象可以拥有自己的数据校验和转换机制。它们汇集起来的数据将共同决定传往MultiBinding目标的数据。如下图:
考虑这样一个需求,有一个用于新用户注册的UI(2个TextBox和一个Button),还有如下一些限定:
TextBox用于显示输入的邮箱,要求数据必须一致。
当TextBox的内容全部符合要求的时候,Button可用。
XAML:
<Window x:Class="WpfApplication6.wnd65" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="wnd65" Height="200" Width="300"> <StackPanel> <TextBox x:Name="_txtBox1" Margin="5"></TextBox> <TextBox x:Name="_txtBox2" Margin="5"></TextBox> <Button Content="OK" x:Name="_btn" Margin="5"></Button> </StackPanel> </Window>
public class LogMulCvt:IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { // 元素强制转换为指定的string类型 if(!values.Cast<string>().Any((text) => string.IsNullOrEmpty(text)) && values[0].ToString() == values[1].ToString()) { return (true); } return (false); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } /// <summary> /// wnd65.xaml 的交互逻辑 /// </summary> public partial class wnd65 : Window { public wnd65() { InitializeComponent(); Binding binding1 = new Binding("Text") { Source = _txtBox1 }; Binding binding2 = new Binding("Text") { Source = _txtBox2 }; MultiBinding mulBinding = new MultiBinding(); mulBinding.Bindings.Add(binding1); mulBinding.Bindings.Add(binding2); mulBinding.Converter = new LogMulCvt(); _btn.SetBinding(Button.IsEnabledProperty, mulBinding); } }
注意:
MultiBinding对子元素的顺序非常敏感,因为这个数据决定了汇集到Convert里数据的顺序。
MultiBinding的Converter实现的是IMultiValueConverter。
WPF的核心理念是变传统的UI驱动数据变成数据驱动UI,支撑这个理念的基础就是本章讲的Data Binding和与之相关的数据校验和数据转换。在使用Binding的时候,最重要的就是设置它的源和路径。
参考《深入浅出WPF》