简单说就是是双向绑定。
Data Binding就相当于高速公路。以前写winform的时候,没有这条高速路,后台数据发生变化,页面上之前显示的数据没变化,需要再次使用新变化的数据才能显示出来。
举个例子,一个textbox控件,最开始后台变量给这个textbox控件赋一个值a="最开始赋值",窗体显示出来,textbox上的内容就是"最开始赋值",点击一个按钮,点了这个按钮就可以改变后台变量a的值,a="改变后的值",点了这个按钮后,a的值变了,但是textbox显示没有变,需要再次对textbox赋新的值,显示才会变化。

而Data Binding,就是后台数据发生变化,前端显示就跟着变化。数据驱动,就是数据是主要的。上面的例子,textbox用binding绑定的数据,只要变量a发生变化,显示就跟着变。

Binding比作数据的桥梁,它的两端分别是数据源和目标。数据源就是数据从哪里来,一般是逻辑变量,C#代码。目标一般是控件,Binding就是在中间起着连接数据源和目标的作用。

双向绑定的方法

后台逻辑与XAML联合的方法

1.数据源对象

Binding数据源是个对象就行了,需要实现INotifyPropertyChanged接口,在属性的每个set里面,都要通知OnPropertyChanged("属性名")是哪个属性发生了变化,只有通知了,才能做到双向绑定,Name发生了变化,通知一声,所有使用Name属性的东西都要跟着变。

  1. public class Prop: INotifyPropertyChanged
  2. {
  3. private string name;
  4. public string Name
  5. {
  6. get { return name; }
  7. set
  8. {
  9. name = value;
  10. OnPropertyChanged("Name");//通知Name属性发生了变化
  11. }
  12. }
  13. //这是实现接口自带的
  14. public event PropertyChangedEventHandler PropertyChanged;
  15. /// <summary>
  16. /// 属性变化通知事件
  17. /// </summary>
  18. /// <param name="PropertyName"></param>
  19. public void OnPropertyChanged(string PropertyName)
  20. {
  21. PropertyChangedEventArgs e = new PropertyChangedEventArgs(PropertyName);
  22. if (PropertyChanged != null)
  23. {
  24. PropertyChanged(this, e);
  25. }
  26. }
  27. }

2.XAML绑定控件

在XAML中这样绑定,Text是他的内容属性,用{Binding 要绑定的属性}

  1. <TextBox x:Name="name" Text="{Binding Name}">

有一种情况,有时候Binding的值还未刷新,比如说,事件要执行完了Binding的值才刷新,没执行完,Binding的值还没刷新,需要强制刷新,如下写法。
UpdateSourceTrigger,它的类型是UpdateSourceTrigger枚举,可取值为PropertyChanged、LostFocus、Explicit和 Default

  1. <TextBox x:Name="name" Text="{Binding Name,UpdateSourceTrigger=PropertyChanged}">

3.数据源绑定到控件

这一步算是桥梁,在初始化的代码中写道。

  1. Prop prop = new Prop();
  2. DataContext = prop;//DataContext是当前窗体的DataContext,把数据源对象赋值给它,当前窗体的所有控件都能访问这个数据源对象里面数据

纯写C#代码的方法

Binding起始就是个类,上面{Binding xxx,yyy}其实是Binding类用法的简写,这种简写就是新手不太容易分辨有些什么东西可以写。用代码的方式写Binding,可以F12,F1查看Binding里面有些啥,可以写什么。
WPF里面C#代码永远是核心。

绑定数据首先需要数据源,数据源就用上面的那个。

然后需要控件。这里就用TextBox,这里就只给他取了一个名字

  1. <TextBox x:Name="name" >

在后台代码里面写道

  1. //实例化Binding可以传一个Path,就是绑定哪个属性,
  2. Binding binding = new Binding("Name");
  3. //指定数据源
  4. binding.Source = new Prop();
  5. //数据绑定的方式,双向绑定
  6. binding.Mode = BindingMode.TwoWay;
  7. //数据源更新了怎么触发,属性改变就触发
  8. binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
  9. //给TextBox控件绑定上binding
  10. //第一个参数是指定一个依赖属性,就是绑定到拿个属性上,这里是绑定到TextBox控件的Text属性上
  11. //第二个参数就是binding对象
  12. name.SetBinding(TextBox.TextProperty, binding);

好了,绑定就完成了。
至于是双向绑定还是单向绑定等,是由Binding.Mode决定的。详细看下面的节点 Binding的绑定方向

UI控件联动绑定

除了绑定逻辑代码,还可以把两个控件之间绑定起来,让他们产生关联

如这段XAML,两个控件,一个输入框,一个滑动条,输入框的Text用了Binding,Path是指绑定哪个属性,ElementName是指绑定哪个控件。这里TextBox的Text属性绑定了名为slider的控件的Value属性。
也就是slider的Value发生变化,Text跟着变。

