【wpf】深度解析,Binding是如何寻找数据源的

数据源存放位置

目前我用存放数据源的属性有:

  • Resources
  • ItemsSource
  • DataContext

一般控件都有Resources和DataContext属性,列表控件会多一个ItemsSource。

 Resources可以放多个资源,但是需要给每一个资源指定一个key。

<Page.Resources>
    <local:TestMode2 x:Key="TM2"/>
    <local:TestMode x:Key="TM1"/>
</Page.Resources>

而 ItemsSourceDataContext只能放一个对象(不需要指定key)

<Page.DataContext>
    <local:TestMode2/>
</Page.DataContext>

Binding  表达式

Binding  表达式 有一些常用的方法:

  • Source  
  • RelativeSource 
  • Path

Path  相当于是一个过滤器,用来选择具体是哪一个属性

Source  是指定资源的位置,Source  指定的资源是去Resources中找,寻找资源时直接指定key,就能一步到位。

<TextBlock Text="{Binding Source={StaticResource TM2 }, Path=TestDate }"/>

但如果,我们在绑定表达式中,不写Source  指定资源时,绑定表达式就会自动到ItemsSourceDataContext 中去找。

<TextBlock Text="{Binding Path=Value}"/>

而且,默认的寻找方式是一层层的往上找,如果在某一层找到ItemsSourceDataContext  被设置过,不管Path指定的属性是否存在,就会停止继续往上找。

那么是否改变这种默认的查找方式呢?答案是使用RelativeSource 

你会发现 RelativeSource 和 Source  有着本质的区别, 它并不是指定源的位置,而是用于改变默认的寻找资料的方式。(因为此时 源的位置是不确定的,是需要按照一定的规则去寻找的 

<TextBlock  Text="{Binding Path=DataContext.DM1.Value, 
                            RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Page}}}"/>

感悟

在使用数据绑定时,我们更喜欢,使用不指定Source 的方式,此时会自动去ItemsSourceDataContext  中寻找,这种方式更加的灵活。

RelativeSource

接下来,我将介绍 RelativeSource 是如何改变默认的寻找资源的方式。并显示实际的例子。

先看一下它的具体写法

<TextBlock  Background="Orange" Text="{Binding Path=DataContext.DM1.Value, 
                            RelativeSource={RelativeSource Mode=FindAncestor, AncestorType= 
{x:Type Page}}}"/>

最主要就是这个Mode,它定义了寻找源的几种规则:

//
// 摘要:
//     Describes the location of the binding source relative to the position of the
//     binding target.
public enum RelativeSourceMode
{
    //
        // 摘要:
        //     Allows you to bind the previous data item (not that control that contains the
        //     data item) in the list of data items being displayed.
        PreviousData = 0,
    //
        // 摘要:
        //     Refers to the element to which the template (in which the data-bound element
        //     exists) is applied. This is similar to setting a System.Windows.TemplateBindingExtension
        //     and is only applicable if the System.Windows.Data.Binding is within a template.
        TemplatedParent = 1,
    //
        // 摘要:
        //     Refers to the element on which you are setting the binding and allows you to
        //     bind one property of that element to another property on the same element.
        Self = 2,
    //
        // 摘要:
        //     Refers to the ancestor in the parent chain of the data-bound element. You can
        //     use this to bind to an ancestor of a specific type or its subclasses. This is
        //     the mode you use if you want to specify System.Windows.Data.RelativeSource.AncestorType
        //     and/or System.Windows.Data.RelativeSource.AncestorLevel.
        FindAncestor = 3
}

FindAncestor

表示找到最顶层,一般配合AncestorType使用,AncestorType指定一种类型,表示遇到这种类型就停止寻找。

Self 

表示Binding的属性就在自身对象上寻找。

TemplatedParent 

以下,就是定义了一个控件模板:控件模板中就经常用到 TemplatedParent 

这个就是告诉wpf,这个数据源,要在 应用了这个模板的对象中去找!

PreviousData 

这个是表示绑定上一个子项,目前感觉只能在数据模板中才会生效。


以下是具体的代码部分:

<Page x:Class="WpfTest.绑定源的寻找"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:WpfTest"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800" Background="AliceBlue"
      Title="绑定源的寻找">
    <Page.Resources>
        <local:TestMode2 x:Key="TM2"/>
        <local:TestMode x:Key="TM1"/>
    </Page.Resources>
    <Page.DataContext>
        <local:TestMode2/>
    </Page.DataContext>

    <UniformGrid Rows="3">
        <ItemsControl ItemsSource="{Binding Path=ValueList}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <UniformGrid Columns="4" Rows="4">
                        <TextBlock Text="{Binding Path=Value}"/>
                        <TextBlock  Background="Orange" Text="{Binding Path=DataContext.DM1.Value, 
                            RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Page}}"/>
                        <TextBlock  Background="Green" Text="{Binding Path=DataContext.TestDate, 
                            RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Page}}"/>
                        <TextBlock Text="{Binding }"/>
                        <TextBlock Text="{Binding Source={StaticResource TM2 }, Path=TestDate }"/>
                    </UniformGrid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <StackPanel>
            <TextBlock  Background="Orange" Text="{Binding Path=DataContext.DM1.Value, 
                            RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Page}}"/>
            <TextBlock  Background="Green" Text="{Binding Path=ActualWidth, 
                            RelativeSource={RelativeSource Mode=Self}}"/>
            <ItemsControl ItemsSource="{Binding Path=ValueList}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Value}"/>
                            <TextBlock Text="{Binding Path=Value,
                                RelativeSource={RelativeSource Mode=PreviousData}}" Foreground="Red" Margin="10,0,0,0"/>
                        </StackPanel>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </StackPanel>
        
    </UniformGrid>
