如何将RelativeSource
用于WPF绑定以及不同的用例?
答案 0 :(得分:733)
如果要绑定到对象上的另一个属性:
{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}
答案 1 :(得分:126)
Binding RelativeSource={
RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...
RelativeSource
的默认属性是Mode
属性。这里给出了一整套有效值(from MSDN):
PreviousData 允许您在显示的数据项列表中绑定上一个数据项(而不是包含数据项的控件)。
TemplatedParent 指应用模板(其中存在数据绑定元素)的元素。这与设置TemplateBindingExtension类似,仅在Binding位于模板中时才适用。
Self 指您设置绑定的元素,并允许您将该元素的一个属性绑定到同一元素上的另一个属性。
FindAncestor 指数据绑定元素的父链中的祖先。您可以使用它来绑定到特定类型或其子类的祖先。如果要指定AncestorType和/或AncestorLevel,则使用此模式。
答案 2 :(得分:120)
这是MVVM架构上下文中更直观的解释:
答案 3 :(得分:40)
想象一下这种情况,我们想要一个矩形,它的高度总是等于它的宽度,一个正方形让我们说。我们可以使用元素名称
来完成此操作<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模式。
答案 4 :(得分:34)
Bechir Bejaoui在his article here中公开了WPF中RelativeSources的用例:
RelativeSource是特别使用的标记扩展 我们尝试将给定对象的属性绑定到的绑定情况 当我们尝试绑定属性时,对象本身的另一个属性 一个对象的另一个相对父母,当绑定一个 在自定义控件的情况下,依赖属性值到一块XAML 发展,最后在使用一系列差异的情况下 绑定数据。所有这些情况都表示为相对来源 模式。我会逐一揭露所有这些案件。
- 模式自我:
醇>想象一下这个案例,我们希望它的高度始终是一个矩形 等于它的宽度,一个正方形让我们说。我们可以使用 元素名称
<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}"/>
以上情况用于将给定元素的给定属性绑定到 它的一个直接父元素,因为这个元素拥有一个属性 叫做父母。这导致我们进入另一个相对源模式 FindAncestor一个。
- 模式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将显示名称 相对来源级别的父母。
因此,尝试将AncestorLevel = 2更改为AncestorLevel = 1并查看内容 发生。然后尝试更改祖先的类型 AncestorType = AncestorType = Canvas的边框,看看发生了什么。
显示的文字将根据Ancestor类型和更改而改变 水平。如果祖先等级不适合,那会发生什么 祖先类型?这是一个很好的问题,我知道你即将到来 问它。响应是没有异常将被抛出,并且没有例外 在TextBlock级别显示。
- 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在第一次运行时之后进行评估。如 你可以在下面的图中,背景和内容中加以评论 从按钮内应用到控件模板。
答案 5 :(得分:23)
在WPF RelativeSource
绑定中,公开了三个properties
来设置:
<强> 1。模式:这是enum
,可以有四个值:
<强> 一个。 PreviousData(
value=0
): 它将property
的先前值分配给 绑定的<强> 湾TemplatedParent(
value=1
): 在定义templates
时使用 任何控件,并希望绑定到control
的值/属性。例如,定义
ControlTemplate
:
<ControlTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
<强> ℃。自我(
value=2
): 当我们想要从self
或property
自我绑定时。例如:在
上设置checkbox
CommandParameter
时,将Command
的已检查状态发送为CheckBox
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
<强> d。 FindAncestor(
value=3
): 当想要从父control
绑定时 在Visual Tree
。例如:如果选中
checkbox
records
grid
,则在header
内绑定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
时,祖先的级别(如果visual tree
中有两种相同类型的父级)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
以上是
RelativeSource binding
的所有用例。
<强> Here is a reference link 强>
答案 6 :(得分:18)
不要忘记TemplatedParent:
<Binding RelativeSource="{RelativeSource TemplatedParent}"/>
或
{Binding RelativeSource={RelativeSource TemplatedParent}}
答案 7 :(得分:13)
值得注意的是,对于那些对Silverlight这个想法感到磕磕绊绊的人:
Silverlight仅提供这些命令的简化子集
答案 8 :(得分:13)
我创建了一个库来简化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'的例子会更长。
答案 9 :(得分:12)
一些有用的点点滴滴:
以下是如何在代码中执行此操作:
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
答案 10 :(得分:10)
我刚发布了another solution,用于访问适用于我的Silverlight中父元素的DataContext。它使用Binding ElementName
。
答案 11 :(得分:9)
我没有阅读每个答案,但我只想在按钮的相对源命令绑定的情况下添加此信息。
当您使用Mode=FindAncestor
的相对来源时,绑定必须如下:
Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"
如果您未在路径中添加DataContext,则在执行时无法检索该属性。
答案 12 :(得分:8)
这是在空数据网格上使用这种模式的一个例子。
<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>
答案 13 :(得分:4)
如果元素不是可视化树的一部分,那么RelativeSource将永远不会工作。
在这种情况下,你需要尝试一种由Thomas Levesque开创的不同技术。
他在[WPF] How to bind to data when the DataContext is not inherited下的博客上找到了解决方案。它的工作非常出色!
如果他的博客发生故障,附录A包含his article的镜像副本。
请不要在此发表评论,请comment directly on his blog post。
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轻松访问)作为绑定的源:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding Data.ShowPrice,
Converter={StaticResource visibilityConverter},
Source={StaticResource proxy}}"/>
请注意,绑定路径的前缀是“Data”,因为路径现在相对于BindingProxy对象。
绑定现在可以正常工作,并且可以根据ShowPrice属性正确显示或隐藏列。
答案 14 :(得分:0)
我不断更新我对绑定的研究。
? 原版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 开始的 DataContext 类型格式的值。
<TextBlock Text="{Binding}" DataContext="James"/>
绑定到 Text="{Binding}"
的值直接从最近的 DataContext TextBlock
传递。
因此,Text
的 Binding 结果值为 'James'。
输入整数
直接从 Xaml 为 DataContext 分配值时,首先需要为值类型(如整数和布尔值)定义资源。
因为所有的字符串都被识别为字符串。
mscrolib
<块引用>
标准不支持简单类型变量类型。
您可以使用任何词来定义它,但主要使用 sys
词。
xmlns:sys="clr-namespace:System;assembly=mscorlib"
2.在xaml中创建YEAR
资源键
<块引用>
以静态资源的形式声明要创建的类型的值。
<Window.Resources>
<sys:Int32 x:Key="YEAR">2020</sys:Int32>
</Window.Resources>
...
<TextBlock Text="{Binding}" DataContext="{StaticResource YEAR"/>
所有类型的价值
值类型直接绑定到 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>
另一种类型
不仅字符串而且各种类型都是可能的。因为 DataContext 是一种对象类型。
在 WPF 中使用 Binding 时,大多数开发人员并没有完全意识到 DataContext 的存在、功能和重要性。
这可能意味着 Binding 是靠运气连接的。
特别是如果你负责或参与大型 WPF 项目,你应该更清楚地了解应用程序的 DataContext 层次结构。此外,WPF 各种流行的 MVVM Framework 系统的引入没有这个 DataContext 概念,将在自由实现功能方面造成更大的限制。
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}"/>
这是一个可以在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}"/>
您可以直接访问绑定属性值。
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)中。
通过连接控件使用 ElementBinding 不是功能问题,
但它打破了绑定的基本模式。
<TextBox x:Name="text" Text="{Binding UserName}"/>
...
<TextBlock Text="{Binding ElementName=text, Path=Text}"/>
? 绑定好
<TextBox Text="{Binding UserName}"/>
...
<TextBlock Text="{Binding UserName}"/>
<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}"/>
...
<TextBlock x:Name="txt" Text="{Binding ElementName=txt, Path=Foreground}"/>
? 绑定好
<TextBlock Text="{Binding RelativeSource={RelativeSource Self}, Path=Foreground}"/>