WPF Binding
WPF里分三种Binding:Binding, PriorityBinding, MultiBinding,这三种Binding的基类都是BindingBase,而BindingBase又继承于MarkupExtension
Binding
提供对绑定定义的高级别访问,绑定将绑定目标对象(通常为 WPF 元素)的属性与任何数据源(例如数据库、XML 文件或包含数据的任何对象)连接起来。
常见的使用Binding的代码:
C#
Binding binding = new Binding(); // Set source object binding.Source = treeView; // Set source property binding.Path = new PropertyPath("SelectedItem.Header"); // Attach to target property currentFolder.SetBinding(TextBlock.TextProperty, binding);
XAML:
<TextBlock x:Name=”currentFolder” DockPanel.Dock=”Top” Text=”{ Binding ElementName=treeView, Path=SelectedItem.Header}” Background=”AliceBlue” FontSize=”16”/>
所有FrameworkElement都包含SetBinding方法:SetBinding(DependencyProperty dp, String path), SetBinding(DependencyProperty dp, BindingBase binding),可以看出,FrameworkElement中的SetBinding只对DependencyProperty有效。
另一种设置Binding的方法是:BindingOperations.SetBinding(currentFolder, TextBlock.TextProperty, binding);
BindingOperations.SetBinding的原型是
public static BindingExpressionBase SetBinding( DependencyObject target, DependencyProperty dp, BindingBase binding )
第一个参数是DependencyObject,所以我们可以对自定义DependencyObject或者继承自DependencyObject的类进行绑定。当然第二个参数还是DependencyProperty。
清除Binding:
BindingOperations.ClearBinding(currentFolder, TextBlock.TextProperty); //删除currentFolder上的TextBlock.TextProperty绑定
BindingOperations.ClearAllBindings(currentFolder); //删除currentFolder上的所有绑定
直接对dependency property赋值也可以解除binding, 不过只对单向binding有效。
Bingding的源:
有三个属性用来设置源:ElementName(string)、Source(Object) 和 RelativeSource(RelativeSource)。注:这三个只能指定一个,否则异常。
ElementName: 源为一个元素(Element),这里用的是此元素中设置的Name属性。
Source:以object作为源。<TextBlock Text=“{Binding Source={StaticResource myDataSource}, Path=PersonName}”/>
RelativeSource: 源相对于绑定目标的位置。
源是元素本身:{Binding RelativeSource={RelativeSource Self}}
源是Tempalte中元素的Parent:{Binding RelativeSource={RelativeSource TemplatedParent}}
源是绑定以collection形式的前一个数据:{Binding RelativeSource={RelativeSource PreviousData}},MSDN上关于PreviousData的说明并不多,这里有一篇文章可以参考
以上三项为RelativeSource中的Static值,使用这些值可以减少内存开销
源是Ancestor(可能比parent还高):{Binding RelativeSource={RelativeSource FindAncestor,
AncestorLevel=n, AncestorType={x:Type desiredType}}}
Path:
Binding中的Path是 PropertyPath对象。
-
在最简单的情况下,Path 属性值是要用于绑定的源对象的属性名称,如 Path=PropertyName。
-
通过类似于 C# 中使用的语法,可以指定属性的子属性。例如,子句 Path=ShoppingCart.Order 将绑定设置为对象的子属性 Order 或属性 ShoppingCart。
-
若要绑定到附加属性,请将附加属性用括号括起。例如,若要绑定到附加属性 DockPanel.Dock,则语法为 Path=(DockPanel.Dock)。
-
在应用了索引器的属性名称之后的方括号内,可以指定属性的索引器。例如,子句 Path=ShoppingCart[0] 将绑定设置为与属性的内部索引处理文本字符串“0”的方式对应的索引。此外,还支持多个索引器。
-
在 Path 子句中可以同时使用索引器和子属性,例如,Path=ShoppingCart.ShippingInfo[MailingAddress,Street]。
-
在索引器内部,可以有多个由逗号 (,) 分隔的索引器参数。可以使用圆括号指定每个参数的类型。例如,可以使用 Path=”[(sys:Int32)42,(sys:Int32)24]”,其中 sys 映射到 System 命名空间。
-
如果源为集合视图,则可以用斜杠 (/) 指定当前项。例如,子句 Path=/ 设置到视图中当前项的绑定。如果源为集合,则此语法指定默认集合视图的当前项。
-
可以结合使用属性名和斜杠来遍历作为集合的属性。例如,Path=/Offices/ManagerName 指定源集合的当前项,该源集合包含同样是集合的 Offices 属性。其当前项是包含 ManagerName 属性的对象。
也可以使用句点 (.)路径绑定到当前源。例如,Text=”{Binding}” 等效于 Text=”{Binding Path=.}”。
BindingExpression
Binding 类是高级别类。BindingExpression 类是基础对象,用于保持绑定源与绑定目标之间的连接。Binding 中包含可在多个 BindingExpression 对象之间共享的所有信息。也就是说,可以把一个Binding对象绑定对n个元素上,而针对这n个元素,分别有相应的n个BindingExpresion对象。
Binding可以直接绑定普通的.net实例,比如int值。但是如果后台改变int值了,前台不能显示改变后的值,这时可以调用UpdateTarget()方法更新绑定。如下:BindingExpression be = button.GetBindingExpression(Button.ContentProperty);
be.UpdateTarget();还有UpdateSource方法用来更新源。
绑定到.net属性/对象:
上面提到Binding绑到普通的.net属性,如果source变化了,UI上是不会显示的,除了用BindingExpression每次显式更新Target外,还可以使用如下技术:
绑定到单个对象需实现INotifyPropertyChanged接口,这个接口只有一个成员:
event PropertyChangedEventHandler PropertyChanged
实现INotifyPropertyChanged的示例如下:
using System.ComponentModel; namespace SDKSample { // This class implements INotifyPropertyChanged // to support one-way and two-way bindings // (such that the UI element updates when the source // has been changed dynamically) public class Person : INotifyPropertyChanged { private string name; // Declare the event public event PropertyChangedEventHandler PropertyChanged; public Person() { } public Person(string value) { this.name = value; } public string PersonName { get { return name; } set { name = value; // Call OnPropertyChanged whenever the property is updated OnPropertyChanged("PersonName"); } } // Create the OnPropertyChanged method to raise the event protected void OnPropertyChanged(string name) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(name)); } } } } 或者显式实现INotifyPropertyChanged:
#region INotifyPropertyChanged Members event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { add { this.PropertyChanged = (PropertyChangedEventHandler)Delegate.Combine(this.PropertyChanged, value); } remove { this.PropertyChanged = (PropertyChangedEventHandler)Delegate.Remove(this.PropertyChanged, value); } } #endregion
看了上面代码着实没看出source值改变了,前台是通过什么机制反映的,正常的情况下公开了一个事件,必须有一个对此事件的实现体,而上面代码并没有实现PropertyChanged的方法。
我猜想是Binding内部获取了这个接口并对PropertyChanged进行了赋值,因为在debug时,这个事件确实被赋值的,而赋值前的Stack是External Code调用的。
绑定到集合需实现INotifyCollectionChanged,但是推荐使用ObservableCollection<T>,这个类实现了INotifyCollectionChanged和INotifyPropertyChanged。
附:当绑定到普通的.net属性时,WPF使用反射取得source的值,当对象实现ICustomTypeDescriptor时,WPF使用这个接口取得值,性能上会有所提升。
DataContext:
DataContext在共享资源时最有用。
<StackPanel x:Name="parent" DataContext="{StaticResource photos}"> <Label x:Name="numItemsLabel" Content="{Binding Path=Count}" DockPanel.Dock="Bottom"/>
也可以在代码这么写parent.DataContext = photos;
Value Converters:
IValueConverter可以在绑定时加入自己的逻辑,很好。
public class RawCountToDescriptionConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { // Let Parse throw an exception if the input is bad int num = int.Parse(value.ToString()); return num + (num == 1 ? " item" : " items"); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } }
IValueConverter就两个方法需要自己实现,Convert和ConvertBack,一个转过来,一个转过去。
XAML代码如下
<Label Background="{Binding Path=Count, Converter={StaticResource myConverter}, Source={StaticResource photos}}"/>
这里的myConverter是个resource,需要在xaml中预先定义:
<Window.Resources> <local:CountToBackgroundConverter x:Key="myConverter"/> </Window.Resources>
Path对应的Count值会作为第一个参数value传给Convert方法。
注意,返回的值一定要是绑定时对应的值,比如绑定时需要绑到Geometry类上,那么Convert返回的也必须是Geometry类。
Convert方法还带有一个parameter参数,可以在xaml中这么使用
<Label Background="{Binding Path=Count, Converter={StaticResource myConverter}, ConverterParameter=Yellow, Source={StaticResource photos}}"/>
ConverterParameter是object类型。
C#代码中就可以得到parameter的值了。TIP:
可以用Binding.DoNothing作返回值,以指示绑定引擎不要执行任何操作。
可用使用[ValueConversion(typeof(DateTime), typeof(String))]来标识Converter要转化和返回的值类型,第一个参数是soure,第二个参数是target。这样在编译时,如果类型不匹配的话,编译器会抛出异常:error CS0592: Attribute ‘ValueConversion’ is not valid on this declaration type. It is only valid on ‘class’ declarations..net自带一些converter,比如常用的BooleanToVisibilityConverter,可以根据checkbox是否勾上来隐藏其他控件。
在collection中使用converter:使用DateTemplate,在其中使用Converter。(也可以使用Converter对整个collection进行转化,但是可能效率不好)
Binding.Mode
指示源和目标间数据流的方向。
OneWay 源更新时,目标也更新
TwoWay 源更新时目标也更新,或者目标更新时同时更新源
OneTime 仅当应用程序启动时或 DataContext 进行更改时更新目标属性。绑一次就不更维护更新,目标相当于源的一次性镜像
OneWayToSource 目标更新时更新源,和OneWay相反大部分WPF自带的控件的dependency property默认的是OneWay,像TextBox.Text默认的是TwoWay。
值得注意的事,只读属性只能设置成OneWay,不能是TwoWay,否则运行时异常。对于 OneWay 或 TwoWay 绑定,对源的动态更改不会自动传播到目标。必须在源对象上实现 INotifyPropertyChanged 接口。
对于 TwoWay 绑定,对目标的更改不会自动传播到源,除非绑定目标是 Text 属性。在这种情况下,更新仅在 TextBox 失去焦点时发生。
对于 OneTime 和 OneWay 绑定,对 SetValue 的调用会自动更改目标值并删除绑定。再次提醒,源要实现了INotifyPropertyChanged 接口才能把改变反映到目标上。
OneWayToSource 用于多个目标更改一个源的情况,可以想像成多人录入。或者用来实现源和目标倒置的情况。
Binding.UpdateSourceTrigger
指示使用TwoWay或OneWayToSource时,目标在什么情况下更新源。有三个枚举值
PropertyChanged:目标属性改变时更新源
LostFocus:失去焦点时更新源
Explicit:只有在显式调用BindingExpression.UpdateSource方法时才更新源。BindingExpression可以通过BindingOperations.GetBindingExpression或FrameworkElement.GetBindingExpression方法获得Binding类中提供了SourceUpdated和TargetUpdated事件,可以用来记些log,不过必须相应的NotifyOnSourceUpdated或NotifyOnTargetUpdated设置成true才会激发事件。
Binding的验证
-
使用 WPF 数据绑定模型可以将 ValidationRules 与 Binding 对象相关联。验证在从绑定目标到绑定源的值传输过程中调用转换器之前发生。下面描述了验证过程:
-
在将值从目标属性传输到源属性时,数据绑定引擎首先移除可能已添加到所绑定元素的 Validation.Errors 附加属性的任何 ValidationError。然后,数据绑定引擎检查是否为该 Binding 定义了自定义 ValidationRule;如果定义了自定义验证规则,那么它将调用每个 ValidationRule 上的 Validate 方法,直到其中一个规则出错或者全部规则都通过为止。
-
如果某个自定义规则未通过,则绑定引擎会创建一个 ValidationError 对象,并将该对象添加到绑定元素的 Validation.Errors 集合。如果 Validation.Errors 不为空,则元素的 Validation.HasError 附加属性被设置为 true。此外,如果 Binding 的 NotifyOnValidationError 属性设置为 true,则绑定引擎将引发该元素上的 Validation.Error 附加事件。
-
如果所有规则都通过,则绑定引擎会调用转换器(如果存在的话)。
-
如果转换器通过,则绑定引擎会调用源属性的 setter。
-
如果绑定具有与其关联的 ExceptionValidationRule,并且在步骤 4 中引发异常,则绑定引擎将检查是否存在 UpdateSourceExceptionFilter。您可以选择使用 UpdateSourceExceptionFilter 回调来提供用于处理异常的自定义处理程序。如果未对 Binding 指定 UpdateSourceExceptionFilter,则绑定引擎将对异常创建 ValidationError 并将其添加到绑定元素的 Validation.Errors 集合中。
还应注意,任何方向(目标到源或源到目标)上的有效值传输操作都将清除 Validation.Errors 附加属性。
.net提供了三种ValidationRule:
1. CustomerValidationRule 用户提供的Rule
2. ExceptionValidationRule 更新源时,如果有异常(比如类型不匹配)或不满足条件,会向UI上报异常
3. DataErrorValidationRule 更新源时,如果值不满足条件(无法判断值类型异常),会向UI上报异常。要求对象继承自IDataErrorInfo,并且异常是在该对象中抛出的,下面会有示例说明下面的示例来自msdn,显示了Binding Validation的使用:
图中第一个TextBox使用了自定义的ErrorTemplate,在TextBox左边显示一个红色的叹号
第二个TextBox使用的是系统默认的ErrorTemplate,把TextBox边框用红色表示
第三个TextBox使用的是ExceptionValidationRule,这个类在目标更新源时,发生异常(这里是输入是string,而源需要的是int,而引起的异常)时,把异常抛到UI,如果Binding有UpdateSourceExceptionFilter,会调用该方法(使用户可以更人性化的显示异常或做些自己的事),结果如图2。这个步骤解释的是上面第5点。
如果更新源时出现异常,而没有使用ExceptionValidationRule,那么即使输入的不符合条件,界面上也不会有任何提示。
另一种在UI上显示更新源异常的方案是使用Binding.ValidatesOnExceptions属性,如果设置为True,那么也会显示异常,默认是False。ValidatesOnExceptions和ExceptionValidationRule作用是相同的,是.net3.5中新增的一属性。如果两者同时存在,ValidatesOnExceptions为False,也会显示异常的。
<Window
xmlns=“http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=“http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:c=“clr-namespace:SDKSample”
x:Class=“SDKSample.Window1”
Title=“Binding Validation Sample”
SizeToContent=“WidthAndHeight”
ResizeMode=“NoResize”>
<Window.Resources>
<c:MyDataSource x:Key=“ods”/>
<ControlTemplate x:Key=“validationTemplate”>
<DockPanel>
<TextBlock Foreground=“Red” FontSize=“20”>!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate><Style x:Key=“textBoxInError” TargetType=“{x:Type TextBox}”>
<Style.Triggers>
<Trigger Property=“Validation.HasError” Value=“true”>
<Setter Property=“ToolTip”
Value=“{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}”/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources><Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition/>
<RowDefinition />
</Grid.RowDefinitions><TextBlock
Grid.Row=“0” Grid.ColumnSpan=“2”
FontSize=“20” Margin=“8”
Text=“Enter a number between 21-130 or there will be a validation error:”/><Label Grid.Column=“0” Grid.Row=“1” FontSize=“15” Margin=“2”
Target=“{Binding ElementName=textBox1}”>TextBox with _custom ErrorTemplate and ToolTip:</Label>
<TextBox Name=“textBox1” Width=“50” FontSize=“15”
Validation.ErrorTemplate=“{StaticResource validationTemplate}”
Style=“{StaticResource textBoxInError}”
Grid.Row=“1” Grid.Column=“1” Margin=“2”>
<TextBox.Text>
<Binding Path=“Age” Source=“{StaticResource ods}”
UpdateSourceTrigger=“PropertyChanged” >
<Binding.ValidationRules>
<c:AgeRangeRule Min=“21” Max=“130”/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox><Label Grid.Row=“2” Grid.Column=“0” FontSize=“15” Margin=“2”
Target=“{Binding ElementName=textBox2}”>TextBox with _default ErrorTemplate:</Label>
<TextBox Name=“textBox2” Width=“50” FontSize=“15”
Grid.Row=“2” Grid.Column=“1” Margin=“2”>
<TextBox.Text>
<Binding Path=“Age2” Source=“{StaticResource ods}”
UpdateSourceTrigger=“PropertyChanged” >
<Binding.ValidationRules>
<c:AgeRangeRule Min=“21” Max=“130”/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox><TextBlock Grid.Row=“3” Grid.ColumnSpan=“3” FontSize=“20” Margin=“8”
Text=“The following TextBox uses the ExceptionValidationRule and UpdateSourceExceptionFilter handler:”/>
<Label Grid.Row=“4” Grid.Column=“0” FontSize=“15” Margin=“2”
Target=“{Binding ElementName=textBox3}”>TextBox with UpdateSourceExceptionFilter _handler:</Label>
<TextBox Name=“textBox3” Width=“50” FontSize=“15”
Grid.Row=“4” Grid.Column=“1” Margin=“2”
Validation.ErrorTemplate=“{StaticResource validationTemplate}”
Style=“{StaticResource textBoxInError}”>
<TextBox.Text>
<Binding Path=“Age3” Source=“{StaticResource ods}”
UpdateSourceTrigger=“PropertyChanged”>
<Binding.ValidationRules>
<ExceptionValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<CheckBox Name=“cb” FontSize=“15” HorizontalAlignment=“Left”
Grid.Row=“4” Grid.Column=“2” Margin=“5”
Checked=“UseCustomHandler” Unchecked=“DisableCustomHandler”>Enable Custom Handler (see ToolTip)</CheckBox>
</Grid>
</Window>public class AgeRangeRule : ValidationRule { private int _min; private int _max; public AgeRangeRule() { } public int Min { get { return _min; } set { _min = value; } } public int Max { get { return _max; } set { _max = value; } } public override ValidationResult Validate(object value, CultureInfo cultureInfo) { int age = 0; try { if (((string)value).Length > 0) age = Int32.Parse((String)value); } catch (Exception e) { return new ValidationResult(false, "Illegal characters or " + e.Message); } if ((age < Min) || (age > Max)) { return new ValidationResult(false, "Please enter an age in the range: " + Min + " - " + Max + "."); } else { return new ValidationResult(true, null); } } }
public partial class Window1 : Window { public Window1() { InitializeComponent(); } void UseCustomHandler(object sender, RoutedEventArgs e) { BindingExpression myBindingExpression = textBox3.GetBindingExpression(TextBox.TextProperty); Binding myBinding = myBindingExpression.ParentBinding; myBinding.UpdateSourceExceptionFilter = new UpdateSourceExceptionFilterCallback(ReturnExceptionHandler); myBindingExpression.UpdateSource();//加入UpdateSourceExceptionFilter后,更新源,以触发异常 } void DisableCustomHandler(object sender, RoutedEventArgs e) { // textBox3 is an instance of a TextBox // the TextProperty is the data-bound dependency property Binding myBinding = BindingOperations.GetBinding(textBox3, TextBox.TextProperty); myBinding.UpdateSourceExceptionFilter -= new UpdateSourceExceptionFilterCallback(ReturnExceptionHandler); BindingOperations.GetBindingExpression(textBox3, TextBox.TextProperty).UpdateSource(); } object ReturnExceptionHandler(object bindingExpression, Exception exception) { return "This is from the UpdateSourceExceptionFilterCallBack."; } }
public class MyDataSource { private int _age; private int _age2; private int _age3; public MyDataSource() { Age = 0; Age2 = 0; } public int Age { get { return _age; } set { _age = value; } } public int Age2 { get { return _age2; } set { _age2 = value; } } public int Age3 { get { return _age3; } set { _age3 = value; } } }
ValidationRule.ValidationStep用来设置rule是什么时候被调用:
RawProposedValue 在进行任何转换之前运行 ValidationRule。ConvertedProposedValue 在转换了值后运行 ValidationRule。
UpdatedValue 在更新了源后运行 ValidationRule。
CommittedValue 在将值提交到源后运行 ValidationRule。
默认值是RawProposedValue。
DataErrorValidationRule 的示例如下:
<Binding Path="Age" Source="{StaticResource data}" ValidatesOnExceptions="True" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <!--DataErrorValidationRule checks for validation errors raised by the IDataErrorInfo object.--> <!--Alternatively, you can set ValidationOnDataErrors="True" on the Binding.—> <DataErrorValidationRule/> </Binding.ValidationRules> </Binding>
public class Person : IDataErrorInfo { private int age; public int Age { get { return age; } set { age = value; } } public string Error { get { return null; } } public string this[string name] { get { string result = null; if (name == "Age") { if (this.age < 0 || this.age > 150) { result = "Age must not be less than 0 or greater than 150."; } } return result; } } }
和ExceptionValidationRule与ValidatesOnExceptions这对一样,DataErrorValidationRule 也有一个叫ValidatesOnDataErrors的属性与之配对。
-
今天的文章WPF Binding分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/11975.html