21、wpf之绑定使用小记

前言:只要是涉及到MVVM开发,就不得不提Binding,现将Binding的一些知识点总结下,方便以后温故知新。

一、简介

Data Binding作用:Data Binding在MVVM模式下就像个快递员,ViewModel中处理好的数据就是绑定源,Binding这个快递员从这个绑定源中拿数据给绑定目标,一般是UI控件,绑定目标拿着这些数据更新自己的生活。有时候绑定目标也会退回一些东西给绑定源,也就是UI界面数据发生变化会回传到ViewModel中。所以说Binding是用来实现界面控件的属性后台数据之间的绑定,通过这种形式将前台界面与后台数据联系在一起达到界面与数据解耦的目的。

由图可知,一个绑定由四部分组成:绑定目标目标属性绑定源源属性。 

目标属性必须是依赖属性,这就意味着,不能绑定字段。UIElement对象的大部分属性是依赖属性并且这些大部分属性(除了只读)默认支持数据绑定的。 (只有 DependencyObject 类型可以定义依赖项属性,并且所有 UIElement 对象都派生自 DependencyObject.)

1.1 Binding源

根据数据驱动的精神,Binding的源就是数据的源头,数据的源头只要是一个对象,并且通过属性公开了自己的数据,既可作为Binding的源。包含数据的对象比比皆是,但必须为Binding的Source指定合适的对象Binding才能正常工作。

Binding的源

  • 把普通CLR类型单个对象指定为Source。包括.NET Framework自带类型的对象和用户自定义类型的对象。
  • 把普通CLR集合类型对象指定为Source。包括数组、List<T>、ObservableCollection<T>等集合类型。
  • 把ADO.NET数据对象指定为Source。包括DataTable和DataView对象。
  • 把依赖对象指定为Source。
  • 把容器DataContext指定为Source。

ContentControl类型,比如TextBox、Botton等,更多的是绑定单个属性,这个属性要么后台自定义,要么绑定UI控件的某个属性。

列表类控件更多的是绑定集合,这个集合可能来自于数据库,也可能来自于xml、json等配置文件。

微软针对XML绑定和对象绑定,提供了两个辅助类XmlDataProvider和ObjectDataProvider。

这是Binding类的源属性,可以看出来,Source属性可以设置的数据源对象类型很多,基本上可以理解为两类,一类是UI控件,一种是自定义的后台数据对象。

1.2 源属性

数据源就是一个对象,一个对象本身可能会有很多数据,这些数据又通过属性暴露给外界。那么其中哪个元素是你想通过Bindidng送达UI元素的呢?换句话说,UI元素关心的是哪个属性值的变化呢?这个属性值称之为Binding的路径(Path)。但光有属性还不行,Binding是一种自动机制,当值变化后属性要有能力通知Binding,让Binding把变化传递给UI元素。怎样才能让一个属性具备这种通知Binding值变化改变的能力呢?方法使在属性的set语句中激发一个PropertyChanged事件。这个事件不需要我们自己声明,我们要做的事是让作为数据源的类实现System.ComponentModel

名称空间中的INotifyPropertyChanged接口。当为Binding设置了数据源之后,Binding就会自动侦听来自这个接口PropertyChanged事件。

二、Source类型

前面也说了,这里的Source是泛指,并不明确指定这个绑定源就叫Source关键字,而是有很多对象都可以叫Source,如CLR对象等。但是,如果是在后台进行绑定,大部分都是使用的Source进行绑定的。当然Binding类中也有一个属性叫Source。但我们应用更多的还是下面这几种方式。

2.1 ElementName(数据源明确)

使用ElementName后数据源就明确了,这个绑定源就是ElementName属性值,比如下面这个名为sourceTBox的TextBox,同样绑定属性,也是这个TextBox的对应属性。TextBlock就会从这个TextBox源中拿对应的数据丰富自己。

 <TextBox x:Name="sourceTBox" />
 <TextBlock x:Name="tb" Text="{Binding ElementName=sourceTBox,Path=Text}" />

为了代码简洁,默认的属性和默认的属性配制不需要在代码中重写设置,所有上面的代码可以优化成下面代码。

<TextBox x:Name="sourceTBox" />
<TextBlock x:Name="tb" Text="{Binding Text,ElementName=sourceTBox}" />

绑定表达式中Path属性被省略,Path=Text简写成Text,为什么可以这样简写,是因为Binding类中有两个构造函数,也是为什么有些绑定中有"path"关键字,有些没有,没有的就是默认使用了这个带参构造函数。

2.2 DataContext(数据源不明确)

