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);