ChatGPT解决这个技术问题 Extra ChatGPT

如何将 WPF 绑定与 RelativeSource 结合使用?

如何将 RelativeSource 与 WPF 绑定一起使用,有哪些不同的用例?


G
Guge

如果要绑定到对象上的另一个属性:

{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 Path=PathToProperty,RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}”,看起来它需要在“AncestorType”之前有“Mode=FindAncestor”
用什么技术?在 WPF 中,这是在您指定 AncestorType 时推断的。
我同意@EdwardM。当我在 AncestorType 之前省略 FindAncestor 时,我收到以下错误:“RelativeSource 不在 FindAncestor 模式下”。 (VS2013,社区版)
@kmote,自.net 3.0以来这对我有用,我再次验证它在kaxaml中以这种方式工作......再次,你使用什么技术? XAML 处理器对于 WPF/Silverlight/UWP 是不同的,因此您在不同的技术上可能会有不同的结果。您还提到了 VS 社区,所以也许这是一个 IDE 警告,但在运行时有效?
只是想在这里注意,如果您想绑定到 RelativeSource 的 DataContext 中的属性,那么您必须明确指定它:{Binding Path=DataContext.SomeProperty, RelativeSource=...。当我尝试在 DataTemplate 中绑定到父级的 DataContext 时,这对我这个新手来说有点出乎意料。
D
Drew Noakes
Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

RelativeSource 的默认属性是 Mode 属性。此处给出了一组完整的有效值 (from MSDN):

PreviousData 允许您在显示的数据项列表中绑定上一个数据项(不是包含该数据项的控件)。

TemplatedParent 指应用模板(数据绑定元素所在的元素)的元素。这与设置 TemplateBindingExtension 类似,并且仅在 Binding 位于模板内时适用。

Self 指您设置绑定的元素,并允许您将该元素的一个属性绑定到同一元素的另一个属性。

FindAncestor 指的是数据绑定元素的父链中的祖先。您可以使用它来绑定到特定类型的祖先或其子类。如果您想指定 AncestorType 和/或 AncestorLevel,这是您使用的模式。


J
Jeffrey Knight

这是 MVVM 架构上下文中更直观的解释:

https://i.stack.imgur.com/6Tcc6.jpg


我错过了什么?你怎么能认为这是一个简单而清晰的图形? 1:左边的框的含义与右边的框并没有真正的关系(为什么 ViewModel 中有一个 .cs 文件?) 2:这些 DataContext 箭头指向什么? 3:为什么ViewModel1中没有Message属性?最重要的是 5:如果 TextBlock 已经具有相同的 DataContext,为什么还需要 RelativeSource Binding 才能访问 Window 的 DataContext?我显然在这里遗漏了一些东西,所以要么我很愚蠢,要么这个图形不像每个人想象的那样简单明了!请赐教
@MarkusHütter 该图显示了一组嵌套视图和相应的视图模型。 View1 的 DataContext 是 ViewModel1,但它想绑定到 BaseViewModel 的一个属性。因为 BaseViewModel 是 BaseView(它是一个 Window)的 DataContext,它可以通过找到第一个父容器(它是一个 Window)并获取它的 DataContext 来做到这一点。
@MatthewCargille 我很清楚它应该是什么意思,那不是我的意思。但是把自己放在一个不太了解 XAML 和 MVVM 的人的位置上,你会发现这并不简单明了。
我必须同意@MarkusHütter,顺便说一下,左边的绑定可以像这样简单:{Binding Message}(更简单一点......)
@florien我不这么认为,至少对于我的用例而言。我有一个 DataTemplate 需要参考 MainWindow 的 DataContext(我的 viewmodel 类)来获取下拉菜单的选项列表(从数据库加载)。 DataTemplate 绑定到同样从数据库加载的模型对象,但它只能访问选定的选项。我必须明确设置 Path=DataContext.Message 才能使绑定起作用。这是有道理的,因为您可以对宽度/高度/等进行相对绑定。的一个控件。
B
Brad Larson

Bechir Bejaoui 在 his article here 中公开了 WPF 中 RelativeSources 的用例:

当我们尝试将给定对象的属性绑定到对象本身的另一个属性时,当我们尝试将对象的属性绑定到其相对父对象的另一个属性时,RelativeSource 是一种标记扩展,用于特定的绑定情况,在自定义控件开发的情况下以及最后在使用一系列绑定数据的差异的情况下将依赖属性值绑定到一段 XAML 时。所有这些情况都表示为相对源模式。我将一一揭露所有这些案例。 Mode Self:想象一下这种情况,我们希望它的高度总是等于它的宽度的矩形,比如说一个正方形。我们可以使用元素名称 但是在上面这种情况下,我们有义务指明绑定对象的名称,即矩形。我们可以使用 RelativeSource 以不同的方式达到相同的目的。不必提及绑定对象的名称,并且无论何时更改高度,宽度都将始终等于高度。如果要将 Width 参数设置为高度的一半,则可以通过将转换器添加到 Binding 标记扩展来实现。现在让我们想象另一种情况: 上述情况用于将给定元素的给定属性与其直接父元素之一联系起来因为这个元素拥有一个名为 Parent 的属性。这导致我们进入另一种相对源模式,即 FindAncestor 模式。模式 FindAncestor 在这种情况下,给定元素的属性将绑定到其父级之一,即 Corse。与上述情况的主要区别在于,由您决定祖先类型和层次结构中的祖先等级以绑定属性。顺便试一下 XAML 上述情况是两个TextBlock 元素,它们嵌入在一系列边框中,而canvas 元素代表它们的分层父级。第二个 TextBlock 将在相对源级别显示给定父级的名称。所以尝试将 AncestorLevel=2 更改为 AncestorLevel=1 看看会发生什么。然后尝试将祖先的类型从 AncestorType=Border 更改为 AncestorType=Canvas,看看会发生什么。显示的文本将根据祖先类型和级别而变化。那么如果祖先级别不适合祖先类型会发生什么?这是个好问题,我知道你要问了。响应是不会抛出异常,并且不会在 TextBlock 级别显示任何内容。 TemplatedParent 此模式允许将给定的 ControlTemplate 属性与应用 ControlTemplate 的控件的属性联系起来。为了更好地理解这里的问题,下面是一个示例 如果我想应用的属性一个给定的控件到它的控件模板然后我可以使用 TemplatedParent 模式。还有一个与此标记扩展类似的,即 TemplateBinding,它是第一个的一种简写,但 TemplateBinding 是在编译时评估的,而 TemplatedParent 是在第一次运行后评估的。如下图所示,背景和内容从按钮内部应用到控件模板。


对我来说非常好的示例,使用 Find Ancestor 将命令绑定到父 ListView 的数据上下文中。父级在其下方还有 2 个 ListView 级别。这有助于我防止将数据传递到每个 ListViewDataTemplate 的每个后续 vm
P
Peter Mortensen

在 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 绑定的所有用例。

Here is a reference link


太棒了..这对我有用: 我试图绑定到父窗口的 selectedbuyer.IsPaid 属性
B
Bob King

不要忘记 TemplatedParent:

<Binding RelativeSource="{RelativeSource TemplatedParent}"/>

或者

{Binding RelativeSource={RelativeSource TemplatedParent}}

M
Matthew Black

值得注意的是,对于那些偶然发现 Silverlight 这种想法的人:

Silverlight 仅提供这些命令的一个缩减子集


是的,我也在寻找 SL 支持。投票:connect.microsoft.com/VisualStudio/feedback/details/480603/…
L
Luis Perez

我创建了一个库来简化 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 的本机部分。如果没有那个,“之前”的例子会更长。


这种手把手的练习表明了 XAML 的弱点;太复杂了。
P
Peter Mortensen

一些有用的点点滴滴:

以下是主要在代码中执行此操作的方法:

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


我对 WPF 的模糊记忆是,在代码中进行绑定可能通常不是最好的事情。
C
Community

我刚刚发布了 another solution 以访问适用于我的 Silverlight 中父元素的 DataContext。它使用 Binding ElementName


E
Edd

这是在空数据网格上使用此模式的示例。

<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>

P
Peter Mortensen

我没有阅读每个答案,但我只想添加此信息以防按钮的相对源命令绑定。

当您使用具有 Mode=FindAncestor 的相对源时,绑定必须类似于:

Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"

如果您不在路径中添加 DataContext,则在执行时它无法检索该属性。


j
james.lee

我不断更新我对绑定的研究。

👉 原创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的形式声明要创建的类型的值。 2020 ...

所有类型的值 很少有将值类型直接绑定到 DataContext 的情况。因为我们要绑定一个对象。 true 7.77 。 ..

另一种类型不仅可以是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}"/>

C
Community

如果一个元素不是可视化树的一部分,那么 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 属性正确显示或隐藏列。