Answers:
如果要绑定到对象的另一个属性:
{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}
AncestorType
。
FindAncestor
之前AncestorType
,我得到以下错误:“ RelativeSource不在FindAncestor模式下”。(在VS2013中,社区版本)
{Binding Path=DataContext.SomeProperty, RelativeSource=...
。当我尝试绑定到DataTemplate中父级的DataContext时,这对我作为新手来说有些意外。
Binding RelativeSource={
RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...
的默认属性RelativeSource
是Mode
属性。此处提供了一组完整的有效值(来自MSDN):
PreviousData允许您在显示的数据项列表中绑定上一个数据项(不包含该数据项的控件)。
TemplatedParent引用模板(数据绑定元素存在于其中)所应用到的元素。这类似于设置TemplateBindingExtension,并且仅在Binding在模板内时适用。
Self指的是要在其上设置绑定的元素,并允许您将该元素的一个属性绑定到同一元素上的另一个属性。
FindAncestor引用数据绑定元素的父链中的祖先。您可以使用它绑定到特定类型的祖先或其子类。如果要指定AncestorType和/或AncestorLevel,则使用此模式。
在MVVM体系结构的上下文中,这是更直观的解释:
{Binding Message}
稍微简单一点……)
Path=DataContext.Message
绑定才能正常工作。鉴于您可以对width / height / etc进行相对绑定,因此这很有意义。控件。
Bechir Bejaoui在他的文章中公开了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}"/>
在这种情况下,我们不必提及绑定对象的名称,并且只要更改高度,宽度就始终等于高度。
如果要将Width设置为高度的一半,则可以通过在Binding标记扩展中添加转换器来实现。现在让我们想象另一种情况:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}"/>
上述情况用于将给定元素的给定属性与其直接父元素之一绑定,因为此元素拥有一个称为Parent的属性。这导致我们进入另一种相对的源模式,即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,然后看看会发生什么。
显示的文本将根据祖先的类型和级别而变化。如果祖先级别不适合祖先类型,那会发生什么?这是一个好问题,我知道您将要问这个问题。响应是不会抛出异常,并且在TextBlock级别不会显示任何内容。
- 模板化家长
通过此模式,可以将给定的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的对比度进行评估的,而后者恰好是在第一次运行后才进行评估的。如您在风箱图中标记的那样,背景和内容从按钮内应用于控件模板。
ListView
。父ListView
级下还有2个层级。这帮助了我防止数据传递到每个每个后续VM ListView
的DataTemplate
在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 时发送
checkbox
as的选中状态CommandParameter
Command
CheckBox
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
d。FindAncestor(
value=3
):当要与中的父项绑定control
时Visual Tree
。例如:如果选中了,if
checkbox
,records
则将a 绑定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
什么 时候mode才是祖先的级别(如果中有两种相同类型的parentvisual tree
)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
以上是的所有用例
RelativeSource binding
。
值得一提的是,对于那些在Silverlight思维中绊脚石的人:
Silverlight仅提供这些命令的缩减子集
我创建了一个库来简化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的本机部分,已经对代码进行了优化。否则,“之前”的例子就更长了。
一些有用的细节:
这主要是通过代码完成的:
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类
这是在空数据网格上使用此模式的示例。
<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>
如果元素不属于视觉树,则RelativeSource将永远无法工作。
在这种情况下,您需要尝试由Thomas Levesque开创的另一种技术。
他在[WPF]下的博客上有解决方案,当不继承DataContext时如何绑定数据。而且它绝对出色!
万一他的博客被关闭,这种情况极少发生,附录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属性正确显示或隐藏该列。