</Page>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace WpfTest
{
    public class TestMode2
    {
        public string TestDate { get; set; } = "test";

        public DataModel1 DM1 { get; set; } = new DataModel1();
        public DataModel2 DM2 { get; set; } = new DataModel2();

        public class DataModel1 : INotifyPropertyChanged
        {

            private int _value = 1;

            public int Value
            {
                get { return _value; }
                set
                {
                    _value = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
                }
            }


            public event PropertyChangedEventHandler PropertyChanged;
        }

        public class DataModel2 : INotifyPropertyChanged
        {
            private int _value = 2;

            public int Value
            {
                get { return _value; }
                set
                {
                    _value = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
                }
            }


            public event PropertyChangedEventHandler PropertyChanged;
            public int MyProperty { get; set; } = 3;
        }

        public List<DataModel2> ValueList { get; set; } = new List<DataModel2>()
        {
            new DataModel2{ Value=1},
            new DataModel2{ Value=2},
            new DataModel2{ Value=3},
            new DataModel2{ Value=4},
        };




    }
}

2022年12月2日 修正 

AncestorType={x:Type xxx}, 这里的x:Type不能省略。

2022年12月6日

完善对 TemplatedParent  的理解。

2023年7月24日新增-------

如何显示指定ItemsSource的子项为绑定源!

编程过程中遇到一个问题,就是DataTemplate包含了一个用户控件:

 <ListBox Name="tileViewControl" ItemsSource="{Binding TileViewItems}">
       <ListBox.ItemTemplate>
           <DataTemplate>
                <sw:ROIWindowCtrl ScreenNum="{Binding Path=ItemsSource.Index, RelativeSource={RelativeSource Mode=FindAncestor,  AncestorType= {x:Type ???}}}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
 </ListBox>

ScreenNum,本来想绑定的是对象是ListBox 的子项,默认情况下本来也是这样。但是,用户控件本身也设置了DataContext,所以 ScreenNum 找到用户控件的DataContext就会停止向上找了。

所以,我们现在需要:显示指定ItemsSource的子项为绑定源。

 ScreenNum="{Binding Path=DataContext.Index, RelativeSource={RelativeSource Mode=FindAncestor,  AncestorType= {x:Type ???}}}"

这里需要关注两点:

1 Path=DataContext.Index  Path 是子项的DataContext中的Index属性

2  其实就是  AncestorType  应该写啥?  ListBox 的子项其实就是 ListBoxitem。

所以这么写:

 ScreenNum="{Binding Path=DataContext.Index, RelativeSource={RelativeSource Mode=FindAncestor,  AncestorType= {x:Type ListBoxitem}}}"  这样就显示指定ItemsSource的子项为绑定源,而不会找到控件的DataContext里面去了。

哪如果,把ListBox 换成  ItemsControl 呢?ItemsControl的子项是啥? 这次我找了很久都没发现ItemsControl子项类型是啥,后问了一个大神,大神告知是:ContentPresenter。

这个我们并不陌生。

在文章《子项容器模板 控件模板 数据模板 逻辑树 视觉树 之间的关系》我们讲过:http://t.csdn.cn/ooJSsicon-default.png?t=N6B9http://t.csdn.cn/ooJSs

 ContentPresenter是视觉树里的东西,也是就控件内部的东西。

<ItemsControl ItemsSource="{Binding multiCamEntityList}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Columns="2"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <!--<sw:ROIWindowCtrl ScreenNum="{Binding DriverIndex}"/>-->
            <Grid>
                <sw:ROIWindowCtrl ScreenNum="{Binding DataContext.DriverIndex, 
RelativeSource={RelativeSource AncestorType=ContentPresenter}}"/>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

哪为啥 是ContentPresenter 呢? 更具我目前的理解是:ContentPresenter和数据模板是息息相关的。而DriverIndex就是数据模板对应的数据源(multiCamEntityList)里的内容。

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
WPF DataGrid 是一种强大的控件,可以用于在 WPF 应用程序中显示表格数据。DataGrid 可以使用 Binding 数据来绑定到数据源,这使得在 DataGrid 中显示数据变得非常简单。 下面是一个简单的示例,展示如何在 DataGrid 中使用 Binding 数据: 1. 首先,创建一个数据类,用于表示要在 DataGrid 中显示的数据。例如,在此示例中,我们创建一个名为 Person 的类: ```csharp public class Person { public string Name { get; set; } public int Age { get; set; } public string Email { get; set; } } ``` 2. 接下来,在 XAML 中创建一个 DataGrid 控件,并将其绑定到数据源。例如,在此示例中,我们将 DataGrid 绑定到一个名为 people 的 ObservableCollection<Person> 对象: ```xaml <DataGrid ItemsSource="{Binding people}" AutoGenerateColumns="True"/> ``` 3. 最后,在代码中设置 DataContext,并将 people 集合添加到 DataContext 中: ```csharp public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); // Create a collection of people ObservableCollection<Person> people = new ObservableCollection<Person>(); people.Add(new Person { Name = "John", Age = 30, Email = "john@example.com" }); people.Add(new Person { Name = "Jane", Age = 25, Email = "jane@example.com" }); people.Add(new Person { Name = "Bob", Age = 40, Email = "bob@example.com" }); // Set the DataContext DataContext = new { people }; } } ``` 现在,当运行应用程序时,将显示一个包含三个人的表格,其中每个人都有一个名称、年龄和电子邮件地址。这些数据将使用 Binding 数据绑定到 DataGrid 中。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code bean

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值