winform有binding, WPF也有binding,区别在哪呢?这里暂时不提。以前也检查接触WPF binding, 但为什么过段时间就忘记了呢?
可能主要原因自己的知识体系不够完善吧,下面我先总结下binding的用法,然后再深入挖掘binding的原理,最后再总结,这样希望自己能够对binding达到非常深刻的理解。
binding的用法:
1)绑定控件属性。下面是将一个Slider的值绑定到一个Textbox上。
<TextBox Grid.Row="1" Text="{Binding Path=Value,ElementName=slider1}"></TextBox>
<Slider Grid.Row="2" x:Name="slider1" Maximum="100" Minimum="0"></Slider>
2)绑定多级路径,比如属性的属性,索引器等。
<TextBox Grid.Row="2" x:Name="textbox2" Text="{Binding Path=Text.Length,ElementName=textbox1,Mode=OneWay}"
BorderBrush="Black" Margin="5"></TextBox>
<TextBox Grid.Row="3" x:Name="textbox3" Text="{Binding Path=Text[4],ElementName=textbox1,Mode=OneWay}"
BorderBrush="Red" Margin="5"></TextBox>
3)还可以判定集合,甚至是集合的集合元素。
List<string> stringList = new List<string>(){"xiaowang","dabing","qizhi"};
this.txtCollection1.SetBinding(TextBox.TextProperty, new Binding("/") { Source = stringList });
this.txtCollection2.SetBinding(TextBox.TextProperty, new Binding("/Length") { Source = stringList,
Mode = BindingMode.OneWay });
this.txtCollection3.SetBinding(TextBox.TextProperty, new Binding("/[2]") { Source = stringList, Mode = BindingMode.OneWay });
this.txtCollection4.SetBinding(TextBox.TextProperty, new Binding("/Name") { Source = countryList });
this.txtCollection5.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/Name")
{
Source = countryList,
});
this.txtCollection6.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList/Name") { Source = countryList });
4)还可以没有Path的Binding,这里的路径直接用一个.就可以了。
<TextBox Grid.Row="1" Text="{Binding .,Source={StaticResource ResourceKey=myString}}"></TextBox>
5)使用DataContext作为源:
<StackPanel Grid.Row="1">
<StackPanel.DataContext>
<local:Student Id="4" Age="18" Name="Jim"></local:Student>
</StackPanel.DataContext>
<TextBox Text="{Binding Path=Id}" Margin="5"/>
<TextBox Text="{Binding Path=Name}" Margin="5"/>
<TextBox Text="{Binding Path=Age}" Margin="5"/>
</StackPanel>
6)为列表控件指定ItemsSource
List<Student> stuList = new List<Student>()
{
new Student(){ Age = 18, Id = 1, Name = "Xiao"},
new Student(){ Age = 19, Id = 4, Name = "smao"},
new Student(){ Age = 20, Id = 3, Name = "duo"},
new Student(){ Age = 21, Id = 2, Name = "shao"},
};
this.listBoxStudents.ItemsSource = stuList;
this.listBoxStudents.DisplayMemberPath = "Name";
Binding binding = new Binding("SelectedItem.Id") { Source = this.listBoxStudents };
this.txtId.SetBinding(TextBox.TextProperty, binding);
this.listBoxStudents.SelectedIndex = 0;
7)为列表控件指定外衣来绑定集合,绑定的代码是一样的。
<ListBox x:Name="listBoxStudents2" Grid.Row="4">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Id}" Width="30"/>
<TextBlock Text="{Binding Path=Name}" Width="60"/>
<TextBlock Text="{Binding Path=Age}" Width="30"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
8)DataTable作为源。
<StackPanel Grid.Row="1">
<ListView x:Name="listViewStudents">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Path=Id}"/>
<GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{Binding Path=Name}"/>
<GridViewColumn Header="Age" Width="60" DisplayMemberBinding="{Binding Path=Age}"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
this.listViewStudents.ItemsSource = dt.DefaultView;
9)把类的方法包装成数据作为源ObjectDataProvider
ObjectDataProvider odp = new ObjectDataProvider();
odp.ObjectInstance = new Calculator();//方法类
odp.MethodName = "Add"; //方法名
odp.MethodParameters.Add("0"); //添加方法参数
odp.MethodParameters.Add("1");
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 };
Binding bindingToResult = new Binding(".") { Source = odp };
txtArg1.SetBinding(TextBox.TextProperty, bindingToArg1);//把控件当做目标,源是最终的方法类
txtArg2.SetBinding(TextBox.TextProperty, bindingToArg2);
txtResult.SetBinding(TextBox.TextProperty, bindingToResult);//控件依然是目标,如果是.就是provider的数据
10)自身控件或者上级控件的属性作为源的。
<Grid Grid.Row="2" Grid.Column="2" Background="Black" Margin="15">
<DockPanel x:Name="Dock" Background="Blue" Margin="20">
<Grid Background="Orange" Margin="20">
<StackPanel>
<TextBox Text="{Binding Path=Name, RelativeSource={RelativeSource AncestorLevel=1,AncestorType= {x:Type DockPanel}}}" Margin="0"></TextBox>
<TextBox x:Name="txt1" Text="{Binding Path=Name, RelativeSource={RelativeSource Mode=Self}}" Margin="0"></TextBox>
</StackPanel>
</Grid>
</DockPanel>
</Grid>
11)使用XML数据作为Binding源。
12)使用LINQ检索结果作为Binding的源。
13)binding的数据校验:
private void BindingValidation()
{
Binding binding = new Binding("Value") { Source = sliderValidation, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
RangeValidationRule rvr = new RangeValidationRule();
rvr.ValidatesOnTargetUpdated = true;
binding.NotifyOnValidationError = true;
binding.ValidationRules.Add(rvr);
this.txtValidation.SetBinding(TextBox.TextProperty, binding);
this.txtValidation.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(this.ValidationError));
}
private void ValidationError(object sender, RoutedEventArgs e)
{
if (Validation.GetErrors(this.txtValidation).Count > 0)
{
this.txtValidation.ToolTip = Validation.GetErrors(this.txtValidation)[0].ErrorContent.ToString();
}
}
14)binding的转换:
<ListBox x:Name="listBoxConvert" Grid.ColumnSpan="2">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="20" Height="20" Source="{Binding Path=Category,Converter={StaticResource cts}}"></Image>
<TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"></TextBlock>
<CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource stnb}}"></CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public class CategoryToSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Category c = (Category)value;
switch (c)
{
case Category.Bomber:
return @"ImagesBomber.jpg";
case Category.Fighter:
return @"ImagesFighter.jpg";
default: return null;
} //throw new NotImplementedException(); }
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{ throw new NotImplementedException(); }
}
public class StateToNullableBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
State s = (State)value;
switch (s)
{
case State.Available:
return true;
case State.Locked:
return false;
default:
return null;
}
}
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;
case null:
default:
return State.Unknown;
}
}
}
15)多路binding:
public void SetMultiBinding()
{
Binding b1 = new Binding("Text") { Source = this.txtMult1 };
Binding b2 = new Binding("Text") { Source = this.txtMult2 };
Binding b3 = new Binding("Text") { Source = this.txtMult3 };
Binding b4 = new Binding("Text") { Source = this.txtMult4 };
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.btnSubmit.SetBinding(Button.IsEnabledProperty, mb);
}
public class LogonMultiBindingConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
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(); } }
16. Binding的BindingMode:
<Slider x:Name="sliderMode" Grid.Row="1" Grid.ColumnSpan="2" Value="48" Minimum="0" Maximum="100"></Slider>
<TextBlock Grid.Row="2" Text="OneWay:"></TextBlock>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Path=Value, Mode=OneWay, ElementName=sliderMode}"></TextBox>
<TextBlock Grid.Row="3" Text="TwoWay:"></TextBlock>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Path=Value, Mode=TwoWay, ElementName=sliderMode}"></TextBox>
<TextBlock Grid.Row="4" Text="OneTime:"></TextBlock>
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding Path=Value, Mode=OneTime, ElementName=sliderMode}"></TextBox>
<TextBlock Grid.Row="5" Grid.Column="0" Text="OneWayToSource:"></TextBlock>
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Path=Value, Mode=OneWayToSource, ElementName=sliderMode}"></TextBox>
<TextBlock Grid.Row="6" Text="Default:"></TextBlock>
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding Path=Value, Mode=Default, ElementName=sliderMode}"></TextBox>
在讲完binding的使用后,我们接下来深入剖析下binding:
首先看一看binding的一些重要成员:
binding的源:
1)RelativeSource。相对资源。
2)ElementName:元素名,在指定源的时候,如果是控件,可以指定这个。
3)Source:指定binding的源。
4)StaticSource:静态资源.
Path: PropertyPath类型,指定源的路径。
bindingMode:指定绑定的模式。
1)TwoWay.目标和源都可以影响对方。
2) OneWay.只有源影响目标。
3)OneTime.只有程序启动时或者DataContext进行更改时更新目标属性。
4)OneWayToSource.在目标属性更改时更新源属性。
5)Default.使用目标属性的默认Mode值。比如TextBox.Text就是双向的,TextBlock.Text就是单向的。
UpdateSourceTrigger:指定源更新的时期
1)PropertyChanged. 属性变化的时候更新
2)LostFocus. 失焦的时候更新。
3)Explicit.
4)Default. 缺省。
NotifyOnSourceUpdated NotifyOnTargetUpdated 这两个Bool属性。
如果为true表示当源或者目标更新后binding会激发相应的SourceUpdated和TargetUpdated事件。
实际工作中,我们可以监听这两个事件来找出有哪些数据或者控件被更新了。
ValidationRules.
ValidationRule的对象集合,这里是指定绑定的验证规则,在进行验证的时候,默认都是认为来自source的数据总是正确的,不需要验证,如果需要验证就要
设置ValidatesOnTargetUpdated(因为source是会引起Target更新),如果验证的时候的错误需要发出去,需要设置NotifyOnValidationError属性为true,那么
这个信号就会以binding对象的target为起点在UI树上传播。
Converter.
数据转换功能,当源与目标的数据类型不一样的时候,就需要使用。需要实现IValueConverter接口,
其中:
Convert是转换源到目标。
ConvertBack:转换目标到源。
到这里为止,我可能还只是对binding的使用有了一个大致的了解,binding与依赖属性,依赖对象之间的关系如何,为什么binding了以后,数据驱动的原理是什么?这些都是一个疑问,我这里只是大致了解了一些类:
Binding是BindingBase的子类。BindingExpression是BindingExpressionBase的子类,BindingOperations是一个静态类。
我这样大致猜想一下:当我们Binding的时候,把源和目标绑定在一起,目标必须是依赖对象,源可以是依赖对象也可以是一般的对象,但必须公开了属性。Binding完成后,如果源是依赖属性或者一般类实现了INotifyPropertyChanged接口,那么肯定就会在弱引用管理器中添加监听者与监听对象,当属性有改变的时候,就会触发相应的事件,使得数据同步得到更新,如果一般的类没有实现这个接口,自然是得不到更新的。
知道了这些原理,自然就知道了在不同的设置下面源于目标如何发生变化的情况:
假设源是一个A类对象,其属性P1触发了事件PropertyChanged,P2没有触发事件PropertyChanged,假设分别绑定到了控件C1,C2。
我们来分析一下不同模式下的情况:
OneWay: P1改变会影响C1,P2改变不会影响C2,C1改变不会影响P1,C2改变不会影响P2。
TwoWay: P1与C1相互影响,P2与C2互不影响。
OneTime: P1只会影响C1一次,P2与C2互不影响,C1改变不会影响P1。
OneWayToSource: P1改变不会影响C1,P2改变不会影响C2,C1改变影响P1,C2改变不会影响P2。
另外假设源是一个依赖对象,其依赖属性作为路径,那么其在不同模式下的情况就跟上面的假设中P1与C1的情况是一样的。
代码如下
http://files.cnblogs.com/files/monkeyZhong/WPFBindingDemo.zip