如何将WPF绑定与RelativeSource一起使用?


Answers:


782

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

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

15
对于此“ {Binding Path = PathToProperty,RelativeSource = {RelativeSource AncestorType = {x:Type typeOfAncestor}}}”,它看起来需要在“ AncestorType”之前具有“ Mode = FindAncestor”,
EdwardM

1
为了什么技术?在WPF中,当您指定时可以推断出这一点AncestorType
安倍·海德布雷希特

2
我同意@EdwardM。当我省略FindAncestor之前AncestorType,我得到以下错误:“ RelativeSource不在FindAncestor模式下”。(在VS2013中,社区版本)
kmote 17'Apr 10'3

1
@kmote,从.net 3.0开始,这对我一直有效,我再次验证了它在kaxaml中的工作方式。对于WPF / Silverlight / UWP,XAML处理器有所不同,因此在不同的技术上可能会有不同的结果。您还提到了VS社区,所以也许这是一个IDE警告,但在运行时有效吗?
安倍·海德布雷希特

6
只是想在这里指出,如果要绑定到RelativeSource的DataContext中的属性,则必须显式指定它:{Binding Path=DataContext.SomeProperty, RelativeSource=...。当我尝试绑定到DataTemplate中父级的DataContext时,这对我作为新手来说有些意外。
DrEsperanto

133
Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

的默认属性RelativeSourceMode属性。此处提供了一组完整的有效值(来自MSDN):

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

  • TemplatedParent引用模板(数据绑定元素存在于其中)所应用到的元素。这类似于设置TemplateBindingExtension,并且仅在Binding在模板内时适用。

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

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


128

在MVVM体系结构的上下文中,这是更直观的解释:

在此处输入图片说明


19
我错过了什么?您如何看待一个简单清晰的图形?1:左边的框与右边的框没有真正的联系(为什么ViewModel中有一个.cs文件?)2:这些DataContext箭头指向什么位置?3:为什么Message属性不在ViewModel1中?最重要的5:为什么如果TextBlock已经具有相同的DataContext,则为什么需要RelativeSource绑定才能到达Window的DataContext?我在这里显然缺少了一些东西,所以我要么很笨,要么这张图并不像每个人所想的那么简单明了!请启发我
MarkusHütter'16

2
@MarkusHütter该图显示了一组嵌套的Views和对应的ViewModels。View1的DataContext是ViewModel1,但它想绑定到BaseViewModel的属性。因为BaseViewModel是BaseView(它是一个Window)的DataContext,所以可以通过找到第一个父容器(它是Window)并获取其DataContext来做到这一点。
mcargille '16

6
@MatthewCargille我非常清楚地知道什么它应该意味着,这不是我的观点。但是将自己置于对XAML和MVVM不太了解的人的位置,您会发现这并不简单明了
MarkusHütter'16

1
我必须同意@MarkusHütter,顺便说一句,左边的绑定可能像这样简单:({Binding Message}稍微简单一点……)
florien

@florien我不这么认为,至少在我的用例中。我有一个DataTemplate,它需要引用MainWindow的DataContext(我的viewmodel类)以获取下拉菜单(从数据库加载)的选项列表。DataTemplate绑定到了一个模型对象,该模型对象也从数据库中加载,但是它只能访问所选的选项。我必须显式设置Path=DataContext.Message绑定才能正常工作。鉴于您可以对width / height / etc进行相对绑定,因此这很有意义。控件。
DrEsperanto

47

Bechir Bejaoui在他的文章中公开了WPF中RelativeSources的用例:

RelativeSource是一个标记扩展,在特定的绑定情况下,当我们尝试将给定对象的属性绑定到对象本身的另一个属性时,当我们尝试将对象的属性绑定到另一个相对父对象时,在自定义控件开发的情况下,最终在使用一系列绑定数据的差异时,将依赖项属性值绑定到XAML时。所有这些情况都表示为相对源模式。我将一一介绍所有这些情况。

  1. 模式自我:

想象一下这种情况,假设一个矩形,我们希望其高度始终等于其宽度,比如说一个正方形。我们可以使用元素名称来做到这一点

<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模式。

  1. 模式查找祖先

在这种情况下,给定元素的属性将与其父级之一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元素嵌入了一系列边框,而canvas元素代表了它们的层次结构父级。第二个TextBlock将在相对源级别显示给定父对象的名称。

因此,尝试将AncestorLevel = 2更改为AncestorLevel = 1,然后看看会发生什么。然后尝试将祖先的类型从AncestorType = Border更改为AncestorType = Canvas,然后看看会发生什么。

显示的文本将根据祖先的类型和级别而变化。如果祖先级别不适合祖先类型,那会发生什么?这是一个好问题,我知道您将要问这个问题。响应是不会抛出异常,并且在TextBlock级别不会显示任何内容。

  1. 模板化家长

通过此模式,可以将给定的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的对比度进行评估的,而后者恰好是在第一次运行后才进行评估的。如您在风箱图中标记的那样,背景和内容从按钮内应用于控件模板。


对我而言,非常好的示例是使用Find Ancestor绑定到parent的数据上下文中的命令ListView。父ListView级下还有2个层级。这帮助了我防止数据传递到每个每个后续VM ListViewDataTemplate
迦勒W.

33

在WPF RelativeSource绑定中公开了三个properties设置:

1.模式:模式enum可以具有四个值:

一个。PreviousData(value=0):将的先前值分配给property绑定的

b。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或a绑定时property

例如:设置on 时发送checkboxas的选中状态CommandParameterCommandCheckBox

<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />

d。FindAncestor(value=3):当要与中的父项绑定controlVisual Tree

例如:如果选中了,if checkboxrecords则将a 绑定gridheader checkbox

<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什么 时候mode才是祖先的级别(如果中有两种相同类型的parentvisual tree

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}

以上是的所有用例RelativeSource binding

这是参考链接


2
太棒了,这对我有用:<DataGridCheckBoxColumn Header =“ Paid” Width =“ 35” Binding =“ {Binding RelativeSource = {RelativeSource Mode = FindAncestor,AncestorType = {x:Type Window}},Path = DataContext.SelectedBuyer.IsPaid ,Mode = OneWay}“ />的位置,我试图绑定到父窗口的selectedbuyer.IsPaid属性
Michael K,

21

不要忘记TemplatedParent:

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

要么

{Binding RelativeSource={RelativeSource TemplatedParent}}


16

我创建了一个库来简化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的本机部分,已经对代码进行了优化。否则,“之前”的例子就更长了。


2
这种手工练习证明了XAML的弱点。方式太复杂了。
dudeNumber4

16

一些有用的细节:

这主要是通过代码完成的:

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复制了此代码

同样,就示例而言,MSDN页面也相当不错:RelativeSource类


5
我对WPF的模糊记忆是,虽然在代码中进行绑定通常并不是最好的选择。
内森·库珀


10

我没有阅读所有答案,但是我只是想在按钮的相对源命令绑定的情况下添加此信息。

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

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

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


9

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

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

6

如果元素不属于视觉树,则RelativeSource将永远无法工作。

在这种情况下,您需要尝试由Thomas Levesque开创的另一种技术。

他在[WPF]下的博客上有解决方案,当不继承DataContext时如何绑定数据。而且它绝对出色!

万一他的博客被关闭,这种情况极少发生,附录A包含了他的文章的镜像副本。

请不要在此处发表评论,请直接在他的博客文章中发表评论

附录A:博客文章的镜像

WPF中的DataContext属性非常方便,因为它由您为其分配元素的所有子元素自动继承。因此,您无需在要绑定的每个元素上再次设置它。但是,在某些情况下,DataContext是不可访问的:它发生在不属于可视或逻辑树的元素中。然后在这些元素上绑定属性可能非常困难……

让我们用一个简单的示例进行说明:我们想在DataGrid中显示产品列表。在网格中,我们希望能够基于ViewModel公开的ShowPrice属性的值来显示或隐藏Price列。一种明显的方法是将列的可见性绑定到ShowPrice属性:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding ShowPrice,
                Converter={StaticResource visibilityConverter}}"/>

不幸的是,更改ShowPrice的值无效,并且该列始终可见...为什么?如果我们在Visual Studio中查看“输出”窗口,则会注意到以下行:

System.Windows.Data错误:2:找不到目标元素的管理FrameworkElement或FrameworkContentElement。BindingExpression:Path = ShowPrice; DataItem = null; 目标元素是“ 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}"/>

但是这些变通办法似乎都不起作用,我们总是得到相同的结果……

在这一点上,似乎唯一可行的方法是更改​​后台代码中的列可见性,在使用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轻松访问)作为绑定的源:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding Data.ShowPrice,
                Converter={StaticResource visibilityConverter},
                Source={StaticResource proxy}}"/>

请注意,绑定路径的前缀为“ Data”,因为该路径现在是相对于BindingProxy对象的。

绑定现在可以正常工作,并且可以根据ShowPrice属性正确显示或隐藏该列。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.