如何使用RelativeSource
WPF 绑定以及有哪些不同的用例?
14 回答
如果要绑定到对象上的另一个属性:
{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}
如果您想获得祖先的属性:
{Binding Path=PathToProperty,
RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
如果您想在模板化父对象上获取属性(因此您可以在 ControlTemplate 中进行 2 路绑定)
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
或者,更短(这只适用于 OneWay 绑定):
{TemplateBinding Path=PathToProperty}
Binding RelativeSource={
RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...
的默认属性RelativeSource
是Mode
属性。此处给出了一组完整的有效值(来自 MSDN):
PreviousData允许您在显示的数据项列表中绑定上一个数据项(不是包含该数据项的控件)。
TemplatedParent指应用模板(数据绑定元素所在的元素)的元素。这与设置 TemplateBindingExtension 类似,并且仅在 Binding 位于模板内时适用。
Self指您设置绑定的元素,并允许您将该元素的一个属性绑定到同一元素的另一个属性。
FindAncestor指的是数据绑定元素的父链中的祖先。您可以使用它来绑定到特定类型的祖先或其子类。如果您想指定 AncestorType 和/或 AncestorLevel,这是您使用的模式。
这是 MVVM 架构上下文中更直观的解释:
Bechir Bejaoui 在他的文章中公开了 WPF 中 RelativeSources 的用例:
当我们尝试将给定对象的属性绑定到对象本身的另一个属性时,当我们尝试将对象的属性绑定到其相对父对象的另一个属性时,RelativeSource 是一种标记扩展,用于特定的绑定情况,在自定义控件开发的情况下以及最后在使用一系列绑定数据的差异的情况下将依赖属性值绑定到一段 XAML 时。所有这些情况都表示为相对源模式。我将一一揭露所有这些情况。
- 模式自我:
想象一下这种情况,一个我们希望它的高度总是等于它的宽度的矩形,比方说一个正方形。我们可以使用元素名称来做到这一点
<Rectangle Fill="Red" Name="rectangle" Height="100" Stroke="Black" Canvas.Top="100" Canvas.Left="100" Width="{Binding ElementName=rectangle, Path=Height}"/>
但在上述情况下,我们必须指明绑定对象的名称,即矩形。我们可以使用 RelativeSource 以不同的方式达到相同的目的
<Rectangle Fill="Red" Height="100" Stroke="Black" Width="{Binding RelativeSource={RelativeSource Self}, Path=Height}"/>
在这种情况下,我们没有义务提及绑定对象的名称,并且无论何时更改高度,宽度都将始终等于高度。
如果要将 Width 参数设置为高度的一半,则可以通过将转换器添加到 Binding 标记扩展来实现。现在让我们想象另一种情况:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}"/>
上述情况用于将给定元素的给定属性与其直接父元素之一联系起来,因为该元素拥有一个称为 Parent 的属性。这导致我们进入另一种相对源模式,即 FindAncestor 模式。
- 模式 FindAncestor
在这种情况下,给定元素的属性将绑定到其父级之一 Of Corse。与上述情况的主要区别在于,由您决定祖先类型和层次结构中的祖先等级以绑定属性。顺便尝试玩一下这段 XAML
<Canvas Name="Parent0"> <Border Name="Parent1" Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}" Height="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualHeight}"> <Canvas Name="Parent2"> <Border Name="Parent3" Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}" Height="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualHeight}"> <Canvas Name="Parent4"> <TextBlock FontSize="16" Margin="5" Text="Display the name of the ancestor"/> <TextBlock FontSize="16" Margin="50" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}, AncestorLevel=2},Path=Name}" Width="200"/> </Canvas> </Border> </Canvas> </Border> </Canvas>
上述情况是嵌入在一系列边框中的两个 TextBlock 元素和代表其分层父级的画布元素。第二个 TextBlock 将在相对源级别显示给定父级的名称。
所以尝试将 AncestorLevel=2 更改为 AncestorLevel=1 看看会发生什么。然后尝试将祖先的类型从 AncestorType=Border 更改为 AncestorType=Canvas,看看会发生什么。
显示的文本将根据祖先类型和级别而变化。那么如果祖先级别不适合祖先类型会发生什么?这是个好问题,我知道你要问了。响应是不会抛出异常,并且不会在 TextBlock 级别显示任何内容。
- 模板父
此模式允许将给定的 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 是在第一次运行后评估的。如下图所示,背景和内容从按钮内部应用到控件模板。
在 WPFRelativeSource
绑定中公开了三个properties
要设置的内容:
1.模式:这是一个enum
可以有四个值:
一种。PreviousData(
value=0
):它将先前的值分配给property
绑定湾。TemplatedParent(
value=1
): 当定义templates
任何控件并希望绑定到 的值/属性时使用control
。例如,定义
ControlTemplate
:
<ControlTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
C。Self(
value=2
):当我们想从 self 的 aself
或 aproperty
。例如:在设置on时发送
checkbox
as的选中状态CommandParameter
Command
CheckBox
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
d。FindAncestor(
value=3
):当想要control
从Visual Tree
.例如:
checkbox
在records
if agrid
,ifheader
checkbox
被选中时绑定 a
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />
2. AncestorType: 当mode是FindAncestor
然后定义什么类型的祖先
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}
3. AncestorLevel: 当mode是FindAncestor
什么级别的祖先(如果有两个相同类型的父级visual tree
)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
以上是
RelativeSource binding
.
不要忘记 TemplatedParent:
<Binding RelativeSource="{RelativeSource TemplatedParent}"/>
或者
{Binding RelativeSource={RelativeSource TemplatedParent}}
值得注意的是,对于那些偶然发现 Silverlight 这种想法的人:
Silverlight 仅提供这些命令的一个缩减子集
我创建了一个库来简化 WPF 的绑定语法,包括更容易使用 RelativeSource。这里有些例子。前:
{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
{Binding Path=Text, ElementName=MyTextBox}
后:
{BindTo PathToProperty}
{BindTo Ancestor.typeOfAncestor.PathToProperty}
{BindTo Template.PathToProperty}
{BindTo #MyTextBox.Text}
这是一个如何简化方法绑定的示例。前:
// C# code
private ICommand _saveCommand;
public ICommand SaveCommand {
get {
if (_saveCommand == null) {
_saveCommand = new RelayCommand(x => this.SaveObject());
}
return _saveCommand;
}
}
private void SaveObject() {
// do something
}
// XAML
{Binding Path=SaveCommand}
后:
// C# code
private void SaveObject() {
// do something
}
// XAML
{BindTo SaveObject()}
您可以在此处找到该库:http ://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html
请注意,在我用于方法绑定的“BEFORE”示例中,代码已经通过使用RelayCommand
我检查的最后一个不是 WPF 的本机部分进行了优化。如果没有那个,“之前”的例子会更长。
一些有用的点点滴滴:
以下是主要在代码中执行此操作的方法:
Binding b = new Binding();
b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1);
b.Path = new PropertyPath("MyElementThatNeedsBinding");
MyLabel.SetBinding(ContentProperty, b);
我主要从Binding Relative Source 中的代码Behind 中复制了这个。
此外,就示例而言,MSDN 页面非常好:RelativeSource Class
我刚刚发布了另一个解决方案,用于访问 Silverlight 中适用于我的父元素的 DataContext。它使用Binding ElementName
.
我没有阅读每个答案,但我只想添加此信息以防按钮的相对源命令绑定。
当您将相对源与 一起使用时Mode=FindAncestor
,绑定必须类似于:
Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"
如果您不在路径中添加 DataContext,则在执行时它无法检索该属性。
这是在空数据网格上使用此模式的示例。
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="We did't find any matching records for your search..." FontSize="16" FontWeight="SemiBold" Foreground="LightCoral"/>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
如果一个元素不是可视化树的一部分,那么 RelativeSource 将永远无法工作。
在这种情况下,您需要尝试由 Thomas Levesque 开创的不同技术。
他在他的博客[WPF] How to bind to data when the DataContext is not inherit下有解决方案。它工作得非常出色!
万一他的博客出现故障,附录 A 包含他文章的镜像副本。
请不要在这里发表评论,请直接在他的博客文章中发表评论。
附录A:博文镜像
WPF 中的 DataContext 属性非常方便,因为它会被分配给它的元素的所有子元素自动继承;因此,您无需在要绑定的每个元素上再次设置它。但是,在某些情况下,DataContext 是不可访问的:它发生在不属于可视树或逻辑树的元素上。在这些元素上绑定属性可能非常困难......</p>
让我们用一个简单的例子来说明:我们想在 DataGrid 中显示一个产品列表。在网格中,我们希望能够根据 ViewModel 公开的 ShowPrice 属性的值显示或隐藏 Price 列。显而易见的方法是将列的 Visibility 绑定到 ShowPrice 属性:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding ShowPrice,
Converter={StaticResource visibilityConverter}}"/>
不幸的是,更改 ShowPrice 的值没有任何效果,并且该列始终可见……为什么?如果我们查看 Visual Studio 中的“输出”窗口,我们会注意到以下行:
System.Windows.Data 错误:2:找不到目标元素的管理 FrameworkElement 或 FrameworkContentElement。绑定表达式:路径=显示价格;数据项=空;目标元素是“DataGridTextColumn”(HashCode=32685253);目标属性是“可见性”(类型“可见性”)
该消息相当神秘,但含义实际上很简单:WPF 不知道使用哪个 FrameworkElement 来获取 DataContext,因为该列不属于 DataGrid 的可视树或逻辑树。
我们可以尝试调整绑定以获得所需的结果,例如通过将 RelativeSource 设置为 DataGrid 本身:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding DataContext.ShowPrice,
Converter={StaticResource visibilityConverter},
RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>
或者我们可以添加一个绑定到 ShowPrice 的 CheckBox,并尝试通过指定元素名称将列可见性绑定到 IsChecked 属性:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding IsChecked,
Converter={StaticResource visibilityConverter},
ElementName=chkShowPrice}"/>
但是这些解决方法似乎都不起作用,我们总是得到相同的结果......</p>
在这一点上,似乎唯一可行的方法是更改代码隐藏中的列可见性,我们通常在使用 MVVM 模式时更愿意避免这种情况……但我不会这么快就放弃,至少不会虽然还有其他选择需要考虑
我们的问题的解决方案实际上非常简单,并且利用了 Freezable 类。此类的主要目的是定义具有可修改和只读状态的对象,但在我们的例子中有趣的特性是 Freezable 对象可以继承 DataContext,即使它们不在可视树或逻辑树中。我不知道实现这种行为的确切机制,但我们将利用它来使我们的绑定工作......</p>
这个想法是创建一个继承 Freezable 并声明一个 Data 依赖属性的类(我称它为 BindingProxy,原因很快就会变得显而易见):
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
然后我们可以在 DataGrid 的资源中声明这个类的一个实例,并将 Data 属性绑定到当前的 DataContext:
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
最后一步是将此 BindingProxy 对象(可通过 StaticResource 轻松访问)指定为绑定的 Source:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding Data.ShowPrice,
Converter={StaticResource visibilityConverter},
Source={StaticResource proxy}}"/>
请注意,绑定路径以“Data”为前缀,因为该路径现在是相对于 BindingProxy 对象的。
绑定现在可以正常工作,并且根据 ShowPrice 属性正确显示或隐藏列。
我不断更新我对绑定的研究。
原创在这里
数据上下文
DataContext 是 FrameworkElement 中包含的 DependencyProperty。
PresentationFramework.dll
namespace System.Windows
{
public class FrameworkElement : UIElement
{
public static readonly DependencyProperty DataContextProperty;
public object DataContext { get; set; }
}
}
而且,WPF 中的所有 UI 控件都继承了FrameworkElement
该类。
此时在学习 Binding 或 DataContext 时,您不必更深入地学习 FrameworkElement。
然而,这里只是简单地提到一个事实,可以包含所有 UI 控件的最接近的对象是 FrameworkElement。
DataContext 始终是 Binding 的参考点。
绑定可以直接调用从最近的 DataContext 开始的 DataContext 类型格式的值。
<TextBlock Text="{Binding}" DataContext="James"/>
绑定到的值Text="{Binding}"
直接从最近的 DataContext 传递TextBlock
。
因此,Binding 结果值为Text
'James'。
整数类型
1.
直接从 Xaml 为 DataContext 分配值时,首先需要对值类型(例如 Integer 和 Boolean)进行资源定义。因为所有字符串都被识别为字符串。mscrolib
在Xaml中 使用系统标准不支持简单类型变量类型。
你可以用任何词来定义它,但主要是sys
用词。xmlns:sys="clr-namespace:System;assembly=mscorlib"
YEAR
2.在xaml中 创建资源键以 StaticResource 的形式声明要创建的类型的值。
<Window.Resources> <sys:Int32 x:Key="YEAR">2020</sys:Int32> </Window.Resources> ... <TextBlock Text="{Binding}" DataContext="{StaticResource YEAR"/>
所有类型的值
很少有将值类型直接绑定到 DataContext 的情况。
因为我们要绑定一个对象。<Window.Resources> <sys:Boolean x:Key="IsEnabled">true</sys:Boolean> <sys:double x:Key="Price">7.77</sys:double> </Window.Resources> ... <StackPanel> <TextBlock Text="{Binding}" DataContext="{StaticResource IsEnabled}"/> <TextBlock Text="{Binding}" DataContext="{StaticResource Price}"/> </StackPanel>
另一种类型
不仅可以是String,还可以是各种类型。因为 DataContext 是一个对象类型。
最后...
在WPF中使用Binding时,大部分开发者并没有完全意识到DataContext的存在、作用和重要性。
这可能意味着 Binding 是靠运气连接的。
特别是如果您负责或参与大型 WPF 项目,您应该更清楚地了解应用程序的 DataContext 层次结构。此外,没有这个DataContext概念的WPF各种流行的MVVM Framework系统的引入,将会对自由实现功能造成更大的限制。
捆绑
- 数据上下文绑定
- 元素绑定
- 多重绑定
- 自属性绑定
- 查找祖先绑定
- TemplatedParent 绑定
- 静态属性绑定
数据上下文绑定
string property
<TextBox Text="{Binding Keywords}"/>
元素绑定
<CheckBox x:Name="usingEmail"/>
<TextBlock Text="{Binding ElementName=usingEmail, Path=IsChecked}"/>
多重绑定
<TextBlock Margin="5,2" Text="This disappears as the control gets focus...">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource TextInputToVisibilityConverter}">
<Binding ElementName="txtUserEntry2" Path="Text.IsEmpty" />
<Binding ElementName="txtUserEntry2" Path="IsFocused" />
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>
### 自属性绑定
<TextBlock x:Name="txt" Text="{Binding ElementName=txt, Path=Tag}"/>
如果必须绑定自己的属性,可以使用Self Property Binding
,而不是使用Element Binding
。
您不再需要声明x:Name
绑定您自己的财产。
<TextBlock Text="{Binding RelativeSource={RelativeSource Self}, Path=Tag}"/>
### 根据离它最近的父控件查找 Ancestor Binding Imports。
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Title}"/>
除了找到的控件的属性之外,DataContext 对象中的属性(如果存在)也可以使用。
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.Email}"/>
TemplatedParent 绑定
这是一种可以在内部使用的方法ControlTemplate
,并且您可以导入作为ControlTemplate
.
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
您可以访问所有的 Property 和 DataContext。
<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"/>
静态属性绑定
您可以直接访问绑定属性值。
1. 申报static
财产。
namespace Exam
{
public class ExamClass
{
public static string ExamText { get; set; }
}
}
2. 在 XAML 中使用静态类。
<Window ... xmlns:exam="clr-namespace:Exam">
3.绑定属性。
<TextBlock Text="{Binding exam:ExamClass.ExamText}"/>
或者,您可以像使用Converter
.
<Window.Resource>
<cvt:VisibilityToBooleanConverter x:Key="VisibilityToBooleanConverter"/>
<exam:ExamClass x:Key="ExamClass">
</Window.Resource>
...
<TextBlock Text="{Binding Source={StaticResource ExamClass}, Path=ExamText}"/>
在正常情况下,我从未使用过静态属性。这是因为偏离其自身 DataContext 的数据会破坏整个 WPF 应用程序的流程并显着降低可读性。但是,这种方法在开发阶段被积极使用,以实现快速测试和功能,以及在 DataContext(或 ViewModel)中。
不良装订和良好装订
✔️ 如果要绑定的属性包含在 Datacontext 中,
则不必使用 ElementBinding。
通过连接控件使用 ElementBinding 不是功能问题,
但它打破了 Binding 的基本模式。
<TextBox x:Name="text" Text="{Binding UserName}"/>
...
<TextBlock Text="{Binding ElementName=text, Path=Text}"/>
良好的装订
<TextBox Text="{Binding UserName}"/>
...
<TextBlock Text="{Binding UserName}"/>
✔️ 使用属于较高层控件的属性时不要使用 ElementBinding。
绑定不良<Window x:Name="win">
<TextBlock Text="{Binding ElementName=win, Path=DataContext.UserName}"/>
...
良好的装订
<Window>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.UserName}"/>
...
伟大的!
<Window>
<TextBlock DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext}"
Text="{Binding UserName}"/>
...
✔️ 使用自己的属性时不要使用 ElementBinding。
绑定不良<TextBlock x:Name="txt" Text="{Binding ElementName=txt, Path=Foreground}"/>
良好的装订
<TextBlock Text="{Binding RelativeSource={RelativeSource Self}, Path=Foreground}"/>