我如何使用WPF绑定RelativeSource?

我如何使用RelativeSource与WPF绑定和什么是不同的用例?

496181 次浏览

不要忘记TemplatedParent:

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

{Binding RelativeSource={RelativeSource TemplatedParent}}

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

{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,并且只适用于绑定在模板中的情况。

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

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

值得注意的是,对于那些无意中发现Silverlight的想法的人:

Silverlight只提供了这些命令的一个简化子集

我刚刚发布了< >强另一个解决方案< / >强来访问Silverlight中父元素的DataContext,这对我来说是有效的。它使用Binding ElementName

下面是MVVM架构的一个更直观的解释:

enter image description here

我创建了一个库来简化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进行了优化,我最后检查的RelayCommand不是WPF的本机部分。没有这个BEFORE的例子会更长。

一些有用的小片段:

以下是如何主要在代码中做到这一点:

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

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

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

.

.
  1. 模式:
想象一下这种情况,我们想要一个矩形,它的高度总是 等于它的宽度,比方说一个正方形。我们可以用 元素名称< / p >
<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将始终等于Height 高度改变了。< / p >

如果你想参数宽度是高度的一半,那么 您可以通过向Binding标记扩展添加转换器来实现这一点。 现在让我们想象另一种情况:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
Path=Parent.ActualWidth}"/>
上面的例子用于将给定元素的给定属性绑定到 它的直接父元素之一,如this元素,包含的属性为 叫家长。这就引出了另一种相对源模式 find祖宗。< / p >
  1. 模式FindAncestor
在这种情况下,给定元素的属性将被绑定到它的一个属性 父母,当然。与上述情况的主要区别在于事实 由您决定祖先类型和祖先 在层次结构中排名以绑定属性。顺便说一下,试着玩一下 这段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将显示的名称 相对源级别的给定父元素 所以试着将ancestry level =2更改为ancestry level =1,看看会发生什么 发生了。然后尝试更改祖先的类型 祖先类型=边界祖先类型=画布,看看发生了什么。< / p >

显示的文本将根据祖先类型和而改变 的水平。如果祖先级别不适合 祖先类型?这是个好问题,我知道你马上就要问了 问它。响应是不会抛出异常,也不会抛出任何异常 将显示在TextBlock级别。< / p >

  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,它在第一次运行时之后被求值。作为 你可以在下图中备注背景和内容 从按钮内部应用到控件模板。

这是我在空数据网格上使用该模式的一个示例。

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

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

这是一个enum,可以有四个值:

< em >。PreviousData (# EYZ0): < / em >property的上一个值赋给 约束值

< em > b。TemplatedParent (# EYZ0): < / em >在定义templates时使用 control.

. any控件,并希望绑定到control的值/属性

例如,定义ControlTemplate:

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

当我们想从self的selfproperty中绑定时。

checkbox的检查状态发送为CommandParameter,同时将Command设置为CheckBox

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

< em > d。FindAncestor (# EYZ0): < / em >当想要从父对象control绑定时 在# EYZ0。< / p >

如果选中header checkbox,则在records中绑定checkbox

<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />

当mode为FindAncestor时,定义什么类型的祖先

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

< em > 3。AncestorLevel: < / em >当模式是FindAncestor时,那么祖先的级别是什么(如果在visual tree中有两个相同类型的父)

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

以上是< em > # EYZ0 < / em >的所有用例。

这里是一个参考链接. .

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

当你使用Mode=FindAncestor的相对源时,绑定必须像这样:

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

如果你没有在你的路径中添加DataContext,在执行时它不能检索属性。

如果一个元素不是可视化树的一部分,那么RelativeSource将永远不能工作。

在这种情况下,您需要尝试由Thomas Levesque首创的不同技术。

他在自己的博客[WPF]当DataContext没有被继承时如何绑定到数据下有解决方案。而且它的工作效果绝对出色!

在不太可能的情况下,他的博客关闭了,附录A包含了他的文章的镜像副本。

请不要在这里评论,请直接评论他的博客文章

附录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中的Output窗口,我们会注意到下面这行:

System.Windows.Data错误:2:找不到目标元素的FrameworkElement或FrameworkContentElement。BindingExpression:路径=中的ShowPrice;DataItem =零;目标元素是DataGridTextColumn (HashCode=32685253);目标属性为' Visibility '(类型为' 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}}"/>

或者我们可以添加一个CheckBox绑定到ShowPrice,并尝试通过指定元素名称将列可见性绑定到IsChecked属性:

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

但是这些变通方法似乎都不管用,我们总是得到同样的结果……

在这一点上,似乎唯一可行的方法是改变代码背后的列可见性,这是我们在使用MVVM模式时通常倾向于避免的……但我不会这么快就放弃,至少在有其他选项可以考虑😉的情况下不会放弃

我们的问题的解决方案实际上非常简单,并利用了冷冻类。这个类的主要目的是定义具有可修改和只读状态的对象,但在我们的例子中有趣的特性是,即使冻结对象不在可视或逻辑树中,它们也可以继承DataContext。我不知道支持这种行为的确切机制,但我们将利用它来使我们的绑定工作……

这个想法是创建一个类(我称它为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

< p > # EYZ0
# EYZ0 < / p >
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。

< / p >

DataContext始终是绑定的参考点。

绑定可以直接召回DataContext类型格式的值,从最近的DataContext开始。

<TextBlock Text="{Binding}" DataContext="James"/>
绑定到Text="{Binding}"的值直接从最近的数据文本TextBlock传递。
因此,Text的绑定结果值为'James'。

< / p >
    <李> < p > # EYZ0
    当直接从Xaml为DataContext分配值时,首先需要为值类型(如Integer和Boolean)定义资源。 因为所有的字符串都被识别为String.

    1. 在Xaml中使用System mscrolib

    标准不支持简单类型变量类型。
    你可以用任何单词来定义它,但主要使用sys单词

    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    
    2. 在xaml中创建YEAR资源键

    以StaticResource的形式声明要创建的类型的值。

    <Window.Resources>
    <sys:Int32 x:Key="YEAR">2020</sys:Int32>
    </Window.Resources>
    ...
    <TextBlock Text="{Binding}" DataContext="{StaticResource YEAR"/>
    
    <李> < p > # EYZ0
    很少有Value Type直接绑定到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>
    
    <李> < p > # EYZ0
    不仅是字符串,还有各种类型。因为DataContext是一个对象类型。
    < / p >

最后……

在WPF中使用Binding时,大多数开发人员并没有完全意识到DataContext的存在、功能和重要性。

特别是如果你负责或参与一个大型WPF项目,你应该更清楚地理解应用程序的DataContext层次结构。此外,引入WPF的各种流行的MVVM框架系统,如果没有这种DataContext概念,将在自由实现功能方面产生更大的限制。
< / p >


绑定

  • DataContext绑定
  • 元素绑定
  • MultiBinding
  • 自我属性绑定
  • 查找祖先绑定
  • TemplatedParent绑定
  • 静态属性绑定

    < / >

DataContext绑定

# EYZ0

<TextBox Text="{Binding Keywords}"/>

元素绑定

<CheckBox x:Name="usingEmail"/>
<TextBlock Text="{Binding ElementName=usingEmail, Path=IsChecked}"/>

MultiBinding

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

你可以访问所有的属性和数据文本。

<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)中。

< / p >

装订不良;良好的绑定

✔️如果你想绑定的属性包含在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}"/>