DataContext--如果没有使用Source和RelativeSource指定源,Wpf就从当前元素开始在元素树种向上查找。 

DataContext是每一个继承FrameworkElement类的控件都有的属性,所以每一个UI控件都有此属性(包括布局控件),Binding只知道Path,不知道Source的话就会像UI树的根部找过,直到找到含有Path所指定的属性的对象没然后"借过来"用一下,如果最总没有找到那么就不会得到数据。

DataContext属性是绑定的默认源,除非你具体指定了另外一个源,比如2.1中的ElementName属性。它由FrameworkElement类定义,大部分UI控件包括WPF窗口都继承于此类。简单的说,它允许你指定一个绑定基。

<Grid>
    <TextBlock Text="{Binding S}" FontSize="30" Background="Beige"/>
</Grid>
public partial class MainWindow : Window
{
    public string Name { get; set; } = "engcaipen";
    public string S { get; set; } = "hanyoushui";

    public MainWindow()
    {
        InitializeComponent();
            
        this.DataContext = this;
    }
}

这里没有指定数据源,同时简化了Path,由于窗口有一个DataContext,所以,程序默认就会将DataContext作为默认数据源,它可以传递给子控件,并沿着控件树自下往上找这个S。

注意:

  • 自定义Name这个有问题,并不会被绑定,因为UI控件都有这个Name属性,容易产生歧义。
  • 需要手动设置下Window的DataContext属性,不然这个DataContext=null,同样没法绑定。
  • 同样的也可以将DataContext设置TextBlock的上级控件Grid的对应属性,反正需要设置下这个DataContext。

使用DataContext属性就好像设置了所有绑定的基础,能够贯穿整个控件层次。这样就节省了手动为每一个绑定定义源。

但是,这并不意味着你必须在一个窗口里面为所有控件使用同样的DataContext。由于每一个控件都有着自己的DataContext属性,你可以很容易的打破继承链,用新值来重写DataContext。这就允许你在窗口里使用一个全局DataContext,然后再像panel这样的地官方使用本地具体的DataContext。

2.3 RelativeSource(不知道数据源名)

Binding时,如果明确知道数据源的名字,可以使用ElementName和Source进行绑定,但是有些时候我们需要绑定的数据源可能没有明确的名字,此时我们就需要利用Binding的RelativeSource进行绑定。这种方式的意思是指当前元素和绑定源的位置关系。

2.3.1 Self

这种就是羊毛出在羊身上,自己既是绑定源元也是绑定目标。

<TextBlock FontSize="18" FontWeight="Bold" Margin="10" 
Background="Red" Width="80" Height="{Binding RelativeSource={RelativeSource Self},Path=Width}">MultiBinding Sample</TextBlock>

这里将自身的Height属性绑定在自生的Width上面,其实就是Height=Width。

2.3.2 AncestorType

    <Grid x:Name="g1" Background="Red" Margin="10">
        <DockPanel Name="d1" Background="Orange" Margin="10">
            <Grid x:Name="g2" Background="Yellow" Margin="10">
                <DockPanel Name="d2" Background="LawnGreen" Margin="10">
                    <TextBox Name="textbox" FontSize="24" Margin="10"
                             Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Grid},AncestorLevel=1},Path=Name}"/>
                </DockPanel>
            </Grid>
        </DockPanel>
    </Grid>

RelativeSource属性的数据类型为RelativeSource类,AncestorLevel指的是以Bingding目标控件为起点的层级偏移量,d2的偏移量是1,g2的偏移量是2,AncestorType指的是要找的目标对象的类型。值得注意的是AncestorLevel需要参考AncestorType使用,如上面设置了AncestorType={x:Type Grid},则Binding在寻找时会忽略非Grid的控件,此时g2的偏移量时1,g1的偏移量是2,DockPanel被忽略。

2.3.3 TemplatedParent

这个主要是在模板和数据模板中使用。此模式允许将给定的ControlTemplate 属性绑定到应用ControlTemplate 的控件属性。

    <Window.Resources>
        <ControlTemplate x:Key="template">
            <Canvas>
                <Canvas.RenderTransform>
                    <RotateTransform Angle="20"/>
                    </Canvas.RenderTransform>
                <Ellipse Height="100" Width="150"
                     Fill="{Binding
                RelativeSource={RelativeSource TemplatedParent},
                Path=Background}">
 
                  </Ellipse>
                <ContentPresenter Margin="35"
                      Content="{Binding RelativeSource={RelativeSource 
                      TemplatedParent},Path=Content}"/>
            </Canvas>
        </ControlTemplate>
    </Window.Resources>
        <Canvas Name="Parent0">
        <Button   Margin="50"
                  Template="{StaticResource template}" Height="0"
                  Canvas.Left="0" Canvas.Top="0" Width="0">
            <TextBlock FontSize="22">Click me</TextBlock>
        </Button>
    </Canvas>

