WPF 之 Binding 数据驱动UI (INotifyPropertyChanged 向客户端发送属性更改通知,ValidationRule 数据校验, IValueConverter数据转换 )
2014-01-15 15:36
561 查看
一、Data Binding在WPF中的地位
DataBinding 把UI元素一一与数据关联上(以数据为中心的星形结构),当数据发生变化后,这些UI元素会同步显示这一变化。
程序的逻辑层就像是一个强有力的引擎一直在运作,用加工好的数据驱动用户界面用文字、图形、动画等形式把数据显示出来,这就是数据驱动UI。
DataBinding 比作数据桥梁,那么它的两端分别是Binding的源(Source)和目标(Target)。Binding的源是逻辑层对象,Binding的目标是UI层的控件对象。想象 DataBinding 这座桥梁上铺设了高速公路,我们不但可以控制公路是在源与目标之间双向通行还是某个方向的单行道,还可以控制对数据放行的时机,甚至可以在桥上架设一些 “关卡” 用来转换数据类型或检验数据的正确性。
绑定DataGrid
public class Student { public string Name { get; set; } public int Age { get; set; } }
List<Student> liststudent=new List<Student>(); liststudent.Add(new Student{Name="小张",Age=18}); liststudent.Add(new Student{Name="小张",Age=20}); GridList.ItemsSource = liststudent;
<DataGrid Height="157" x:Name="GridList"/>
深入浅出WPF 实例如下:
一:创建简单数据源,并通过 Binding 把它连接到UI元素中。
System.ComponentModel 用于实现组件和控件运行时和设计时行为的类namespace System.ComponentModel
{
// 摘要:
// 向客户端发出某一属性值已更改的通知。
public interface INotifyPropertyChanged
{
// 摘要:
// 在更改属性值时发生。
event PropertyChangedEventHandler PropertyChanged;
}
}
方法一:
(1)、创建名为Student 类做为数据源,继承 INotifyPropertyChanged(向客户端发出某一属性已更改通知) 接口,
//数据源 public class Student:INotifyPropertyChanged { //属性改变 public event PropertyChangedEventHandler PropertyChanged; private string name; public string Name { get { return name; } set { name = value; if (this.PropertyChanged != null) { //属性发生改变后,通知Binding更改UI (Binding Path=**) this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); } } } }当Name属性的值发生变化时 PropertyChanged 事件就会被激活,Binding接收到这个事件后发现事件的消息告诉它是名为Name的属性发生了值得改变,于是就会通知Binding目标端的UI元素显示新的值。
(2)、UI设计
<TextBox x:Name="textBoxName" BorderBrush="Black" Margin="5"/> <Button Content="Add Age" Margin="5" Click="Button_Click"/>
(3)、使用Binding 将 数据源和UI连接起来。
数据绑定:
System.Windows.Data.Binding
System.Windows.Data.BindingOperations
Student stu; public Window1() { InitializeComponent(); //准备数据源 stu = new Student(); //准备Binding Binding binding = new Binding(); binding.Source = stu; //绑定源对象 binding.Path = new PropertyPath("Name");//绑定源对象 属性路径(Name) //使用Binding 连接数据源 与Binding目标 BindingOperations.SetBinding(this.textBoxName, TextBox.TextProperty, binding); //BindingOperations.SetBinding(绑定的绑定目标, 绑定的目标属性, 描述绑定的 System.Windows.Data.BindingBase 对象); } private void Button_Click(object sender, RoutedEventArgs e) { stu.Name += "Name"; }
效果:
方法二:
注意也可以将准备数据源、准备Binding、使用Binding 连接数据源 与Binding目标。三合一操作。
//TextBox基类 FrameworkElement对BindingOperations.SetBinding()方法进行了封装 this.textBoxName.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu = new Student()}); //源更改,目标UI也会更改
方法三:
Student4 stu; public Window30() { InitializeComponent(); stu = new Student4(); stu.Name = "aaa"; textBox1.DataContext = stu; }
<TextBox Name="textBox1" Text="{Binding Path=Name}"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding (DataContext).TreeSelectCommand,
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Page}}" CommandParameter="{Binding Name}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Binding 数据源路径,
RelativeSource(相对数据源)={查找父级,父级类型是Page}
方法四:
<Window x:Class="WpfApplication.Window30"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="Window30" Height="300" Width="300">
<Grid>
<Grid.DataContext>
<local:Student4 Name="aa"/>
</Grid.DataContext>
<TextBox Name="textBox1" Text="{Binding Path=Name}"/>
</Grid>
</Window>
二:控件间建立关联
1、将TextBox的Text属性 关联 ScrollBar的Value属性
<StackPanel> <ScrollBar x:Name="SB" Orientation="Horizontal" Height="30" Minimum="1" Maximum="100" LargeChange="1" SmallChange="1"/> <TextBox x:Name="textbox1" Height="30" BorderBrush="Blue" BorderThickness="2" Text="{Binding Value, ElementName=SB}"/> <!--{Binding 元素属性, ElementName=数据绑定操作源(元素名)}--> </StackPanel>
c#用法:
//将label的content属性,绑定SB的Vlaue值 textbox1.SetBinding(TextBox.TextProperty, new Binding("Value") {ElementName="SB" });
2、多个控件数据绑定。DataContext
<StackPanel DataContext="{Binding ElementName=SB}"> <ScrollBar x:Name="SB" Orientation="Horizontal" Height="30" Minimum="1" Maximum="100" LargeChange="1" SmallChange="1"/> <Label x:Name="labelSBThumb" Height="30" BorderBrush="Blue" BorderThickness="2" Content="{Binding Path=Value}"/> <Button Content="Click" Height="200" FontSize="{Binding Path=Value}"/> </StackPanel>
3、控制Binding的方向及数据更新(TextBox输入值,ScrollBar的值也会更改)
textbox1.SetBinding(TextBox.TextProperty, new Binding("Value") { ElementName = "SB", Mode = BindingMode.TwoWay, //数据流动方式为双向 UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged //目标属性更改时,立即更新数据源 });
三:Binding 的路径(Path):如何在对象上找数据。
binding支持多级路径,通俗讲就是一路 “点” 下去。1、一个TextBox显示另一个TextBox输入文本的长度。
<StackPanel> <TextBox x:Name="textbox1" BorderBrush="Black" Margin="5"/> <TextBox x:Name="textbox2" BorderBrush="Black" Margin="5" Text="{Binding Path=Text.Length,ElementName=textbox1,Mode=OneWay}"/> </StackPanel>注意:Mode=OneWay 无法对“System.String”类型的只读属性“Length”进行 TwoWay 或 OneWayToSource 绑定。
c#代码:
textbox2.SetBinding(TextBox.TextProperty, new Binding("Text.Length") { Source=this.textbox1,Mode=BindingMode.OneWay});
2、一个TextBox显示另一个TextBox文本的第4个字符。
textbox2.SetBinding(TextBox.TextProperty, new Binding("Text.[3]") { Source=this.textbox1,Mode=BindingMode.OneWay});
3、将集合或DataView作为Binding的源,把它的默认值当作Path使用
List<string> stringList = new List<string>() { "Litao","Tom","Blog"}; this.textbox1.SetBinding(TextBox.TextProperty, new Binding("/") { Source = stringList }); this.textbox2.SetBinding(TextBox.TextProperty, new Binding("/Length") { Source = stringList,Mode=BindingMode.OneWay }); this.textbox3.SetBinding(TextBox.TextProperty, new Binding("/[2]") { Source = stringList, Mode = BindingMode.OneWay });
结果:
4、如果集合元素的属性,还有一个集合,把子集合元素当作Path
//城市 class City { public string Name { get; set; } } //省 class Province { public string Name { get; set; } public List<City> CityList { get; set; } } //国家 class Country { public string Name { get; set; } public List<Province> ProvinceList { get; set; } } public partial class Window19 : Window { public Window19() { InitializeComponent(); List<Country> countryList = new List<Country>(){ new Country() { Name="中国",ProvinceList=new List<Province>(){ new Province() { Name="湖北",CityList=new List<City>() { new City{ Name="襄阳" } } } } } }; this.textbox1.SetBinding(TextBox.TextProperty, new Binding("/Name") { Source = countryList, Mode = BindingMode.OneWay }); this.textbox2.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/Name") { Source = countryList, Mode = BindingMode.OneWay }); this.textbox3.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList/Name") { Source = countryList, Mode = BindingMode.OneWay }); } }
5、“没有 Path ”的Binding, Binding源的本身就是数据
<StackPanel> <StackPanel.Resources> <sys:String x:Key="mySTR"> 字符串 </sys:String> </StackPanel.Resources> <TextBlock x:Name="textblock1" TextWrapping="Wrap" Text="{Binding Source={StaticResource ResourceKey=mySTR}}"/> </StackPanel>
c#代码:
string str = "aaa"; this.textblock1.SetBinding(TextBlock.TextProperty, new Binding(".") { Source = str });
四:为Binding 指定源(Source)的几种方法
前面介绍的Binding的数据源有两种:Binding.Source=对象 或 Binding.ElementName=对象Name1、没有Source的Binding, 使用DataContext 作为Binding的源
DataContext属性被定义在 FrameWorkElement 类里,这个类是WPF控件的基类,这就意味着所有的WPF控件(包括容器控件),都具备这个属性。实例1:Binding只知道自己的Path,而不知道自己的Source时,它会沿UI元素树,一路向树的根部找过去,每路过一个结点就会看看这个结点的DataContext是否具有Path所指定的属性。如果有,那就把这个对象作为自己的Source;如果没有,那就继续找下去;如果到了树的根部还没找到,那么这个Binding就没有Source,因而不会得到数据。
效果图:
创建Student1类
class Student1:INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string name; public string Name { get { return name; } set { name = value; if (PropertyChanged != null) { PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Name")); } } } }xaml 代码
<Window x:Class="WpfApplication.Window20" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:WpfApplication" Title="Window20" Height="300" Width="300"> <Grid> <Grid.DataContext> <sys:Student1 Name="姓名"/> </Grid.DataContext> <StackPanel> <TextBox Text="{Binding Path=Name}"/> </StackPanel> </Grid> </Window>
实例2:当Binding的Source本身是数据源
<Window x:Class="WpfApplication.Window20" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" Title="Window20" Height="300" Width="300"> <Grid> <Grid.DataContext> <sys:String>本身是数据源</sys:String> </Grid.DataContext> <StackPanel> <TextBlock Text="{Binding}"/> <!--<TextBox Text="{Binding Mode=OneWay}"/>--> </StackPanel> </Grid> </Window>
注意:TextBox 数据流动的方式是双向的,必须需要 Path。所有我们把它设置为单向 Mode=OneWay
实例3:”Binding沿着UI元素向上找“本质是:DataContext是个依赖属性,如果你没为控件某个属性显示赋值时,控件会把自己容器的属性”借过来“当作自己的属性值。
<Window x:Class="WpfApplication.Window20" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window20" Height="300" Width="300"> <Grid DataContext="6"> <Grid> <Grid> <Button x:Name="btn" Content="OK" Click="btn_Click"/> </Grid> </Grid> </Grid> </Window>
private void btn_Click(object sender, RoutedEventArgs e) { MessageBox.Show(btn.DataContext.ToString()); }
弹出结果为,按钮自己容器的DataContext属性的值。
2、使用集合对象作为列表控件的 ItemsSource
WPF所有的 列表式控件 都派生自ItemsControl类,自然继承了类的ItemsSource这个属性,[b]ItemsSource这个属性可以接收一个IEnumerable接口派生类的实例作为自己的值(如数组,List<T>)。[/b]每个ItemsControl派生类都具有自己对应的条目容器(ListBox条目容器是ListBoxItem, ComboBox条目容器是ComboBoxItem)。
实例:comboBox绑定数据源,路径
public Window2() { InitializeComponent(); List<Student> stuList = new List<Student>() { new Student(){Id=0,Name="Tim",Age=20}, new Student(){Id=1,Name="Tom",Age=30} }; comboBox1.ItemsSource = stuList;//数据源 comboBox1.DisplayMemberPath = "Name";//路径Path comboBox1.SelectedIndex = 0; } private void button1_Click(object sender, RoutedEventArgs e) { Student stu = (Student)comboBox1.SelectedItem; MessageBox.Show(stu.Age.ToString()); }
comboBox 做为 textbox的数据源
Binding binding = new Binding("SelectedItem.Id") { Source = comboBox1 }; this.textbox2.SetBinding(TextBox.TextProperty,binding);
3、ADO.NET对象作为Binding数据源
在.NET开发中,我们使用ADO.NET类对数据库进行操作,常用的工作是从数据库中把数据读取到DataTable中,把DataTable显示到UI列表控件里。(尽管现在流行的软件框架,先通过LINQ等把DataTable里数据转换成恰当的用户自定义类型集合)。但是WPF也支持 列表控件 与 DataTable 之间建立直接绑定。实例:将 DataTable 绑定到 ListBox
效果图:代码:
DataTable dt = new DataTable("Student"); DataColumn dc1 = new DataColumn("Name",Type.GetType("System.String")); DataColumn dc2 = new DataColumn("Age", Type.GetType("System.Int16")); dt.Columns.Add(dc1); dt.Columns.Add(dc2); for (int i = 1; i <= 10; i++) { DataRow dr = dt.NewRow(); dr["Name"] = "姓名" + i; dr["Age"] = i; dt.Rows.Add(dr); } //DefaultView是个DataView类型对象,DataView实现IEnumerable接口 //所以可以被赋值给ItemsSource属性 listboxStudent.ItemsSource = dt.DefaultView; listboxStudent.DisplayMemberPath = "Name";
实例:将 DataTable 绑定到 ListView
效果:代码:
<StackPanel Background="LightBlue"> <ListView x:Name="listviewStudents" Height="130" Margin="5"> <ListView.View> <GridView> <GridViewColumn Header="姓名" Width="60" DisplayMemberBinding="{Binding Name}"/> <GridViewColumn Header="年龄" Width="60" DisplayMemberBinding="{Binding Age}"/> </GridView> </ListView.View> </ListView> </StackPanel>
//方法一: //this.listviewStudents.ItemsSource = dt.DefaultView; //方法二: this.listviewStudents.DataContext = dt; //给ListView的ItemsSource属性,设置一个没有Path,没有Source的绑定,自动找DataContext this.listviewStudents.SetBinding(ListView.ItemsSourceProperty,new Binding());
4、XML作为Binding数据源(讲解文档对象模型标准类库)
迄今为止,.NETFrameWork提供两种处理XML数据的类库:符合Dom(Document Object Modle,文档对象模型) 标准类库:包括XmlDocument、XmlElement、XmlNode、XmlAttribute等类。这套类库的特点是中规中矩,功能强大, 但也背负着太多传统Xml的传统和复杂。
以Linq(Language-Integrated Query,语言集成查询)为基础类库:包括XDocument、XElement、XNode、XAttribute等类。这套类库的特点是可以使用Linq进行查询或操作,方便快捷。
现在程序设计,只要涉及数据传输就离不开XML,因为大多数数据传输都是基于SOAP(Simple Object Access Protocol,简单对象访问协议)的相关协议。而SOAP又是通过将对象序列化XML文本进行传输。XML文本是树型结构的,所以XML可以方便地用于表示线性集合(如:Array、List等)和树形结构数据。
实例:XML文档是一组学生信息,显示到 ListView控件上。 XmlDataProvider
效果:RawDate.xml
<?xml version="1.0" encoding="utf-8" ?> <StudentList> <Student Id="1"> <Name>小明</Name> </Student> <Student Id="2"> <Name>小王</Name> </Student> <Student Id="3"> <Name>小张</Name> </Student> </StudentList>
XAML
<StackPanel Background="LightBlue"> <ListView x:Name="listViewStudent" Height="130" Margin="5"> <ListView.View> <GridView> <!--加@符号字符串表示的是XML元素的Attribute,不加@符号字符串表示的是子级元素--> <GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding XPath=@Id}"/> <GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding XPath=Name}"/> </GridView> </ListView.View> </ListView> </StackPanel>
//方法一: XmlDocument doc = new XmlDocument(); doc.Load(Environment.CurrentDirectory+"\\RawDate.xml"); XmlDataProvider xdp = new XmlDataProvider(); xdp.Document = doc; //使用XPath需要暴露一组Student数据 xdp.XPath = @"/StudentList/Student"; this.listViewStudent.DataContext = xdp; this.listViewStudent.SetBinding(ListView.ItemsSourceProperty,new Binding()); //方法二: XmlDataProvider xdp2 = new XmlDataProvider(); xdp2.Source = new Uri(Environment.CurrentDirectory + "\\RawDate.xml"); //使用XPath需要暴露一组Student数据 xdp2.XPath = @"/StudentList/Student"; this.listViewStudent.DataContext = xdp2; this.listViewStudent.SetBinding(ListView.ItemsSourceProperty, new Binding());
实例:TreeView显示若干层目录的XML文件
效果:XAML
<Window x:Class="WpfApplication.Window22" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window22" Height="300" Width="300"> <Window.Resources> <!--把XML数据和XmlDataProvider对象直接写到XAML中--> <XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder"> <x:XData> <FileSystem xmlns=""> <Folder Name="书"> <Folder Name="程序开发"> <Folder Name="Windows"> <Folder Name="WPF"/> <Folder Name="DelPhi"/> </Folder> </Folder> <Folder Name="工具"> <Folder Name="Vs2010"/> </Folder> </Folder> </FileSystem> </x:XData> </XmlDataProvider> </Window.Resources> <Grid> <TreeView x:Name="DemoTreeView" ItemsSource="{Binding Source={StaticResource xdp}}"> <TreeView.ItemTemplate> <!--用HierarchicalDataTemplate类,这个类具有ItemsSource属性,展示的数据可以拥有子级集合--> <HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}"> <TextBlock Text="{Binding XPath=@Name}"/> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid> </Window>
获取选中的值:
方法一:
<Label Content="{Binding ElementName=DemoTreeView, Path=SelectedItem.Attributes[Name].Value}"/>
Name是xml文件中的属性
方法二:
this.DemoTreeView.AddHandler(TreeViewItem.SelectedEvent, new RoutedEventHandler(this.ButtonClicked));
public void ButtonClicked(object sender, RoutedEventArgs e) { TreeViewItem tvi = e.OriginalSource as TreeViewItem; XmlElement xe = tvi.Header as XmlElement; MessageBox.Show(xe.Attributes["Value"].Value); }
5、linq查询结果作为数据源
自3.0版本开始,.NETFrameWork开始支持LINQ(Language-Integrated Query,语言集成查询),使用LINQ查询,我们可以方便的操作集合对象、DataTable对象和XML对象,而不必动辄就好几层的foreach循环嵌套在一起却只是完成一个简单的任务。Linq查询结果是一个IEnumerable<T>类型对象,而IEnumerable<T>又派生自IEnumerable,所以它可以作为列表控件的ItemsSource来使用。
实例:linq 集合对象
效果:代码:
public class Student3 { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
<StackPanel Background="LightBlue"> <ListView x:Name="listViewStudent" Height="143" Margin="5"> <ListView.View> <GridView> <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}"/> <GridViewColumn Header="Name" Width="100" DisplayMemberBinding="{Binding Name}"/> <GridViewColumn Header="Age" Width="80" DisplayMemberBinding="{Binding Age}"/> </GridView> </ListView.View> </ListView> </StackPanel>
List<Student3> stuList = new List<Student3>() { new Student3(){Id=0,Name="小明",Age=20}, new Student3(){Id=1,Name="小王",Age=25}, new Student3(){Id=1,Name="张三",Age=30} }; this.listViewStudent.ItemsSource = from stu in stuList where stu.Name.StartsWith("小") select stu;
实例:linq DataTable对象
public class Student3 { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
DataTable dt = new DataTable("Student"); DataColumn dc1 = new DataColumn("Id",Type.GetType("System.Int16")); DataColumn dc2 = new DataColumn("Name", Type.GetType("System.String")); DataColumn dc3 = new DataColumn("Age", Type.GetType("System.Int16")); dt.Columns.Add(dc1); dt.Columns.Add(dc2); dt.Columns.Add(dc3); DataRow dr = dt.NewRow(); dr["Id"] = 0; dr["Name"] = "小明"; dr["Age"] = 20; dt.Rows.Add(dr); DataRow dr1 = dt.NewRow(); dr1["Id"] = 1; dr1["Name"] = "小王"; dr1["Age"] = 25; dt.Rows.Add(dr1); DataRow dr2 = dt.NewRow(); dr2["Id"] = 2; dr2["Name"] = "张三"; dr2["Age"] = 30; dt.Rows.Add(dr2); this.listViewStudent.ItemsSource = from row in dt.Rows.Cast<DataRow>() where Convert.ToString(row["Name"]).StartsWith("小") select new Student3() { Id=int.Parse(row["Id"].ToString()), Name=row["Name"].ToString(), Age=int.Parse(row["Age"].ToString()) };
实例:linq Xml文件
效果:代码:
public class Student3 { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
<StackPanel Background="LightBlue"> <ListView x:Name="listViewStudent" Height="143" Margin="5"> <ListView.View> <GridView> <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}"/> <GridViewColumn Header="Name" Width="100" DisplayMemberBinding="{Binding Name}"/> <GridViewColumn Header="Age" Width="80" DisplayMemberBinding="{Binding Age}"/> </GridView> </ListView.View> </ListView> </StackPanel>
XDocument xdoc = XDocument.Load(Environment.CurrentDirectory + "\\RawData2.xml"); this.listViewStudent.ItemsSource = from element in xdoc.Descendants("Student") where element.Attribute("Name").Value.StartsWith("小") select new Student3() { Id = int.Parse(element.Attribute("Id").Value), Name=element.Attribute("Name").Value, Age = int.Parse(element.Attribute("Age").Value), };
6、使用ObjectDataProvider对象作为Binding的Source
ObjectDataProvider 包装一个以方法暴露数据的对象。实例:把类中的加法作为数据源。
class Calculator { public string Add(string arg1, string arg2) { double x = 0; double y = 0; double z = 0; if (double.TryParse(arg1, out x) && double.TryParse(arg2, out y)) { z = x + y; return z.ToString(); } return "Input Error!"; } }
ObjectDataProvider odp = new ObjectDataProvider(); odp.ObjectInstance = new Calculator();//设置绑定源对象 odp.MethodName = "Add";//调用的方法名 odp.MethodParameters.Add("100");//方法参数列表 odp.MethodParameters.Add("200"); MessageBox.Show(odp.Data.ToString());
实例:加法计算器
效果:代码:
class Calculator { public string Add(string arg1, string arg2) { double x = 0; double y = 0; double z = 0; if (double.TryParse(arg1, out x) && double.TryParse(arg2, out y)) { z = x + y; return z.ToString(); } return "Input Error!"; } }
<StackPanel Background="LightBlue" Orientation="Horizontal" Height="30" > <TextBox Name="textBoxArg1" Margin="5" Width="30" Height="20"/> <TextBlock Text="+" VerticalAlignment="Center" /> <TextBox Name="textBoxArg2" Margin="5" Width="30" Height="20"/> <TextBlock Text="=" VerticalAlignment="Center" /> <TextBox Name="textBoxResult" Margin="5" Width="30" Height="20"/> </StackPanel>
//第1步:创建并配置ObjectDataProvider对象 ObjectDataProvider odp = new ObjectDataProvider(); odp.ObjectInstance = new Calculator(); odp.MethodName = "Add"; odp.MethodParameters.Add("0"); odp.MethodParameters.Add("0"); //第2步:为ObjectDataProvider对象(Source)创建Binding //Path是MethodParameters[0] //Source是ObjectDataProvider 对象 //UpdateSourceTrigger 有更新立刻将值传给Source Binding bindingToArg1 = new Binding("MethodParameters[0]") { Source=odp, BindsDirectlyToSource=true, UpdateSourceTrigger=UpdateSourceTrigger.PropertyChanged }; Binding bindingToArg2= new Binding("MethodParameters[1]") { Source = odp, BindsDirectlyToSource = true, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }; //将数据源本身代表数据时,Path为点 Binding bindingToResult = new Binding(".") { Source=odp}; //第3步:将Binding关联到UI元素上 this.textBoxArg1.SetBinding(TextBox.TextProperty,bindingToArg1); this.textBoxArg2.SetBinding(TextBox.TextProperty, bindingToArg2); this.textBoxResult.SetBinding(TextBox.TextProperty, bindingToResult);
7、使用 Binding 的 RelativeSource
当一个Binding 有明确数据源时,我们可以通过Source 或 ElementName 赋值的方法让Binding 与之关联。当我们不能确定Source 对象的名字时,用RelativeSource
实例: TextBox 文本框的TextProperty,设置为第一个Grid的Name.
效果:代码:
<Grid x:Name="g1" Background="Red" Margin="10"> <DockPanel x:Name="d1" Background="Orange" Margin="10"> <Grid x:Name="g2" Background="Yellow" Margin="10"> <DockPanel x:Name="d2" Background="LawnGreen" Margin="10"> <TextBox x:Name="textbox1" FontSize="24" Margin="10" /> </DockPanel> </Grid> </DockPanel> </Grid>
//上一级,最近数据源 RelativeSource rs = new RelativeSource(RelativeSourceMode.FindAncestor); //1表示与绑定目标(textbox1)最近的一个级别 rs.AncestorLevel = 1; //类型为Grid rs.AncestorType=typeof(Grid); Binding binding = new Binding("Name") { RelativeSource=rs}; this.textbox1.SetBinding(TextBox.TextProperty,binding);
或写成
<TextBox x:Name="textbox1" FontSize="24" Margin="10" Text="
{
Binding Path=Name,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid},AncestorLevel=1}
}"/>
五、Binding 对数据的转换与校验(ValidationRules,Converter)
Binding的作用的架在Source 与 Target 之间的桥梁。设有两端不同数据类型的转换与校验。Binding 用于数据有效性校验的 ValidationRules 属性。
Binding 用于数据转换的是 Converter 属性。
1、Binding 的数据校验
程序集 PresentationFramework.dll System.Windows.Controls.ValidationRule效果:代码:
<StackPanel>
<TextBox x:Name="textbox1" Margin="5"/>
<Slider x:Name="slider1" Minimum="-10" Maximum="100" Margin="5"/>
</StackPanel>
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; namespace WpfApplication { /// <summary> /// Window27.xaml 的交互逻辑 /// </summary> public partial class Window27 : Window { public Window27() { InitializeComponent(); Binding binding = new Binding("Value") { Source = slider1 }; binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; //第一步:校验 RangeValidationRule rvr = new RangeValidationRule(); //默认是对目标对象TextBox进行校验。 //如果ValidatesOnTargetUpdated = true,对数据源slider1,进行校验 rvr.ValidatesOnTargetUpdated = true; binding.ValidationRules.Add(rvr); //第二步:侦听校验错误事件(路由事件) //开启校验错误通知 binding.NotifyOnValidationError = true; //为错误路由事件(Validation.ErrorEvent),添加处理方法 this.textbox1.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(this.ValidationError)); this.textbox1.SetBinding(TextBox.TextProperty, binding); } //处理错误的方法 public void ValidationError(object sender, RoutedEventArgs e) { if (Validation.GetErrors(this.textbox1).Count > 0) { this.textbox1.ToolTip = Validation.GetErrors(this.textbox1)[0].ErrorContent.ToString(); } } } //校验 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 <= 100) { return new ValidationResult(true,null); } } return new ValidationResult(false,"Validation Failed!"); } } }
方法二:XAML
(1)、继承 ValidationRule 验证规则
public class RangeValidationRule:ValidationRule
{
}
(2)、引用 自定义验证规则的 命名空间:
xmlns:local="命名空间“
(3)设置验证
<TextBox x:Name="textbox1" Style="{Binding Source={StaticResource style1}}">
<Binding Path="Value" UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True" Mode="TwoWay">
<Binding.ValidationRules>
<local:RangeValidationRule ValidatesOnTargetUpdated="True" ValidationStep="ConvertedProposedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
设置验证方法,在数据更新时验证。
(4)、显示错误模板
<Window.Resources> <Style x:Key="style1" TargetType="{x:Type TextBox}"> <Setter Property="Validation.ErrorTemplate"> <Setter.Value> <ControlTemplate> <DockPanel LastChildFill="True"> <Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10" ToolTip="{Binding ElementName=aaa, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"> <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white"> </TextBlock> </Border> <Border BorderBrush="Green" BorderThickness="1"> <AdornedElementPlaceholder x:Name="aaa"></AdornedElementPlaceholder> </Border> </DockPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources>
<AdornedElementPlaceholder x:Name="aaa"></AdornedElementPlaceholder>
装饰元素占位符(验证控件本身)
ToolTip="{Binding ElementName=aaa,Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
ElementName=aaa 修饰元素名称
Path=AdornedElement.(Validation.Errors)[0].ErrorContent 修饰元素.验证错误第一个错误信息
鼠标移入或移出的时候,显示错误信息?
<Style TargetType="{x:Type TextBox}">
...
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
推荐阅读:WPF 使用ValidationRule进行表单数据验证
2、Binding的数据转换(IValueConverter 接口 进行数据转换)
System.Windows.Data.IValueConverterpublic interface IValueConverter
{
//当数据从Source流向Target
object Convert(object value, Type targetType, object parameter, CultureInfo culture);
//当数据从Target流向Source
object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}
实例一:
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; namespace WpfApplication1 { /// <summary> /// Window4.xaml 的交互逻辑 /// </summary> public partial class Window4 : Window { public Window4() { InitializeComponent(); //创建Binding对象 Binding b = new Binding(); //注册转换器,源,路径 b.Converter = new MyDoubleConverter(); b.Source = SB; b.Path = new PropertyPath("Value"); //调用Label 的 SetBinding() 方法 this.labelSBThumb.SetBinding(Label.ContentProperty,b); } } //System.Windows.Data 命名空间的 IValueConverter 接口 进行数据转换 public class MyDoubleConverter : IValueConverter { //值转换成目标类型 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { double v = (double)value; return (int)v; } //将目标类型转换成值 public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return value; } } }
实例二:
效果:PlaneList.txt
Category=Bomber,Name=B-1,State=Available
Category=Fighter,Name=F-22,State=Available
代码:
自定义数据类型:
//种类 public enum Category { Bomber,//轰炸机 Fighter//战斗机 } //状态 public enum State { Available,//可以 Locked,//锁定 Unknown//未知 } //飞机 public class Plane { public Category Category { get; set; } public string Name { get; set; } public State State { get; set; } }
类型转换
#region 数据转换 public class CategoryToSourceConverter : IValueConverter { //将Category转成Uri public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { Category c = (Category)value; switch(c) { case Category.Bomber: return @"\Icons\Bomber.png"; case Category.Fighter: return @"\Icons\Fighter.png"; default: return null; } } //不会调用 public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } public class StateToNullableBoolConverter : IValueConverter { //将State转成bool? public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { State s = (State)value; switch (s) { case State.Locked: return false; case State.Available: return true; case State.Unknown: default: return null; } } //将bool?转成State public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { bool? nb = (bool?)value; switch (nb) { case true: return State.Available; case false: return State.Locked; default: return State.Unknown; } } } #endregion
XAML
<Window x:Class="WpfApplication.Window28" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication" Title="Window28" Height="300" Width="300"> <Window.Resources> <local:CategoryToSourceConverter x:Key="cts"/> <local:StateToNullableBoolConverter x:Key="stnb"/> </Window.Resources> <StackPanel Background="LightBlue"> <ListBox x:Name="listBoxPlane" Height="160" Margin="5"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Image Width="20" Height="20" Source="{Binding Path=Category,Converter={StaticResource cts}}"/> <TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"/> <CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource stnb}}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button x:Name="buttonLoad" Content="Load" Height="25" Margin="5,0" Click="buttonLoad_Click"/> <Button x:Name="buttonSave" Content="Save" Height="25" Margin="5,5" Click="buttonSave_Click"/> </StackPanel> </Window>
private void buttonLoad_Click(object sender, RoutedEventArgs e) { List<Plane> planeList = new List<Plane>() { new Plane(){Category=Category.Bomber,Name="B-1",State=State.Unknown}, new Plane(){Category=Category.Fighter,Name="F-22",State=State.Unknown} }; this.listBoxPlane.ItemsSource = planeList; } private void buttonSave_Click(object sender, RoutedEventArgs e) { StringBuilder sb = new StringBuilder(); foreach (Plane p in listBoxPlane.Items) { sb.AppendLine(string.Format("Category={0},Name={1},State={2}",p.Category,p.Name,p.State)); } File.WriteAllText(Environment.CurrentDirectory+@"\PlaneList.txt",sb.ToString()); }
六、MultiBinding(多路Binding)
实例:注册页面,添加验证。第一个、第二个文本框相同,第三个、第四个文本框相同时,提交按钮可以使用。代码:
<Window x:Class="WpfApplication.Window29" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window29" Height="300" Width="300"> <StackPanel> <TextBox Height="23" Name="textBox1" Margin="5"/> <TextBox Height="23" Name="textBox2" Margin="5,0"/> <TextBox Height="23" Name="textBox3" Margin="5"/> <TextBox Height="23" Name="textBox4" Margin="5,0"/> <Button Content="Submit" Height="23" Name="button1" Width="75" /> </StackPanel> </Window>
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; namespace WpfApplication { /// <summary> /// Window29.xaml 的交互逻辑 /// </summary> public partial class Window29 : Window { public Window29() { InitializeComponent(); SetMultiBinding(); } private void SetMultiBinding() { //准备基础Binding Binding b1 = new Binding("Text") { Source=this.textBox1}; Binding b2 = new Binding("Text") { Source = this.textBox2 }; Binding b3 = new Binding("Text") { Source = this.textBox3 }; Binding b4 = new Binding("Text") { Source = this.textBox4 }; //准备MultiBinding //注意:MultiBinding对Add子Binding的顺序是敏感的。 MultiBinding mb = new MultiBinding() { Mode = BindingMode.OneWay }; mb.Bindings.Add(b1); mb.Bindings.Add(b2); mb.Bindings.Add(b3); mb.Bindings.Add(b4); mb.Converter = new LogonMultiBindingConverter(); this.button1.SetBinding(Button.IsEnabledProperty,mb); } } public class LogonMultiBindingConverter : 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() && 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(); } } }
相关文章推荐
- WPF 之 Binding 数据驱动UI—实战
- WPF:向客户端发出某一属性值已更改的通知INotifyPropertyChanged接口
- WPF:向客户端发出某一属性值已更改的通知INotifyPropertyChanged接口
- WPF中INotifyPropertyChanged用法与数据绑定
- [WPF]入门理解Binding 数据驱动思想
- WPF中INotifyPropertyChanged用法与数据绑定
- wpf 客户端【JDAgent桌面助手】开发详解(三) 瀑布流效果实现与UI虚拟化优化大数据显示
- 【转载】wpf数据绑定binding与INotifyPropertyChanged
- WindowPhone(SliverLight)中属性变化通知机制INotifyPropertyChanged 接口
- wp7中实现 INotifyPropertyChanged 是为了属性变更后的通知的代码笔记
- WPF研究之道——数据驱动UI
- WPF使用Binding对数据的校验
- vue.js组件之间通讯的数据双向绑定----父亲把数据传递给儿子,儿子更改数据后,重新发送给父亲,父亲数据更改后,属性会重新发送个儿子,儿子刷新新数据
- WPF中INotifyPropertyChanged用法与数据绑定
- WPF中INotifyPropertyChanged用法与数据绑定
- WPF Data Binding之数据的转换和校验【四】
- wpf学习笔记 NotifyPropertyChanged实现数据变化自动更新UI
- WPF中的binding(十)- Binding数据的校验
- WPF中INotifyPropertyChanged用法与数据绑定
- WPF系列之一:基于并行任务和MVVM创建响应灵敏和数据驱动的UI