如何将 WPF 绑定与relativesource 结合使用?
-
01-07-2019 - |
题
我该如何使用 RelativeSource
使用 WPF 绑定以及有哪些不同的用例?
解决方案
如果你想绑定到对象的另一个属性:
{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):
历史数据 允许您绑定正在显示的数据项列表中的上一个数据项(不是包含该数据项的控件)。
模板化父级 指应用模板(其中存在数据绑定元素)的元素。这与设置 TemplateBindingExtension 类似,并且仅当 Binding 位于模板内时才适用。
自己 指您要设置绑定的元素,并允许您将该元素的一个属性绑定到同一元素上的另一个属性。
寻祖 指数据绑定元素的父链中的祖先。您可以使用它来绑定到特定类型或其子类的祖先。如果您想指定 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 的用例 他的文章在这里:
当我们试图将对象本身的另一个属性绑定到对象本身的另一个属性时,私人流是一种标记扩展,当我们试图将对象的属性绑定到其另一个相对父母,如果在自定义控制开发的情况下,将依赖属性属性值绑定到XAML的一部分时,最终在使用一系列绑定数据的情况下。所有这些情况表示为相对源模式。我会将这些案件一一曝光。
- 自我模式:
想象一下,这个情况是一个矩形,我们希望它的高度总是等于其宽度,一个广场。我们可以使用元素名称执行此操作
<Rectangle Fill="Red" Name="rectangle" Height="100" Stroke="Black" Canvas.Top="100" Canvas.Left="100" Width="{Binding ElementName=rectangle, Path=Height}"/>
但是在上述情况下,我们有义务指示绑定对象的名称,即矩形。我们可以使用亲戚来实现相同的目的
<Rectangle Fill="Red" Height="100" Stroke="Black" Width="{Binding RelativeSource={RelativeSource Self}, Path=Height}"/>
对于这种情况,我们没有义务提及绑定对象的名称,只要更改高度时,宽度将始终等于高度。
如果要参数宽度为高度的一半,则可以通过将转换器添加到绑定标记扩展名来执行此操作。现在让我们想象另一种情况:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}"/>
上面的情况用于将给定元素的给定属性与其直接母体的属性联系起来,因为此元素持有称为父的属性。这使我们进入了另一个相对源模式,即Findancestor One。
- 模式查找祖先
在这种情况下,给定元素的财产将与其一位父母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将在相对源级别显示给定父级的名称。
因此,尝试将祖先= 2更改为祖先= 1,看看会发生什么。然后,尝试将祖先的类型从祖先=边框变为祖先类型=画布,看看发生了什么。
显示的文本将根据祖先类型和级别进行更改。那么,如果祖先级别不适合祖先类型,会发生什么?这是一个很好的问题,我知道您要问它。响应也不例外,将在文本屏幕级别显示。
- 模板化父级
此模式使给定的ControlTemplate属性可以将ControlTemplate应用于控件的属性。很好地理解这里的问题是一个典型的bellow
<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对比时,在编译时评估了模板键入。正如您可以在波纹管中说明的那样,背景和内容是从按钮内部应用于控制模板的。
在WPF中 RelativeSource
绑定暴露了三个 properties
设置:
1.模式: 这是一 enum
可能有四个值:
A。上一个数据(
value=0
): 它分配之前的值property
到界一个b.模板化父级(
value=1
): 这在定义时使用templates
任何控制control
.例如, 定义
ControlTemplate
:
<ControlTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
C。自己(
value=2
): 当我们想要从 a 绑定时self
或一个property
自我的。例如: 发送检查状态
checkbox
作为CommandParameter
同时设置Command
在CheckBox
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
d.查找祖先(
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.祖先类型: 当模式为 FindAncestor
然后定义祖先的类型
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}
3.始祖等级: 当模式为 FindAncestor
那么祖先的级别是多少(如果有两个相同类型的父母 visual tree
)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
以上是所有用例
RelativeSource binding
.
不要忘记 TemplatedParent:
<Binding RelativeSource="{RelativeSource TemplatedParent}"/>
或者
{Binding RelativeSource={RelativeSource TemplatedParent}}
值得注意的是,对于那些偶然发现 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);
我很大程度上复制了这个 在代码后面绑定相对源.
此外,MSDN 页面上的示例也非常好: 相对源类
我刚刚发帖 另一种解决方案 用于访问适用于我的 Silverlight 中父元素的 DataContext。它用 Binding ElementName
.
我没有阅读每个答案,但我只想在按钮的相对源命令绑定的情况下添加此信息。
当您使用相对来源时 Mode=FindAncestor
, ,绑定必须类似于:
Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"
如果您不在路径中添加 DataContext,则在执行时它无法检索该属性。
这是在空数据网格上使用此模式的示例。
<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 包含以下内容的镜像副本: 他的文章.
请不要在这里发表评论,拜托 直接在他的博客文章上发表评论.
附录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 中的“输出”窗口,我们会注意到以下行:
系统.Windows.数据错误:2:找不到目标元素的管理 FrameworkElement 或 FrameworkContentElement。BindingExpression:路径=显示价格;数据项=空;目标元素是“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 轻松访问)作为绑定的 Source:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding Data.ShowPrice,
Converter={StaticResource visibilityConverter},
Source={StaticResource proxy}}"/>
请注意,绑定路径已带有“Data”前缀,因为该路径现在相对于 BindingProxy 对象。
现在,绑定可以正常工作,并且该列可以根据 ShowPrice 属性正确显示或隐藏。