如果我想应用给定控件的属性到它的控件模板,那么我可以使用TemplatedParent模式。也有类似的一个标记扩展的TemplateBinding是一种短手的第一个,但TemplateBinding是在编译时进行的对比TemplatedParent评估就是在第一次运行时。

三、总结

其实写到这里,笔者慢慢的才有了点感觉,而且也感觉文章整个布局有点乱,按照笔者之前的思考,主要想总结下这个绑定源和UI界面的关系,然后就把这种数据源分成前端(UI层)一份,后台(逻辑层)一份,慢慢的发现对于数据源理解有些错误,笔者之前对数据源的理解是Source、ElementName、DataContxt等,一直以为ElementName和DataContxt都属于控件的或者叫前端的数据源类型,根据官网及各位大佬的介绍,说这个数据源啊,只要是个对象,并公开了自己属性就可以,然后Binding正好有一个属性Source,以为这个Source是专门用来绑定后台(逻辑层)的数据源的,笔者就写了个Student类,然后在Xmal中通过Binding.Source将Student的实例绑定到TextBlock的Text中,发现不行。现在顿悟了:

  • 在c#代码中,可以访问XAML中声明的变量,但是XAML中不能访问c#中声明的变量,因此,要想在XAML中建立UI元素和逻辑对象的Binding,就需要把逻辑代码声明为XAML中的资源(Resource)。
  • 基于上一条规则,如果要在XAML中使用Binding的Source进行绑定逻辑层的数据源,就需要将逻辑层的数据源设置成XAML中的资源。这样们就不需要设置DataContext也能绑定这个自定义的Student源了。
  • Source关键字更多的是在后台进行绑定使用,而且是Binding类的属性,这个属性,在xaml中直接使用就不好使。
//定义一个Student变量
Student stu;
public MainWindow()
{
    InitializeComponent();
 
    //准备数据源       
    stu = new Student();
    //准备Binding
    Binding binding = new Binding();//创建Binding实例
    binding.Source = stu;//指定数据源
    binding.Path = new PropertyPath("Name");//指定访问路径 
 
    //使用Binding 连接数据源与Bingding目标
    BindingOperations.SetBinding(this.textBoxName,TextBox.TextProperty,binding);//使用binding实例将数据源与目标关联起来
    //参数为 目标;目标的某个属性
}
  • CLR对象可以理解为我们需要自定义的或者重写的一些对象。如果是自定义的对象,就像Student一样,要使用Binding.Source进行绑定。

在实际项目开发中,更多的数据源还是逻辑层的数据对象,这样可以显示从数据库或者文件中提取的数据。绑定源是非UI元素的对象时,需要放弃Binding.ElementName属性。绑定到逻辑层数据对象时,使用以下属性的其中一个:

  • Source--该属性是指向源对象的引用(数据的对象)
  • RelativeSource--这是引用,使用RelativeSource对象指向源对象,RelativeSource是一种特殊的工具,在对控件模板和数据模板进行二次创作时是很方便的。

WPF数据绑定数据结构不能获取私有信息或公有字段(不是属性)。

笔者应用最多的绑定源是DataContext,MVVM模式下通过设定View的DataContext属性,将ViewModel作为绑定源绑定到View中,然后在ViewModel中定义各种属性供View中的TextBolck、TextBox进行绑定,或者写几个类,将类的属性绑定给DataGrid使用,再或者定义一些集合,给ItemControl类控件使用。

在写这篇之前,甚至是写这篇文章前半部分时候,笔者还以为自定义的这个Student是数据源,根本就没意识到其实是将ViewModel作为View的DataContext,所以对这个Binding.Source属性理解很混乱,包括现在也不是很清楚,只是需要先结束下,等有时间专门来总结下这个Binding.Source在XAML中的使用。

四、参考文献

4.1 Binding 类 (System.Windows.Data) | Microsoft Docs

4.2 整理:WPF中Binding的几种写法 - mingruqi - 博客园

4.3 C# Wpf Binding 使用详解_badxnui的博客-CSDN博客_wpfbinding

4.4 WPF教程(三十五)使用DataContext_seanbei的博客-CSDN博客_datacontext

4.5 WPF中的binding(九)- 使用Binding的RelativeSource_HymanLiuTS的博客-CSDN博客

4.6 WPF绑定与RelativeSource结合使用_有技巧搬砖的博客-CSDN博客

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值