wpf致力于将业务逻辑层处于核心地位,展示层永远处于逻辑层的从属位置 wpf的这种能力源于 DataBinding 、与之配套的Dependency Property系统和DataTemplate Target <=> Binding <=> Source Binding的源:也就是数据的源头,只要求是个对象并通过属性公开自己的数据,它就能作为Binding源 例如: 1.把控件自己或者自己的容器或子级元素作为源 2.用一个控件作为另一个控件的源 3.把集合作为ItemsControl的数据源 4.使用XML作为TreeView或Menu的数据源 5.把多个控件关联到一个“数据制高点”上,甚至不给Binding指定数据源让它自己去找。 控制Binding的方向和数据的更新 控制Binding数据流向的属性是Model,它的类型是BindingModel BindingModel枚举 TwoWay OneWay OnTime OneWayToSource Default 根据实际情况来定,可编辑的双向,只读的单向 Binding数据更新的属性是 UpdateSourceTrigger枚举 PropertyChanged LostFocus Explicit Default 另Binding还有一个属性NotifyOnSourceUpdated、NotifyOnTargetUpdated两个bool属性 如果设为true,当源或目标更新时会激发相应的SourceUpdated事件和TargetUpdated事件 Binding的路径Path 来关注源中哪个属性的值,由path来指定,可以在Binding的构造器来指定,Path的实际类型是PropertyPath 我们知道用一个控件可以作为另一个控件的源 Path可以用来关注属性,而属性又分为无参属性、有参属性(索引器) 针对于有参属性 例如:textBox2显示textBox1的第四个文本字符。 <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5" /> <TextBox x:Name="textBox2" Text="{Binding Path=Text[3],ElementName=textBox1,Mode=OneWay}" Margin="5" BorderBrush="Black"/> 当一个集合或DataView作为源时,如果想默认的元素当做Path使用,需要这样的语法 List<string> list=new List<string>(){"Tom","Tim","Blog"}; this.textBox1.SetBinding(TextBox.TextProperty,new Binding("/"){ Source=list}); //Tom this.textBox2.SetBinding(TextBox.TextProperty,new Binding("/Length"){ Source=list}); //3 this.textBox3.SetBinding(TextBox.TextProperty,new Binding("/[2]"){ Source=list}); //m 如果想把子集合中的元素当做Path,可以使用多级斜线的语法 没有Path的Binding 实例本身就是数据源,不需要Path来指明,例如string int等基本类型就是,实例本身就是数据,无法通过哪个属性来访问数据 例如: <StackPanel> <StackPanel.Resource> <sys:String x:Key="myString"> 实例本身就是数据源 </sys:String> </StackPanel.Resource> <TextBlock x:Name="textBox1" Text="{Binding Path=.,Source={ StatciResource ResourceKey=myString}}" /> </StackPanel> Path可以省略掉 为Binding指定Path的几种方法 1、普通CLR类型单个对象指定为Source:包括FCL类型和用户自定义类型的对象,如果该类型实现了INotifyPropertyChanged接口, 则可通过在属性的set语句里激发PropertyChanged事件来通知Binding数据已被更新。 2、数组、List<T>、ObservableCollection<T>等集合类型,一般是把控件的ItemsSource属性使用Binding关联到一个集合对象上。 3、ADO.Net的对象DataTable和DataView对象 4、XmlDataProvider把XML指定为Source ,如TreeView、Menu 上上节提到的x:XData指令元素 5、把依赖对象指定为Source 6、把容器的DataContext设为Source,只设置Path,不设置Source,让这个Binding自己去找Source(会沿着控件树一层一层向外找,直到找到带有path指定属性的对象为止) 7、通过ElementName指定Source 8、通过Binding的RelativeSource属性相对的指定Source:当控件想关注自己的、自己容器的或者自己内部元素的某个值时采用这种办法。 9、把ObjectDataProvider对象指定为Source:当数据源不是通过属性而是通过方法暴漏给外界的时候 10、使用Linq检索的数据对象作为Binding的源。 一一介绍: 6、没有Source的Binding——使用DataContext作为Binding的源 Binding就会自动向UI元素树的上层去寻找可用的DataContext 如下代码: <StackPanel> <StackPanel.DataContext> <local:Teacher Name="hailiang" Age="25" Sex="男" /> </StackPanel.DataContext> <Grid> <StackPanel> <TextBlock Text="{Binding Path=Name}" Margin="5"/> <TextBlock Text="{Binding Path=Age}" Margin="5"/> <TextBlock Text="{Binding Path=Sex}" Margin="5"/> </StackPanel> </Grid> </StackPanel> 当实例本身就是数据源时,Path=.,可以没有path,如果某个DataContext是个简单类型对象时,也可以不指定Source 如下代码: <Grid> <Grid.DataContext> <sys:String >Hello DataContext!!</sys:String> </Grid.DataContext> <StackPanel> <TextBlock Text="{Binding}" Margin="5"/> </StackPanel> </Grid> Binding沿着UI元素向上找,其实Binding没有那么智能,只是DataContext是一个依赖属性 这里依赖属性很重要的一个特点是: 当你没有为控件的某个依赖属性显示赋值时,控件会把自己容器的属性值借过来当做自己的属性值 实际工作中DataContext的用法非常灵活: (1)、当UI上的多个控件使用Binding关注同一个对象时,可以使用DataContext。 (2)、当作为Source的对象不能被直接访问时——比如B窗体的控件想把A窗体的控件当做自己的Binding源时,但A窗体的控件是private访问级别,这时候就可以 把这个控件(控件的值)作为A窗体的DataContext(这个属性石public访问级别)从而暴漏出来 2、使用集合对象作为列表控件的ItemsSource 使用集合类型作为列表控件的ItemsSource时一般会考虑用ObserableCollection<T>代替List<T>,因为其实现了INotifyCollectionChanged和INotifyPropertyChanged接口 能把集合的变化立刻显出来。 3、使用ADO.NET做为Binding的源 一般是 DataTable dt=Bussiness.GetData(); listBox1.ItemsSource=dt.DefaultView; 这里的DefaultView是个DataView类型的对象。DataView实现了IEnumerable接口,所以可以给ItemsSource赋值。 DataTable不能直接拿来为ItemsSource赋值 但是可以将DataTable对象放到一个对象的DataContext属性里,并把ItemsSource与一个既没有Path有没有Source的Binding关联起来,Binding能自动找到DefaultView并 当做自己的Source 代码: DataTable dt=this.Load(); this.listViewStudents.DataContext=dt; this.listViewStudents.SetBinding(ListView.ItemsSourceProperty,new Binding()); GridView可以为GridViewColumn设置HeaderTemplate和CellTemplate,他们的类型都是DataTemplate。 4、使用Xml数据作为Binding源 迄今为止 .net framework提供了2套处理xml数据的类库 1、符合DOM标准的类库:包括XmlDocument、XmlElement、XmlNode、XmlAttribute 2、Linq:包括XDocument、XElement、XNode、XAttribute 注意:当使用Xml作为Binding的Source时,这里讲使用XPath,而非Path XPath=@Id 这里使用@符号表示的是XML元素的Attribute 把XML数据和XmlDataProvider对象直接写在XAML代码里,那么XML数据要放在<x:XData>...</x:XData>中。代码中用到HierarchicalDataTemplate 代码如下: <Window.Resources> <XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder"> <x:XData> <FileSystem xmlns=""> <Folder Name="Books"> <Folder Name="C#"> <Folder Name="CLR via C#" /> <Folder Name="C#本质论"/> </Folder> <Folder Name=".NET"> <Folder Name="Silverlight" /> <Folder Name="WPF" /> </Folder> <Folder Name="DataBase"> <Folder Name="SQL Server 2008" /> <Folder Name="MySql"/> <Folder Name="Oracle"/> </Folder> </Folder> </FileSystem> </x:XData> </XmlDataProvider> </Window.Resources> <StackPanel> <TreeView ItemsSource="{Binding Source={StaticResource ResourceKey=xdp}}"> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}"> <TextBlock Text="{Binding XPath=@Name}"/> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </StackPanel> 10、使用Linq作为Binding的源 Linq的查询结果是个IEnumberable<T>类型的对象 可以作为列表控件的ItemsSource来使用 9、使用ObjectDataProvider作为Binding的Source 把对象作为数据源提供给Binding ObjectDataProvider作用是用来包装一个以方法暴露数据的对象 代码: ObjectDataProvider odp = new ObjectDataProvider(); odp.ObjectInstance = new Calculator(); odp.MethodName = "Add"; odp.MethodParameters.Add("0"); odp.MethodParameters.Add("0"); Binding bindingToArg1 = new Binding("MethodParameters[0]") { Source = odp, //告诉Binding只负责把从UI元素收集到的数据写入Source(ObjectDataProvider对象) BindsDirectlyToSource=true, //一有更新立即传回Source UpdateSourceTrigger=UpdateSourceTrigger.PropertyChanged }; this.txtNum1.SetBinding(TextBox.TextProperty, bindingToArg1); txtNum2.SetBinding(TextBox.TextProperty, new Binding("MethodParameters[1]") { Source = odp, BindsDirectlyToSource = true, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }); txtResult.SetBinding(TextBox.TextProperty, new Binding(".") { Source = odp }); 8、使用Binding的RelativeSource 略 Binding对数据的转换与校验 Binding在Target与Source之间起着桥梁的作用,但是桥上也有关卡,用来 数据转换盒数据校验 数据校验:Binding的ValidationRules属性 继承抽象类:ValidationRule,重写override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)该方法 数据转换:Binding的Converter属性 实现IValueConverter接口 public infterface IValueConverter { //当数据从Binding的Source流向Target时Converter被调用 /** *参数1:未转型之前的值 *参数2:确定方法的返回类型,outputType *参数3:把额外的信息传入方法 *另外Binding的Model属性会影响这2个方法的调用 */ object Converter(object value,Type targetType,object parameter,CultureInfo culture); //当数据从Binding的Target流向Source时ConverterBack被调用 object ConvertBack(object value,Type targetType,object parameter,CultureInfo culture); } MultiBinding 与Binding一样都是以BindingBase为基类 其有个属性Bindings类型为Collection<BindingBase> 例如,注册用户时有重复输入用户名或密码,来控制提交按钮的启用与禁用 Converter: class LogonMultiBindingConverter:IMultiValueConverter //实现的是IMultiBindingConverter接口 { public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { //throw new NotImplementedException(); if (!values.Cast<string>().Any(text=>string.IsNullOrEmpty(text))&& values[0].ToString()==values[1].ToString()&& values[2].ToString()==values[3].ToString() ) { return true; } return false; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } 后台代码: Binding b1 = new Binding("Text") { Source=txt1 }; Binding b2 = new Binding("Text") { Source = txt2 }; Binding b3 = new Binding("Text") { Source = txt3 }; Binding b4 = new Binding("Text") { Source = txt4 }; MultiBinding bindings = new MultiBinding() { Mode=BindingMode.OneWay}; //MultiBinding对添加子级Binding的顺序是敏感的 bindings.Bindings.Add(b1); bindings.Bindings.Add(b2); bindings.Bindings.Add(b3); bindings.Bindings.Add(b4); bindings.Converter = new LogonMultiBindingConverter(); btnSubmit.SetBinding(Button.IsEnabledProperty, bindings);