Path关键字可以省略,直接写Binding Value也是可以的。

  1. <Grid>
  2. <TextBox x:Name="text" BorderBrush="Black" Width="200" Height="50" Text="{Binding Path=Value ,ElementName=slider}"></TextBox>
  3. <Slider x:Name="slider" Maximum="100" Minimum="0" Width="200" Margin="300,277,300,-176"></Slider>
  4. </Grid>

image.png

Binding的绑定方向

Binding在源与目标之间架起了沟通的桥梁,默认情况下数据既能够通过Binding送达目标,也能够从目标返回源(收集用户对数据的修改)。有时候数据只需要展示给用户、不允许用户修改,这时候可以把 Binding 模式更改为从源向目标的单向沟通。Binding还支持从目标向源的单向沟通以及只在 Binding关系确立时读取一次数据,这需要我们根据实际情况去选择。

控制 Binding 数据流向的属性是 Mode,它的类型是BindingMode枚举。BindingMode可取值为TwoWay、OneWay、OnTime、OneWayToSource和 Default。这里的Default 值是指Binding 的模式会根据目标的实际情况来确定,比如若是可编辑的(如TextBox.Text 属性),Default就采用双向模式,若是只读的(如TextBlock.Text)则采用单向模式。

  1. <TextBox x:Name="text" BorderBrush="Black" Width="200" Height="50" Text="{Binding Path=Value ,ElementName=slider,Mode=TwoWay}"></TextBox>

Binding的Path属性

Path指定了绑定了哪个属性,如上双向绑定的方法,Path可以省略。不是说没有,是简写了。绑定的值就是来自Path。
当Binding的Source本身就是数据,没有格外的属性时,Path可以写.或者不写。

为Binding指定数据源

Binding 的源是数据的来源,所以,只要一个对象包含数据并能通过属性把数据暴露出来,它就能当作 Binding 的源来使用。包含数据的对象比比皆是,但必须为Binding 的Source指定合适的对象Binding才能正常工作。

ElementName

详情看上面节点 UI控件联动绑定

Source

详情看上面节点 纯写C#代码的方式

DataContext

没有未Binding设置Source,它就会自动去DataContext属性里面找有没有Path设置的这个属性,一般的每个控件都有DataContext这个属性。如上面的 [双向绑定的方法] 的例子,给TextBox绑定值一个名为Name的属性,但是没有给textBox的Binding设置Source,它就会去TextBox的DataContext里面找Name这个属性,但是没有给TextBox的DataContext设置对象,就会把它的父容器的DataContext拿来用,父容器也没有设置,就继续往上拿,直到拿到当前窗体Window对象的DataContext,因为我在当前窗体Window对象的DataContext里面设置了对象,里面有Name这个属性,TextBox一层一层的往上找,最终找到了Name。要是没找到就是没有源,无法绑定。

通过DataContext的方式设置源,一般Path都可以简写。如果绑定就一个{Binding},说明DataContext是一个简单的数据类型,字符串,数字等。

ItemsSource

WPF中列表式控件派生自ItemsControl类,白然也就继承了ItemsSource这个属性。ltemsSource属性可以接收一个IEnumerable接口实现类的实例作为自己的值。每个ItemControl派生类都有自己的条目容器,例如:ListBox的条目容器是ListBoxItem,ComboBox的条目容器是ComboBoxItem。
只要我们设置了ItemsSource,就会自动遍历其中的值。

绑定简单类型的容器,直接就可以迭代显示了

  1. <ListBox x:Name="list"></ListBox>
  1. List<string> strs = new List<string>();
  2. strs.Add("AAAA");
  3. strs.Add("BBBB");
  4. strs.Add("CCCC");
  5. list.ItemsSource = strs;

image.png

如果数据源有多个属性,要显示其中一个具体的属性

  1. <ListBox x:Name="list" DisplayMemberPath="Name"></ListBox>
  1. List<Stu> s = new List<Stu>();
  2. Stu s1 = new Stu()
  3. {
  4. Name = "张三",
  5. Age = 13,
  6. Sex = "男"
  7. };
  8. Stu s2 = new Stu()
  9. {
  10. Name = "李四",
  11. Age = 34,
  12. Sex = "女"
  13. };
  14. Stu s3 = new Stu()
  15. {
  16. Name = "王五",
  17. Age = 25,
  18. Sex = "男"
  19. };
  20. s.Add(s1);
  21. s.Add(s2);
  22. s.Add(s3);
  23. list.ItemsSource = s;

image.png
要显示多个参数

  1. <ListBox x:Name="list">
  2. <ListBox.ItemTemplate>
  3. <DataTemplate>
  4. <StackPanel Orientation="Horizontal">
  5. <TextBlock Text="{Binding Name}"></TextBlock>
  6. <TextBlock Text="{Binding Age}"></TextBlock>
  7. <TextBlock Text="{Binding Sex}"></TextBlock>
  8. </StackPanel>
  9. </DataTemplate>
  10. </ListBox.ItemTemplate>
  11. </ListBox>

