简单说就是是双向绑定。
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属性的东西都要跟着变。
public class Prop: INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");//通知Name属性发生了变化
}
}
//这是实现接口自带的
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 属性变化通知事件
/// </summary>
/// <param name="PropertyName"></param>
public void OnPropertyChanged(string PropertyName)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(PropertyName);
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
2.XAML绑定控件
在XAML中这样绑定,Text是他的内容属性,用{Binding 要绑定的属性}
<TextBox x:Name="name" Text="{Binding Name}">
有一种情况,有时候Binding的值还未刷新,比如说,事件要执行完了Binding的值才刷新,没执行完,Binding的值还没刷新,需要强制刷新,如下写法。
UpdateSourceTrigger,它的类型是UpdateSourceTrigger枚举,可取值为PropertyChanged、LostFocus、Explicit和 Default
<TextBox x:Name="name" Text="{Binding Name,UpdateSourceTrigger=PropertyChanged}">
3.数据源绑定到控件
这一步算是桥梁,在初始化的代码中写道。
Prop prop = new Prop();
DataContext = prop;//DataContext是当前窗体的DataContext,把数据源对象赋值给它,当前窗体的所有控件都能访问这个数据源对象里面数据
纯写C#代码的方法
Binding起始就是个类,上面{Binding xxx,yyy}
其实是Binding类用法的简写,这种简写就是新手不太容易分辨有些什么东西可以写。用代码的方式写Binding,可以F12,F1查看Binding里面有些啥,可以写什么。
WPF里面C#代码永远是核心。
绑定数据首先需要数据源,数据源就用上面的那个。
然后需要控件。这里就用TextBox,这里就只给他取了一个名字
<TextBox x:Name="name" >
在后台代码里面写道
//实例化Binding可以传一个Path,就是绑定哪个属性,
Binding binding = new Binding("Name");
//指定数据源
binding.Source = new Prop();
//数据绑定的方式,双向绑定
binding.Mode = BindingMode.TwoWay;
//数据源更新了怎么触发,属性改变就触发
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
//给TextBox控件绑定上binding
//第一个参数是指定一个依赖属性,就是绑定到拿个属性上,这里是绑定到TextBox控件的Text属性上
//第二个参数就是binding对象
name.SetBinding(TextBox.TextProperty, binding);
好了,绑定就完成了。
至于是双向绑定还是单向绑定等,是由Binding.Mode
决定的。详细看下面的节点 Binding的绑定方向
UI控件联动绑定
除了绑定逻辑代码,还可以把两个控件之间绑定起来,让他们产生关联
如这段XAML,两个控件,一个输入框,一个滑动条,输入框的Text用了Binding,Path
是指绑定哪个属性,ElementName
是指绑定哪个控件。这里TextBox的Text属性绑定了名为slider的控件的Value属性。
也就是slider的Value发生变化,Text跟着变。
Path关键字可以省略,直接写Binding Value也是可以的。
<Grid>
<TextBox x:Name="text" BorderBrush="Black" Width="200" Height="50" Text="{Binding Path=Value ,ElementName=slider}"></TextBox>
<Slider x:Name="slider" Maximum="100" Minimum="0" Width="200" Margin="300,277,300,-176"></Slider>
</Grid>
Binding的绑定方向
Binding在源与目标之间架起了沟通的桥梁,默认情况下数据既能够通过Binding送达目标,也能够从目标返回源(收集用户对数据的修改)。有时候数据只需要展示给用户、不允许用户修改,这时候可以把 Binding 模式更改为从源向目标的单向沟通。Binding还支持从目标向源的单向沟通以及只在 Binding关系确立时读取一次数据,这需要我们根据实际情况去选择。
控制 Binding 数据流向的属性是 Mode,它的类型是BindingMode枚举。BindingMode可取值为TwoWay、OneWay、OnTime、OneWayToSource和 Default。这里的Default 值是指Binding 的模式会根据目标的实际情况来确定,比如若是可编辑的(如TextBox.Text 属性),Default就采用双向模式,若是只读的(如TextBlock.Text)则采用单向模式。
<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
Source
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,就会自动遍历其中的值。
绑定简单类型的容器,直接就可以迭代显示了
<ListBox x:Name="list"></ListBox>
List<string> strs = new List<string>();
strs.Add("AAAA");
strs.Add("BBBB");
strs.Add("CCCC");
list.ItemsSource = strs;
如果数据源有多个属性,要显示其中一个具体的属性
<ListBox x:Name="list" DisplayMemberPath="Name"></ListBox>
List<Stu> s = new List<Stu>();
Stu s1 = new Stu()
{
Name = "张三",
Age = 13,
Sex = "男"
};
Stu s2 = new Stu()
{
Name = "李四",
Age = 34,
Sex = "女"
};
Stu s3 = new Stu()
{
Name = "王五",
Age = 25,
Sex = "男"
};
s.Add(s1);
s.Add(s2);
s.Add(s3);
list.ItemsSource = s;
要显示多个参数
<ListBox x:Name="list">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding Age}"></TextBlock>
<TextBlock Text="{Binding Sex}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
提醒一下,一般使ObservableCollection
类型的列表作为ItemsSource的数据源,因为ObservableCollection 实现了INotifyPropertyChanged接口,列表内数据发生了变化,会被通知到使用你这个列表的控件,ui跟着变化。
ObjectDataProvider
有时候要绑定的不是一个属性,而是一个方法计算出来的值。
如果不方便写属性的get,可以用ObjectDataProvider。
首先三个输入框,目的是让t1和t2的值自动相加
<Grid>
<StackPanel>
<TextBox x:Name="t1" Width="100" Height="20" />
<TextBox x:Name="t2" Width="100" Height="20" />
<TextBox x:Name="t3" Width="100" Height="20" />
</StackPanel>
</Grid>
写一个相加的方法,参数这些都要用字符串。
public class S
{
public string Add(string a,string b)
{
return (Convert.ToInt32(a) + Convert.ToInt32(b)).ToString();
}
}
然后绑定
ObjectDataProvider obj = new ObjectDataProvider();
obj.ObjectInstance = new S();//设置对象的实例
obj.MethodName = "Add";//设置方法名
//添加两个方法的参数,默认值是0
obj.MethodParameters.Add("0");
obj.MethodParameters.Add("0");
Binding b1 = new Binding("MethodParameters[0]")
{
Source = obj,
BindsDirectlyToSource = true,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
Binding b2 = new Binding("MethodParameters[1]")
{
Source = obj,
BindsDirectlyToSource = true,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
//因为obj就是要绑定的值,所以这里的Path就用.
Binding b3 = new Binding(".") { Source = obj };
//用控件对象的方法SetBinding就把Binding绑定上去了
t1.SetBinding(TextBox.TextProperty, b1);
t2.SetBinding(TextBox.TextProperty, b2);
t3.SetBinding(TextBox.TextProperty, b3);
RelativeSource
当一个 Binding 有明确的数据来源时我们可以通过为Source或ElementName赋值的办法让Binding 与之关联。
有些时候我们不能确定作为Source的对象叫什么名字,但知道它与作为Binding目标的对象在UI布局上有相对关系,比如控件自己关联自己的某个数据、关联自己某级容器的数据。这时候我们就要使用 Binding 的RelativeSource属性。
XAML布局样子像这样
逻辑代码像这样
RelativeSource rs = new RelativeSource();
rs.Mode = RelativeSourceMode.FindAncestor;//用什么方式,这里是查找元素,还可以用关联自身的方式
rs.AncestorLevel = 1;//指的是Binding目标控件为起点的层级偏移量,1就是一层
rs.AncestorType = typeof(StackPanel);//指的是寻找那个类型,不是这个类型的会被忽略
Binding binding = new Binding("Name");
binding.RelativeSource = rs;
textBox1.SetBinding(TextBox.TextProperty, binding);
MultiBinding(多路Binding )
有时候UI要显示的数据不是由一个数据源来决定的,这时候就需要用MultiBinding.
MultiBinding 与 Binding一样均以 BindingBase为基类,也就是说,凡是能使用Binding对象的场合都能使用MultiBinding。MultiBinding具有一个名为Bindings 的属性,其类型是Collection
适用场景嘛,比如注册页面,用户要填很多信息,填写完了验证这些信息的合法性,合法就启用注册按钮,不合法就禁用注册按钮。
如果每个输入框控件都是一个对象,而这个输入框的值就是内容属性。建几个Binding,Source就是这个输入框控件。
而注册按钮绑定的是一个MultiBinding,这个MultiBinding又和刚才建立的Binding绑定的,在MultiBinding里面验证输入框的数据源合不合法,如果都合法,就返回true,让注册按钮启用。
其实我更喜欢另一种方式,新建一个类,里面的属性和注册要用到的输入框一一对应,名字,性别,电话等等……把这个类实例化的对象当做数据源,用DataContext的方式绑定给这些控件。这个类里面还有一个属性,这个属性get里面验证信息合法性,返回bool,注册按钮绑定这个属性。之前的属性的set里面再加一个通知属性改变,除了通知本身属性的改变,还要通知注册按钮的这个属性改变。