《深入浅出WPF》笔记——绑定篇(二)
一、Binding对数据的校验与转化
在上一篇中有提到过绑定像是一座桥梁,在桥梁两端要有源和目标以及在桥梁上面我们可以设立关卡对数据进行验证,除此之外,源提供的数据有时不一定是目标想要的类型,但是可以通过转化成为目标需要的类型。
1.1Binding的数据验证
在软件设计过程中,数据的验证是经常要实现的。要实现Binding的数据验证,主要通过Binding的ValidationRoles属性来实现。下面让我们认识一下ValidationRoles(验证条件):可以看到ValidationRoles是复数形式,应该可以想到他是一个Collection<ValidationRole>类型的的属性,而ValidationRole是一个抽象类,所以我们要向验证条件集合里面添加的应该是继承自ValidationRole的一个实例,既然要继承抽象类,那么就要实现Validate方法,其形式为public abstract ValidationResult Validate(object value, CultureInfo cultureInfo),其中Value是要验证的值,cultureInfo暂不用理会,方法的返回值为ValidationResult类型的,Validate具有两个形参(一个是否通过验证,一个是错误信息)。为什么验证条件要用集合类型的呢?这是因为在一个绑定中可以有一个源,每一个源可以有很多属性,而且一个绑定可以对应多个目标。所以就可能有多个验证(由于上面文字涉及的变量比较多,建议在VS上面转到定义上,好好理解一下)。我们暂且还拿TextBox文本框与Slider控件的相互绑定为例吧!现在的需求是想让用户在滑动Slider和填写TextBox时,验证滑动范围和填写数字范围在0-100之间,如果不是在这个范围里,就提示输入数字不合理,且文本框的边框显示红色。
A、实现Validate方法代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Controls; namespace CommonLib { public class RangeValidationRule : ValidationRule { public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { double d = 0; if (double.TryParse(value.ToString(), out d)) { if (d >= 0 && d <= 80) { return new ValidationResult(true, null); } } return new ValidationResult(false,"输入数字不合理!!"); } }
B、XAML代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<Window x:Class="BindingOfValid.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="200" Width="300"> <StackPanel> <TextBox x:Name="textbox1" Margin="5"></TextBox> <Slider x:Name="slider1" Minimum="-10" Maximum="110"></Slider> </StackPanel> </Window>
C、cs代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; 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.Navigation; using System.Windows.Shapes; using CommonLib; namespace BindingOfValid { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); //新建绑定实例,指定源和路径 Binding binding = new Binding("Value"){Source=this.slider1}; //设置更新绑定源的方式 binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; //创建验证条件 RangeValidationRule rvr = new RangeValidationRule(); //默认数据源的数据都是正确的,如果加上下面的验证目标更新,则也会验证源的数据是否合法 rvr.ValidatesOnTargetUpdated = true; //向ValidationRules添加验证条件 binding.ValidationRules.Add(rvr); //设置通知错误的提示,我们可以把它想象为报警器,它会沿着目标,传到安装处理报警的地方 binding.NotifyOnValidationError = true; binding.NotifyOnTargetUpdated = true; //添加对源和目标的更新的监视,设置ToolTip为空 this.TargetUpdated += new EventHandler<DataTransferEventArgs>(MainWindow_TargetUpdated); this.SourceUpdated += new EventHandler<DataTransferEventArgs>(MainWindow_SourceUpdated); this.textbox1.SetBinding(TextBox.TextProperty, binding); //接到报警后,会处理报警(通过路由事件) this.textbox1.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(this.ValidError)); } void MainWindow_SourceUpdated(object sender, DataTransferEventArgs e) { (sender as MainWindow).textbox1.ToolTip = ""; } void MainWindow_TargetUpdated(object sender, DataTransferEventArgs e) { (sender as MainWindow).textbox1.ToolTip = ""; } void ValidError(object sender, RoutedEventArgs e) { if (Validation.GetErrors(this.textbox1).Count > 0) { this.textbox1.ToolTip = Validation.GetErrors(this.textbox1)[0].ErrorContent.ToString(); e.Handled = true; } } } }
效果图如图1:
图1
本段代码重在理解Binding的数据校验,还有很多细节要进行优化。再总结一下实现过程:定义一个验证规则和Binding,把验证规则添加到绑定的验证规则集里面,开启Binding的报警器(NotifyOnValidationError),为指定的UI控件添加路由事件来做出相应的反应。
1.2 Binding的数据转化
在XAML的语法的记录中有出现过转化(TypeConverter),主要是实现XAML标签的Attribute与对象的Property进行映射。今天要记录的是绑定的数据的转化,在这里可以把Binding比作一笔交易,买家想要某种东西,但是卖家给的却不是成型的产品,所以需要去加工才能成为买家最终想要的产品。 反过来说,买家有的时间不给卖家钱,而是给的黄金或其他有价值的东西,那么如果卖家用钱的话,他要去转化成现金。同样在使用Binding时,这样的例子也是屡见不鲜了,最明显的我们要把字符串类型的性别转化成布尔型的性别然后绑定到指定的CheckBox上面或者是其他控件上面。下面举个例子来说明一下,把飞机型号转化成图片路径显示出来,并且可以安排飞机的位置,("OnAir"表示在空中,"OnLand"表示在陆上,"Unknow"表示位置保密),可能我画的飞机不像,但重在说明问题。
A、先定义一个飞机类:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Chapter_04 { public class Plane { public string Category { get; set; } public string Area { get; set; } public string State { get; set; } } }
B、写两个转化类PlaneConVerter和StateOfPlane,并且都实现IValueConverter这个接口,写代码的时间可以直接在IValueConverter上面右击,实现接口,要实现的方法就能搞定了,剩余的就是一些算法。代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Data; namespace Chapter_04 { //TypeConverter public class PlaneConVerter:IValueConverter { //单向绑定 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return @"\Icons\" + value.ToString() + ".png"; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } public class StateOfPlane : IValueConverter { //双向绑定 //源向目标的转化 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { switch (value.ToString()) { case "OnAir": return true; case "OnLand": return false; case "Unknow": default: return null; } } //目标向源的转化 public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { bool? s = (bool?)value; switch (s) { case true : return "OnAir"; case false : return "OnLand"; case null : default: return "Unknow"; } } } }
C、前后台代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<Window x:Class="Chapter_04.ConvertInBinding" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Chapter_04" Title="ConvertInBinding" Height="250" Width="300"> <Window.Resources> <local:PlaneConVerter x:Key="pcv"/> <local:StateOfPlane x:Key="sop"/> </Window.Resources> <StackPanel> <ListBox x:Name="listBox" Height="160"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Background="Beige" Margin="5"> <Image Width="25" Height="30" Source="{Binding Path=Category,Converter={StaticResource pcv}}"/> <TextBlock Text="{Binding Path=Area}" Width="60" Height="30"/> <CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource sop}}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button Content="load" Click="Button_Click" /> <Button Content="SaveInfo" Click="Button_Click_1"/> </StackPanel> </Window>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; 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; using System.Collections.ObjectModel; using System.IO; namespace Chapter_04 { /// <summary> /// ConvertInBinding.xaml 的交互逻辑 /// </summary> public partial class ConvertInBinding : Window { public ConvertInBinding() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { ObservableCollection<Plane> planeList = new ObservableCollection<Plane>() { new Plane{Category="歼7B",Area="济南军区",State="Unknow"}, new Plane{Category="歼8F",Area="兰州军区",State="Unknow"}, new Plane{Category="歼8F",Area="成都军区",State="Unknow"}, new Plane{Category="歼7B",Area="南京军区",State="Unknow"}, }; this.listBox.ItemsSource = planeList; } private void Button_Click_1(object sender, RoutedEventArgs e) { StringBuilder sb = new StringBuilder(); foreach (Plane p in listBox.Items) { sb.AppendLine(string.Format("Category={0},Area={1},State={2}",p.Category,p.Area,p.State)); File.WriteAllText(@"E:\WPFCode\Chapter_04\PlaneList.txt", sb.ToString()); } } } }
最后实现的结果为图2:
图2
其中load按钮时实现装载数据,SaveInfo按钮是保存飞机安排的情况。下面解释一下上面的例子的重点代码,IValueConverter接口有两个方法,如果是实现源到目标的单向绑定的转化话,直接实现Convert(object value, Type targetType, object parameter, CultureInfo culture)方法;如果是要实现双向绑定的转化,两个方法都要重写,所以还要实现它ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)方法。Converter是绑定的一个属性,所以在<CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource sop}}"/>里面直接为Converter赋值显得很自然了。
二、多路Binding(MultiBinding)
多路Binding主要用于:一个UI需要显示的信息不止一个数据来决定,MultiBinding有一个属性为Bindings,就是用来添加Binding的。同时,多路Binding也有Converter属性。但实现的是IMultiValueConverter接口,方法不一样的地方就是Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)里面的第一个参数是复数形式,分别是Binding的源的属性值。还是通过例子来说明多路绑定的应用吧。需求是一个按钮是否可用取决于两个文本框的值是否一样。代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<Window x:Class="MultiBindingOfbind.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="139" Width="383"> <StackPanel Orientation="Vertical" Height="102"> <TextBox Height="23" HorizontalAlignment="Left" Margin="5" Name="textBox1" VerticalAlignment="Top" Width="350" /> <TextBox Height="23" HorizontalAlignment="Left" Margin="5" Name="textBox2" VerticalAlignment="Top" Width="350" /> <Button Content="Submit" Height="23" IsEnabled="False" HorizontalAlignment="Left" Name="button1" VerticalAlignment="Top" Width="75" /> </StackPanel> </Window>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; 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.Navigation; using System.Windows.Shapes; namespace MultiBindingOfbind { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.SetMultiBinding(); } private void SetMultiBinding() { Binding bind1 = new Binding("Text") {Source = this.textBox1 }; Binding bind2 = new Binding("Text") { Source = this.textBox2 }; MultiBinding mb = new MultiBinding() {Mode=BindingMode.OneWay }; mb.Bindings.Add(bind1); mb.Bindings.Add(bind2); mb.Converter = new LogonMutilBindingConvert(); this.button1.SetBinding(Button.IsEnabledProperty, mb); } private void buttonclick(object sender, RoutedEventArgs e) { MessageBox.Show((e.Source as FrameworkElement).Name); MessageBox.Show((e.OriginalSource as FrameworkElement).Name); } } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Data; namespace MultiBindingOfbind { public class LogonMutilBindingConvert:IMultiValueConverter { object IMultiValueConverter.Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { 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(); } } }
效果图如图3
图3
三、总结
绑定的总结,深入浅出上面的图最可以总结了,所以我就引用一下了。
图4
下一篇《深入浅出WPF》笔记——属性篇