提醒一下,一般使ObservableCollection类型的列表作为ItemsSource的数据源,因为ObservableCollection实现了INotifyPropertyChanged接口,列表内数据发生了变化,会被通知到使用你这个列表的控件,ui跟着变化。

ObjectDataProvider

有时候要绑定的不是一个属性,而是一个方法计算出来的值。
如果不方便写属性的get,可以用ObjectDataProvider。

首先三个输入框,目的是让t1和t2的值自动相加

  1. <Grid>
  2. <StackPanel>
  3. <TextBox x:Name="t1" Width="100" Height="20" />
  4. <TextBox x:Name="t2" Width="100" Height="20" />
  5. <TextBox x:Name="t3" Width="100" Height="20" />
  6. </StackPanel>
  7. </Grid>

写一个相加的方法,参数这些都要用字符串。

  1. public class S
  2. {
  3. public string Add(string a,string b)
  4. {
  5. return (Convert.ToInt32(a) + Convert.ToInt32(b)).ToString();
  6. }
  7. }

然后绑定

  1. ObjectDataProvider obj = new ObjectDataProvider();
  2. obj.ObjectInstance = new S();//设置对象的实例
  3. obj.MethodName = "Add";//设置方法名
  4. //添加两个方法的参数,默认值是0
  5. obj.MethodParameters.Add("0");
  6. obj.MethodParameters.Add("0");
  7. Binding b1 = new Binding("MethodParameters[0]")
  8. {
  9. Source = obj,
  10. BindsDirectlyToSource = true,
  11. UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
  12. };
  13. Binding b2 = new Binding("MethodParameters[1]")
  14. {
  15. Source = obj,
  16. BindsDirectlyToSource = true,
  17. UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
  18. };
  19. //因为obj就是要绑定的值,所以这里的Path就用.
  20. Binding b3 = new Binding(".") { Source = obj };
  21. //用控件对象的方法SetBinding就把Binding绑定上去了
  22. t1.SetBinding(TextBox.TextProperty, b1);
  23. t2.SetBinding(TextBox.TextProperty, b2);
  24. t3.SetBinding(TextBox.TextProperty, b3);

image.png

RelativeSource

当一个 Binding 有明确的数据来源时我们可以通过为Source或ElementName赋值的办法让Binding 与之关联。
有些时候我们不能确定作为Source的对象叫什么名字,但知道它与作为Binding目标的对象在UI布局上有相对关系,比如控件自己关联自己的某个数据、关联自己某级容器的数据。这时候我们就要使用 Binding 的RelativeSource属性。

XAML布局样子像这样
image.png
逻辑代码像这样

  1. RelativeSource rs = new RelativeSource();
  2. rs.Mode = RelativeSourceMode.FindAncestor;//用什么方式,这里是查找元素,还可以用关联自身的方式
  3. rs.AncestorLevel = 1;//指的是Binding目标控件为起点的层级偏移量,1就是一层
  4. rs.AncestorType = typeof(StackPanel);//指的是寻找那个类型,不是这个类型的会被忽略
  5. Binding binding = new Binding("Name");
  6. binding.RelativeSource = rs;
  7. textBox1.SetBinding(TextBox.TextProperty, binding);

MultiBinding(多路Binding )

有时候UI要显示的数据不是由一个数据源来决定的,这时候就需要用MultiBinding.

MultiBinding 与 Binding一样均以 BindingBase为基类,也就是说,凡是能使用Binding对象的场合都能使用MultiBinding。MultiBinding具有一个名为Bindings 的属性,其类型是Collection,通过这个属性 MultiBinding 把一组 Binding对象聚合起来,处在这个集合中的 Binding 对象可以拥有自己的数据校验与转换机制,它们汇集起来的数据将共同决定传往MultiBinding目标的数据。
image.png
适用场景嘛,比如注册页面,用户要填很多信息,填写完了验证这些信息的合法性,合法就启用注册按钮,不合法就禁用注册按钮。
如果每个输入框控件都是一个对象,而这个输入框的值就是内容属性。建几个Binding,Source就是这个输入框控件。
而注册按钮绑定的是一个MultiBinding,这个MultiBinding又和刚才建立的Binding绑定的,在MultiBinding里面验证输入框的数据源合不合法,如果都合法,就返回true,让注册按钮启用。

其实我更喜欢另一种方式,新建一个类,里面的属性和注册要用到的输入框一一对应,名字,性别,电话等等……把这个类实例化的对象当做数据源,用DataContext的方式绑定给这些控件。这个类里面还有一个属性,这个属性get里面验证信息合法性,返回bool,注册按钮绑定这个属性。之前的属性的set里面再加一个通知属性改变,除了通知本身属性的改变,还要通知注册按钮的这个属性改变。