如何将 RelativeSource
与 WPF 绑定一起使用,有哪些不同的用例?
如果要绑定到对象上的另一个属性:
{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
属性。此处给出了一组完整的有效值 (from MSDN):
PreviousData 允许您在显示的数据项列表中绑定上一个数据项(不是包含该数据项的控件)。
TemplatedParent 指应用模板(数据绑定元素所在的元素)的元素。这与设置 TemplateBindingExtension 类似,并且仅在 Binding 位于模板内时适用。
Self 指您设置绑定的元素,并允许您将该元素的一个属性绑定到同一元素的另一个属性。
FindAncestor 指的是数据绑定元素的父链中的祖先。您可以使用它来绑定到特定类型的祖先或其子类。如果您想指定 AncestorType 和/或 AncestorLevel,这是您使用的模式。
这是 MVVM 架构上下文中更直观的解释:
https://i.stack.imgur.com/6Tcc6.jpg
{Binding Message}
(更简单一点......)
Path=DataContext.Message
才能使绑定起作用。这是有道理的,因为您可以对宽度/高度/等进行相对绑定。的一个控件。
Bechir Bejaoui 在 his article here 中公开了 WPF 中 RelativeSources 的用例:
当我们尝试将给定对象的属性绑定到对象本身的另一个属性时,当我们尝试将对象的属性绑定到其相对父对象的另一个属性时,RelativeSource 是一种标记扩展,用于特定的绑定情况,在自定义控件开发的情况下以及最后在使用一系列绑定数据的差异的情况下将依赖属性值绑定到一段 XAML 时。所有这些情况都表示为相对源模式。我将一一揭露所有这些案例。 Mode Self:想象一下这种情况,我们希望它的高度总是等于它的宽度的矩形,比如说一个正方形。我们可以使用元素名称
ListView
的数据上下文中。父级在其下方还有 2 个 ListView
级别。这有助于我防止将数据传递到每个 ListView
的 DataTemplate
的每个后续 vm
在 WPF RelativeSource
中绑定公开了三个要设置的 properties
:
<强> 1。模式: 这是一个可以有四个值的 enum
:
一个。 PreviousData(value=0):它将属性的前一个值分配给绑定的 b。 TemplatedParent(value=1):在定义任何控件的模板并希望绑定到控件的值/属性时使用。例如,定义 ControlTemplate:
<ControlTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
C。 Self(value=2):当我们想从一个 self 或一个 self 的属性绑定时。例如:在 CheckBox 上设置命令时,将复选框的选中状态作为 CommandParameter 发送
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
d。 FindAncestor(value=3):当想要从 Visual Tree 中的父控件绑定时。例如:如果网格,如果标题复选框被选中,则在记录中绑定一个复选框
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />
<强> 2。 AncestorType: 当模式为 FindAncestor
时定义什么类型的祖先
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}
3. AncestorLevel: 当模式为 FindAncestor
时,祖先是什么级别(如果 visual tree
中有两个相同类型的父级)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
以上是 RelativeSource 绑定的所有用例。
不要忘记 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
请注意,在我用于方法绑定的“之前”示例中,代码已经通过使用 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 in code Behind 复制了这个。
此外,就示例而言,MSDN 页面非常好:RelativeSource Class
这是在空数据网格上使用此模式的示例。
<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>
我没有阅读每个答案,但我只想添加此信息以防按钮的相对源命令绑定。
当您使用具有 Mode=FindAncestor
的相对源时,绑定必须类似于:
Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"
如果您不在路径中添加 DataContext,则在执行时它无法检索该属性。
我不断更新我对绑定的研究。
👉 原创Here
数据上下文
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
传递过来的。
因此,Text
的 Binding 结果值为 'James'。
整数类型 直接从 Xaml 为 DataContext 分配值时,首先需要对值类型(例如 Integer 和 Boolean)进行资源定义。因为所有字符串都被识别为字符串。 1. 在 Xaml 中使用 System mscrolib 简单类型变量类型不受标准支持。你可以用任何词来定义它,但主要使用 sys 词。 xmlns:sys="clr-namespace:System;assembly=mscorlib" 2.在xaml中创建YEAR资源键 以StaticResource的形式声明要创建的类型的值。
所有类型的值 很少有将值类型直接绑定到 DataContext 的情况。因为我们要绑定一个对象。
另一种类型不仅可以是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}"/>
<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}"/>
静态属性绑定
您可以直接访问绑定属性值。
静止的
namespace Exam
{
public class ExamClass
{
public static string ExamText { get; set; }
}
}
<Window ... xmlns:exam="clr-namespace:Exam">
<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}"/>
如果一个元素不是可视化树的一部分,那么 RelativeSource 将永远无法工作。
在这种情况下,您需要尝试由 Thomas Levesque 开创的不同技术。
他在他的博客 [WPF] How to bind to data when the DataContext is not inherited 下提供了解决方案。它工作得非常出色!
万一他的博客出现故障,附录 A 包含 his article 的镜像副本。
请不要在这里发表评论,请comment directly on his blog post。
附录A:博文镜像
WPF 中的 DataContext 属性非常方便,因为它会被分配给它的元素的所有子元素自动继承;因此,您无需在要绑定的每个元素上再次设置它。但是,在某些情况下,DataContext 是不可访问的:它发生在不属于可视树或逻辑树的元素上。在这些元素上绑定属性可能非常困难......
让我们用一个简单的例子来说明:我们想在 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); target 属性是 'Visibility' (type 'Visibility') 消息比较晦涩,但意思其实很简单: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}"/>
但是这些解决方法似乎都不起作用,我们总是得到相同的结果......
在这一点上,似乎唯一可行的方法是更改代码隐藏中的列可见性,我们通常在使用 MVVM 模式时更愿意避免这种情况……但我不会这么快放弃,至少不会虽然还有其他选择可以考虑😉
我们的问题的解决方案实际上非常简单,并且利用了 Freezable 类。这个类的主要目的是定义具有可修改和只读状态的对象,但在我们的例子中有趣的特性是 Freezable 对象可以继承 DataContext,即使它们不在可视树或逻辑树中。我不知道实现这种行为的确切机制,但我们将利用它来使我们的绑定工作......
这个想法是创建一个继承 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 属性正确显示或隐藏列。
AncestorType
时推断的。AncestorType
之前省略FindAncestor
时,我收到以下错误:“RelativeSource 不在 FindAncestor 模式下”。 (VS2013,社区版){Binding Path=DataContext.SomeProperty, RelativeSource=...
。当我尝试在 DataTemplate 中绑定到父级的 DataContext 时,这对我这个新手来说有点出乎意料。