14、数据绑定相关例子
1、把普通CLR类型单个对象指定为Source:包括.NET Framework自带类型的对象和用户自定义类型的对象。
如果类型实现了INotifyPropertyChanged接口,则可通过在属性的set语句里激发PropertyChanged事件来通知Binding数据已被更新。
using System.ComponentModel; namespace _SampleDataBinding { class Person:INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _personName= string.Empty; public string PersonName { get { return this._personName; } set { this._personName = value; if (PropertyChanged != null) { PropertyChanged.Invoke(this,new PropertyChangedEventArgs(PersonName)); } } } } }
<StackPanel> <TextBox Name="TxtTest1" Text="{Binding Source={StaticResource MyPerson}, Path=PersonName}"></TextBox> <Button Name="BtnTest" Height="36" Click="BtnTest_OnClick">修改下试试</Button> </StackPanel>
private void BtnTest_OnClick(object sender, RoutedEventArgs e) { Person per=new Person(); per.PersonName = "我修改名字了,叫麦克"; }
2、把普通CLR集合类型对象指定为Source:包括数组、List<T>、ObservableCollection<T>等集合类型。
class Student { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
<StackPanel Background="LightBlue"> <TextBlock Text="学号:" FontWeight="Bold" Margin="5" FontSize="16"></TextBlock> <TextBox x:Name="TxtBoxId" Margin="5" Height="36"></TextBox> <TextBlock Text="学生集合:" FontSize="16" FontWeight="Bold" Margin="5"></TextBlock> <ListBox x:Name="ListBoxStudents" Height="195" Margin="5"> </ListBox> </StackPanel>
//准备数据源 List<Student> stuList =new List<Student> ( ) { new Student(){Id = 001,Name = "Apple",Age = 12}, new Student(){Id = 002,Name = "Banana",Age = 13}, new Student(){Id = 003,Name = "Cocoa",Age = 14}, new Student(){Id = 004,Name = "Dog",Age = 15}, new Student(){Id = 005,Name = "Func",Age = 16}, new Student(){Id = 006,Name = "Great",Age = 17} }; //为ListBox设置Binding this.ListBoxStudents.ItemsSource = stuList; this.ListBoxStudents.DisplayMemberPath = "Name"; //为TextBox设置Binding Binding binding=new Binding("SelectedItem.Id"){Source = this.ListBoxStudents}; this.TxtBoxId.SetBinding(TextBox.TextProperty, binding);
如果将绑定代码写在XAML中该怎么写呢?
<TextBox x:Name="TxtBoxId" Text="{Binding ElementName=ListBoxStudents,Path=SelectedItem.Id}"></TextBox>
在写Path的时候SelectedItem.Id,其中.Id是不提示的,需要硬写上去。
想试试直接在XAML中绑定,然后搞了半天没成功,请教了猥琐猫之后他给了个解决方案,下面是代码
public class Student { public int StudentNum { get; set; } public string StudentName { get; set; } public int StudentAge { get; set; } }
public class Data { private List<Student> _studentsList; public List<Student> StudentsList { get { return _studentsList; } set { _studentsList = value; } } }
{ public Data myData; private List<Student> studentsList; public MainWindow ( ) { InitializeComponent ( ); myData=new Data(); //准备数据源 studentsList =new List<Student> ( ) { new Student(){StudentNum = 001,StudentName = "Apple",StudentAge = 12}, new Student(){StudentNum = 002,StudentName = "Banana",StudentAge = 13}, new Student(){StudentNum = 003,StudentName = "Cocoa",StudentAge = 14}, new Student(){StudentNum = 004,StudentName = "Dog",StudentAge = 15}, new Student(){StudentNum = 005,StudentName = "Func",StudentAge = 16}, new Student(){StudentNum = 006,StudentName = "Great",StudentAge = 17} }; myData.StudentsList = studentsList; this.DataContext = myData; }
再写个例子试试
private List<Student> stuList; public MainWindow ( ) { InitializeComponent ( ); stuList=new List<Student>() { new Student(){StudentId = 001,StudentName = "张三",StudentAge = 12}, new Student(){StudentId = 002,StudentName = "李四",StudentAge = 13}, new Student(){StudentId = 003,StudentName = "王五",StudentAge = 14}, new Student(){StudentId = 004,StudentName = "赵六",StudentAge = 15}, new Student(){StudentId = 005,StudentName = "陈七",StudentAge = 16} }; this.listBoxStudents.ItemsSource = stuList; }
<ListBox x:Name="listBoxStudents" Height="300" Margin="5" > <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=StudentId}" Width="30"></TextBlock> <TextBlock Text="{Binding Path=StudentName}" Width="60"></TextBlock> <TextBlock Text="{Binding Path=StudentAge}" Width="30"></TextBlock> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
如果上面这个例子中又来了几条数据咋办啊?WPF给出的解决方案是:在使用集合类型作为列表空间的ItemSource时一般会考虑使用ObservableCollection<T>
来替换List<T>,因为ObservableCollection<T>实现了INotifyCollectionChanged和INotifyPropertyChanged接口,能够把集合的变化立刻通知显示它的
列表控件,该百脑汇立刻显示出来。
private ObservableCollection<Student> newStuList; newStuList=new ObservableCollection<Student>() { new Student(){StudentId = 001,StudentName = "张三",StudentAge = 12}, new Student(){StudentId = 002,StudentName = "李四",StudentAge = 13}, new Student(){StudentId = 003,StudentName = "王五",StudentAge = 14}, new Student(){StudentId = 004,StudentName = "赵六",StudentAge = 15}, new Student(){StudentId = 005,StudentName = "陈七",StudentAge = 16} }; this.listBoxStudents.ItemsSource = newStuList; private void BtnTest_OnClick(object sender, RoutedEventArgs e) { newStuList.Add(new Student(){StudentId = 006,StudentName = "新添加的",StudentAge = 17}); }
3、将ElementName指定为Source
<TextBox Name="TxtBox" BorderBrush="DeepSkyBlue" Height="35" Margin="5"
Text="{Binding ElementName=SliderTest,Path=Value,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox> <Slider Name="SliderTest" Margin="5" BorderBrush="DimGray" Maximum="100" Minimum="0"></Slider>
Mode表示数据流动的方向,其值TwoWay说明数据是双向流动的,Slider控件的值能够更新到TextBox,TextBox上的值也能够更新到Slider上,实现这个功能需要修改
UpdateSourceTrigger属性的值,因为TextBox的该属性值默认为Default也就是LostFocus,当失去焦点之后才发生变化。
4、没有Path的绑定
<StackPanel> <StackPanel.Resources> <sys:String x:Key="myString">没有Path的绑定,静态资源</sys:String> </StackPanel.Resources> <TextBlock Name="TextBlockTest" Text="{Binding Source={StaticResource myString} }"></TextBlock> </StackPanel>
C#中的等效代码
string a = "dafagagdfgghh"; this.TextBoxTest.SetBinding(TextBox.TextProperty, new Binding(".") {Source = a});
前面的例子大多是把单个CLR类型对象指定为Binding的Source,方法有两种-把Banding.Source属性或把对象的Name赋值给Binding.ElementName。
5、没有Source的绑定-使用DataContext作为Binding的源
DataContext属性被定义在FrameworkElement类里,这个类是WPF控件的基类,这意味着WPF空间(包括容器控件)都具备这种属性。如前所述,WPF的UI布局是树形结构,这棵树的每个节点都有DataContext。
当一个Binding只知道自己的Path而不知道自己的Source时,它会沿着UI元素树一路向树的根部找过去,每路过一个结点就要看看这个结点的DataContext是否具有Path所指定的属性。如果有就把这个对象作为自己的Source;如果没有,那就继续找下去;如果到了树的根部还没有找到,那么这个Binding就没有Source,因而也不会得到数据。
eg1:
<Grid DataContext="你好"> <Grid> <StackPanel> <Button x:Name="BtnTest" Content="点击试试" Click="BtnTest_OnClick"></Button> </StackPanel> </Grid> </Grid>
private void BtnTest_OnClick(object sender, RoutedEventArgs e) { MessageBox.Show(BtnTest.DataContext.ToString()); }
eg2:
<Grid DataContext="你好"> <Grid> <StackPanel> <Button x:Name="BtnTest" Content="点击试试" Click="BtnTest_OnClick"></Button> <TextBox x:Name="TextBoxtTest" Text="{Binding Path=.}"></TextBox> </StackPanel> </Grid> </Grid>
eg3:
public partial class Student { public int StuId { get; set; } public string StuName { get; set; } public int StuAge { get; set; } }
<StackPanel> <StackPanel.DataContext> <local:Student StuId="001" StuName="张三" StuAge="100"></local:Student> </StackPanel.DataContext> <TextBox Text="{Binding StuId}"></TextBox> <TextBox Text="{Binding StuName}"></TextBox> <TextBox Text="{Binding StuAge}"></TextBox> </StackPanel>
eg4:MVVM模式解决 小灰猫提供的解决方案
class StudentViewModel { public Student stu { get; set; } public StudentViewModel() { stu=new Student() { StuId = 2, StuName = "张三", StuAge = 100 }; } }
public partial class MainWindow : Window { public MainWindow ( ) { InitializeComponent ( ); this.DataContext = new StudentViewModel(); } }
<StackPanel DataContext="{Binding stu}"> <TextBox Text="{Binding StuId}"></TextBox> <TextBox Text="{Binding StuName}"></TextBox> <TextBox Text="{Binding StuAge}"></TextBox> </StackPanel>
6、使用XML数据作为Binding的数据源
<Window.Resources> <XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder"> <x:XData> <FileSystem xmlns=""> <Folder Name="A"> <Folder Name="BooksA"> <Folder Name="Programming"> <Folder Name="Windows"> <Folder Name="WCF"/> <Folder Name="MFC"/> <Folder Name="Delphi"/> </Folder> </Folder> <Folder Name="Tools"> <Folder Name="Development"/> <Folder Name="Designment"/> <Folder Name="Players"/> </Folder> <Folder Name="Tools"> <Folder Name="Development"/> <Folder Name="Designment"/> <Folder Name="Players"/> </Folder> </Folder> <Folder Name="BooksB"> <Folder Name="Programming"> <Folder Name="Windows"> <Folder Name="WCF"/> <Folder Name="MFC"/> <Folder Name="Delphi"/> </Folder> </Folder> <Folder Name="Tools"> <Folder Name="Development"/> <Folder Name="Designment"/> <Folder Name="Players"/> </Folder> <Folder Name="Tools"> <Folder Name="Development"/> <Folder Name="Designment"/> <Folder Name="Players"/> </Folder> </Folder> </Folder> </FileSystem> </x:XData> </XmlDataProvider> </Window.Resources> <Grid> <TreeView ItemsSource="{Binding Source={StaticResource xdp}}"> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}"> <TextBlock Text="{Binding XPath=@Name}"></TextBlock> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid>
如果把XmlDataProvider直接写在XAML代码里,那么它的XML数据需要放在<x:Data>...</X:Data>标签里。
如果将XML文件单独写,该如何?
<?xml version="1.0" encoding="utf-8" ?> <StudentList> <Student Id="1"> <Name>Trim</Name> </Student> <Student Id="2"> <Name>Tom</Name> </Student> <Student Id="3"> <Name>Jim</Name> </Student> <Student Id="4"> <Name>Aim</Name> </Student> </StudentList>
<StackPanel Background="DeepSkyBlue"> <ListView x:Name="listViewTest" Height="200"> <ListView.View> <GridView> <GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding XPath=@Id}"></GridViewColumn> <GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding XPath=Name}"></GridViewColumn> </GridView> </ListView.View> </ListView> <Button Height="36" Click="ButtonBase_OnClick">XML作为绑定源</Button> <Button Height="36" Margin="0,5,0,0" Name="BtnTest" Click="BtnTest_OnClick">新的写法</Button> </StackPanel>
private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { XmlDocument doc=new XmlDocument(); doc.Load(@"./StudentsData.xml"); XmlDataProvider xdp=new XmlDataProvider(); xdp.Document = doc; xdp.XPath = @"/StudentList/Student"; this.listViewTest.DataContext = xdp; this.listViewTest.SetBinding(ListView.ItemsSourceProperty, new Binding()); } private void BtnTest_OnClick(object sender, RoutedEventArgs e) { XmlDataProvider xdp=new XmlDataProvider(); xdp.Source = new Uri ( @"E:\C#\WPF\数据绑定练习\08使用XML数据作为Binding的源\bin\Debug\StudentsData.xml" ); xdp.XPath = @"/StudentList/Student"; this.listViewTest.DataContext = xdp; this.listViewTest.SetBinding(ListView.ItemsSourceProperty, new Binding()); }
两种不同的读取方式
7、使用LINQ检索结果作为Binding的源
<ListView x:Name="listViewStudents" Height="143" Margin="5"> <ListView.View> <GridView> <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}"></GridViewColumn> <GridViewColumn Header="Name" Width="100" DisplayMemberBinding="{Binding Name}"></GridViewColumn> <GridViewColumn Header="Age" Width="80" DisplayMemberBinding="{Binding Age}"></GridViewColumn> </GridView> </ListView.View> </ListView>
private void BtnTest_OnClick(object sender, RoutedEventArgs e) { List<Student> stuList=new List<Student>() { new Student(){Id = 0,Name = "Tim",Age=10}, new Student(){Id = 1,Name = "Tom",Age = 11}, new Student(){Id = 2,Name = "Tony",Age = 12}, new Student(){Id = 3,Name = "Jim",Age = 13} }; this.listViewStudents.ItemsSource = from student in stuList where student.Name.StartsWith("T") select student; }
8、使用ObjectDataProvider对象作为Binding的Source:就是将对象作为数据源提供给Binding。
理想的情况下,上游程序员把类ishejihao、使用属性把数据暴露出来,下游程序员把这些类的实例作为Binding的Source、把属性作为Binding的Path来消费这些类。但是很难保证一个类idea所有数据都能使用属性来暴露,比如我们需要的数据可能是方法的返回值。而重新设计底层类的风险和成本会比较高,况且黑盒引用类库的情况下我们也不可能更改已经编译好的类,这时就要使用ObjectDataProvider来包装作为Binding的源数据对象了。
class Calculator { public string Add(string str1, string str2) { double x,y,z = 0; if (double.TryParse(str1, out x) && double.TryParse(str2, out y)) { z = x + y; return z.ToString(); } else { return "Inpurt Error!"; } } }
private void BtnTest_OnClick(object sender, RoutedEventArgs e) { ObjectDataProvider odp=new ObjectDataProvider(); odp.ObjectInstance = new Calculator(); odp.MethodName = "Add"; odp.MethodParameters.Add("100"); odp.MethodParameters.Add("200"); MessageBox.Show(odp.Data.ToString()); }
在XAML中实现
public partial class MainWindow : Window { public MainWindow ( ) { InitializeComponent ( ); this.SetBinding(); } private void SetBinding() { //创建并配置ObjectDataProvider对象 ObjectDataProvider odp=new ObjectDataProvider(); odp.ObjectInstance = new Calculator(); odp.MethodName = "Add"; odp.MethodParameters.Add("10"); odp.MethodParameters.Add("10"); //以ObjectDataProvider对象为Source创建Binding Binding bindingToStr1=new Binding("MethodParameters[0]") { Source = odp, BindsDirectlyToSource=true, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }; Binding bindingToStr2=new Binding("MethodParameters[1]") { Source = odp, BindsDirectlyToSource = true, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }; Binding bindingToResult=new Binding("."){Source = odp}; //将Binding关联到UI元素上 this.TextBoxStr1.SetBinding(TextBox.TextProperty, bindingToStr1); this.TextBoxStr2.SetBinding(TextBox.TextProperty, bindingToStr2); this.TextBoxResult.SetBinding(TextBox.TextProperty, bindingToResult); }
9、绑定对数据的转换和校验
Binding的作用就是在Source与Target之间建立桥梁,在这座桥上可以设置关卡对数据的有效性进行校验,当Binding两端要求使用不同的数据类型时,我们还可以为数据设置转换器。
Binding用于数据有效性校验的关卡