Binding

    技术2022-06-29  43

    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 =”currentFolderDockPanel .Dock =”TopText =”{Binding ElementName =treeView, Path =SelectedItem.Header}Background =”AliceBlueFontSize= 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提供了可以说是标准的方案,限于篇幅,我单独开了一贴,点这里 查看。


    最新回复(0)