数据源存放位置
目前我用存放数据源的属性有:
- Resources
- ItemsSource
- DataContext
一般控件都有Resources和DataContext属性,列表控件会多一个ItemsSource。
Resources可以放多个资源,但是需要给每一个资源指定一个key。
<Page.Resources>
<local:TestMode2 x:Key="TM2"/>
<local:TestMode x:Key="TM1"/>
</Page.Resources>
而 ItemsSource和DataContext只能放一个对象(不需要指定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 指定资源时,绑定表达式就会自动到ItemsSource和DataContext 中去找。
<TextBlock Text="{Binding Path=Value}"/>
而且,默认的寻找方式是一层层的往上找,如果在某一层找到ItemsSource或DataContext 被设置过,不管Path指定的属性是否存在,就会停止继续往上找。
那么是否改变这种默认的查找方式呢?答案是使用RelativeSource 。
你会发现 RelativeSource 和 Source 有着本质的区别, 它并不是指定源的位置,而是用于改变默认的寻找资料的方式。(因为此时 源的位置是不确定的,是需要按照一定的规则去寻找的)
<TextBlock Text="{Binding Path=DataContext.DM1.Value,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Page}}}"/>
感悟
在使用数据绑定时,我们更喜欢,使用不指定Source 的方式,此时会自动去ItemsSource或DataContext 中寻找,这种方式更加的灵活。
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/ooJSshttp://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)里的内容。