我如何使用WPF绑定与RelativeSource?
我如何使用WPF绑定RelativeSource
和什么是不同的使用情况?
如果你想绑定到对象的另一个属性:
{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指向数据绑定元素的父链中的祖先。 您可以使用它来绑定到特定types的祖先或其子类。 如果要指定AncestorType和/或AncestorLevel,则使用此模式。
在MVVM体系结构的上下文中有一个更直观的解释:
想象一下,这种情况下,我们想要一个矩形,它的高度总是等于它的宽度。 我们可以使用元素名称来做到这一点
<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}"/>
对于这种情况,我们没有义务提及绑定对象的名称,并且每当高度改变时,宽度将总是等于高度。
如果你想参数的宽度是一半的高度,那么你可以通过添加一个转换器到绑定标记扩展来做到这一点。 现在想象另一个案例:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}"/>
上面的例子被用来把给定元素的给定属性绑定到它的直接父元素的一个,因为这个元素拥有一个叫做Parent的属性。 这导致我们到另一个相对来源模式,这是FindAncestor之一。
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}"/>
对于这种情况,我们没有义务提及绑定对象的名称,并且每当高度改变时,宽度将总是等于高度。
如果你想参数的宽度是一半的高度,那么你可以通过添加一个转换器到绑定标记扩展来做到这一点。 现在想象另一个案例:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}"/>
上面的例子被用来把给定元素的给定属性绑定到它的直接父元素的一个,因为这个元素拥有一个叫做Parent的属性。 这导致我们到另一个相对来源模式,这是FindAncestor之一。
- 模式FindAncestor
在这种情况下,给定元素的属性将被绑定到它的父母之一,科西嘉。 与上述情况的主要区别在于,由您决定层级中的祖先types和祖先级别来绑定属性。 顺便尝试玩这个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元素被embedded在一系列边框中,并且这些元素代表了它们的层次父代。 第二个TextBlock将在相对源级别显示给定父级的名称。
所以尝试将AncestorLevel = 2更改为AncestorLevel = 1,看看会发生什么。 然后尝试将AncestorType = Border的祖先types更改为AncestorType = Canvas,看看会发生什么。
显示的文字将根据祖先types和等级而改变。 那么如果祖先级别不适合祖先types呢? 这是一个很好的问题,我知道你要问这个问题。 响应是没有例外的将被抛出,并将在TextBlock级别显示nothings。
- 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是在第一次运行时间之后被评估的。 正如你可以在下图所示,背景和内容是从button内部应用到控制模板。
别忘了TemplatedParent:
<Binding RelativeSource="{RelativeSource TemplatedParent}"/>
要么
{Binding RelativeSource={RelativeSource TemplatedParent}}
我创build了一个库来简化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()}
你可以在这里find图书馆: http : //www.simplygoodcode.com/2012/08/simpler-wpf-binding.html
请注意,在'BEFORE'示例中,我使用的是用于方法绑定的代码已经通过使用RelayCommand
进行了优化,最后我检查的不是WPF的本地部分。 没有这个,“以前”的例子会更长。
值得注意的是,对于Silverlight这个想法的绊脚石:
Silverlight仅提供这些命令的简化子集
在WPF
RelativeSource
绑定公开了3个properties
来设置:
1.模式:这是一个enum
,可以有四个值:
一个。 PreviousData(
value=0
):它将property
的前一个值分配给绑定的property
湾 TemplatedParent(
value=1
):在定义任何控件的templates
时使用,并且要绑定到控件的value / Property。防爆。 定义
ControlTemplate
<ControlTemplate> <CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> </ControlTemplate>
C。 自我(
value=2
):当想要从self
或self
的property
绑定。防爆。 在
checkbox
设置Command
时,将checkbox
选中状态作为CommandParameter
发送
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
d。 FindAncestor(
value=3
):想要从Visual Tree
的父级control
绑定。防爆。 绑定
records
的checkbox
如果grid
,如果checkbox
header
checkbox
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type iDP:XamDataGrid}},Path=DataContext.IsHeaderChecked,Mode=TwoWay}" />
2. AncestorType:当模式是FindAncestor
然后定义祖先的types
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type iDP:XamDataGrid}}
3.AncestorLevel :当模式是FindAncestor
那么什么级别的ansector(如果在visual tree
有两个相同types的父级)
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type iDP:XamDataGrid,AncestorLevel=1}}
以上都是用于
RelativeSource binding
用例。
这是一个ref链接
由于这个问题是Google的头号问题,所以我认为我会做一些有用的小工具:
以下是如何在代码中完成的:
Binding b = new Binding(); b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1); b.Path = new PropertyPath("MyElementThatNeedsBinding"); MyLabel.SetBinding(ContentProperty, b);
我主要复制这个从: http : //social.msdn.microsoft.com/Forums/en/wpf/thread/c5a59f07-c932-4715-8774-fa7e8472b75b
此外,MSDN页面是相当不错的例子去: http : //msdn.microsoft.com/en-us/library/system.windows.data.relativesource.aspx
我刚刚发布了另一个访问Silverlight中父元素的DataContext的解决scheme 。 它使用Binding ElementName
。
这是使用这个模式的例子,在空数据网格上工作。
<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>
我没有阅读每一个答案,但我只是想添加这个信息的情况下,相对源button的命令绑定。 当您使用Mode=FindAncestor
相对源时,绑定必须如下所示:
Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"
如果您不在path中添加DataContext,则在执行时他无法检索该属性。
如果一个元素不是可视树的一部分,那么RelativeSource将永远不会工作。
在这种情况下,您需要尝试一种由Thomas Levesque开创的另一种技术。
他在他的博客[WPF]下的解决scheme如何在DataContext未被inheritance时绑定到数据 。 它绝对出色!
在不太可能的情况下,他的博客被拒绝,附录A包含他的文章的镜像副本。
请不要在这里发表评论,请直接在他的博客文章发表评论 。
附录A:博客文章的镜像
WPF中的DataContext属性非常方便,因为它是由您分配的元素的所有子元素自动inheritance的; 因此您不需要在要绑定的每个元素上重新设置它。 但是,在某些情况下,DataContext是不可访问的:它发生的元素不是可视或逻辑树的一部分。 那么绑定这些元素的属性可能是非常困难的…
我们用一个简单的例子来说明:我们想要在DataGrid中显示一个产品列表。 在网格中,我们希望能够根据ViewModel公开的ShowPrice属性的值显示或隐藏Price列。 显而易见的方法是将列的可见性绑定到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:path= ShowPrice; 的DataItem = NULL; 目标元素是'DataGridTextColumn'(HashCode = 32685253); 目标属性是“可见性”(types“可见性”)
这个消息是相当含糊的,但其含义其实很简单: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模式…但我不会放弃这么快,至less不会而有其他select可以考虑</s>
我们的问题的解决scheme其实很简单,并利用Freezable类。 这个类的主要目的是定义具有可修改和只读状态的对象,但是在我们的例子中有趣的特性是Freezable对象可以inheritanceDataContext,即使它们不在视觉或逻辑树中。 我不知道这种行为的确切机制,但我们将利用它来使我们的绑定工作…
我们的想法是创build一个类(我称之为BindingProxy,原因很快就会变得明显),它inheritanceFreezable并声明一个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}}"/>
请注意,绑定path前缀为“数据”,因为path现在是相对于BindingProxy对象。
绑定现在可以正常工作,并且根据ShowPrice属性正确显示或隐藏该列。