it-swarm.cn

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

如何在WPF绑定中使用RelativeSource以及不同的用例是什么?

547
David Schmitt

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

{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}

如果您想获得祖先的财产:

{Binding Path=PathToProperty,
    RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}

如果要在模板化父级上获取属性(因此可以在ControlTemplate中执行双向绑定)

{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}

或者,更短(这仅适用于OneWay绑定):

{TemplateBinding Path=PathToProperty}
729
Abe Heidebrecht
Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

RelativeSource的默认属性是Mode属性。这里给出了一整套有效值( 来自MSDN ):

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

  • TemplatedParent 指应用模板(其中存在数据绑定元素)的元素。这与设置TemplateBindingExtension类似,仅在Binding位于模板中时才适用。

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

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

125
Drew Noakes

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

enter image description here

120
Jeffrey Knight

想象一下这个案例,我们想要一个矩形,它的高度总是等于它的宽度,一个正方形让我们说。我们可以使用元素名称来完成此操作

<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}"/>

对于这种情况,我们没有义务提及绑定对象的名称,并且每当高度改变时,宽度将始终等于高度。

如果要将宽度参数设置为高度的一半,则可以通过向Binding标记扩展添加转换器来完成此操作。我们现在想象另一个案例:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Parent.ActualWidth}"/>

上面的情况用于将给定元素的给定属性绑定到其直接父元素之一,因为此元素包含一个名为Parent的属性。这导致我们进入另一个相对源模式,即FindAncestor模式。

40
lasitha edirisooriya

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}"/>

对于这种情况,我们没有义务提及绑定对象的名称,并且每当高度改变时,宽度将始终等于高度。

如果要将宽度参数设置为高度的一半,则可以通过向Binding标记扩展添加转换器来完成此操作。我们现在想象另一个案例:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
               Path=Parent.ActualWidth}"/>

上面的情况用于将给定元素的给定属性绑定到其直接父元素之一,因为此元素包含一个名为Parent的属性。这导致我们进入另一个相对源模式,即FindAncestor模式。

  1. 模式FindAncestor

在这种情况下,给定元素的属性将与其父元素之一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,看看发生了什么。

显示的文本将根据Ancestor类型和级别更改。那么如果祖先级别不适合祖先类型会发生什么?这是一个很好的问题,我知道你将要问它。响应是没有异常将被抛出,并且nothings将在TextBlock级别显示。

  1. TemplatedParent

此模式允许将给定的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的对比度进行评估,TemplatedParent在第一个运行时之后进行评估。正如您在下图中所述,背景和内容从按钮内部应用到控件模板。

34
Cornel Marian

在WPF RelativeSource绑定中暴露三个properties来设置:

1.模式: 这是一个enum,它可以有四个值:

a。 PreviousData(value=0):它将property的先前值赋给绑定的值

b。 TemplatedParent(value=1):这在定义任何控件的templates时使用,并且想要绑定到control的值/ Property。

例如, define ControlTemplate

  <ControlTemplate>
        <CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
 </ControlTemplate>

c。 Self(value=2):当我们想要从selfproperty绑定self时。

例如: checkbox上设置CommandParameter时,将Command的已检查状态发送为CheckBox

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

d。 FindAncestor(value=3):当想要从Visual Tree中的父control绑定时。

例如: 如果选中checkbox,如果recordsgrid被选中,则在header中绑定一个checkbox

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

这是一个参考链接

23
Kylo Ren

不要忘记TemplatedParent:

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

要么

{Binding RelativeSource={RelativeSource TemplatedParent}}
18
Bob King

我创建了一个库来简化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的本机部分。没有它,'BEFORE'的例子会更长。

13
Luis Perez

值得注意的是,对于那些对Silverlight这个想法感到磕磕绊绊的人:

Silverlight仅提供这些命令的简化子集

13
Matthew Black

一些有用的零碎:

以下是如何在代码中执行此操作:

Binding b = new Binding();
b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1);
b.Path = new PropertyPath("MyElementThatNeedsBinding");
MyLabel.SetBinding(ContentProperty, b);

我很大程度上是从在代码Behind中绑定相对源来复制它。

此外,就例子而言,MSDN页面非常好:RelativeSource Class

12
Nathan Cooper

我刚刚发布了 另一种解决方案 用于访问Silverlight中适用于我的父元素的DataContext。它使用Binding ElementName

10
Juve

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

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

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

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

9
Kevin VDF

这是在空数据网格上使用这种模式的一个例子。

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

如果元素不是可视树的一部分,那么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:路径= ShowPrice;的DataItem = NULL; target元素是'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,即使它们不在视觉或逻辑树中。我不知道启用此行为的确切机制,但我们将利用它来使我们的绑定工作...

我们的想法是创建一个类(我称之为BindingProxy,其原因很快就会变得很明显),它继承了Freezable并声明了一个Data依赖属性:

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属性正确显示或隐藏列。

4
Contango