与相关源绑定

Binding with relative source

我正在尝试了解 RelativeSource 的工作原理。

通过下面的设置,我希望在窗体上看到文本“我是 MainViewModel”,但是我在调​​试器中看到一个错误,MainWindow:

Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='UnderstandingBindings.ViewModels.MainViewModel', AncestorLevel='1''. BindingExpression:Path=SomeProperty; DataItem=null; target element is 'TextBlock' (Name='myText'); target property is 'Text' (type 'String')

我有一个这样的 ViewModel:

class MainViewModel
{
    public string SomeProperty { get => "I am the MainViewModel"; }
    private readonly ChildViewModel _child = new ChildViewModel();
    public ChildViewModel Child => _child;
}

class ChildViewModel
{
    public string SomeProperty { get => "I am the ChildViewModel"; }
}

主窗口 XAML 看起来像:

<Window x:Class="UnderstandingBindings.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:UnderstandingBindings.Views"
        xmlns:vm="clr-namespace:UnderstandingBindings.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel x:Name="pnlMain">
        <TextBlock x:Name="myText" Text="{Binding SomeProperty, RelativeSource={RelativeSource AncestorType={x:Type vm:MainViewModel}}}"/>
    </StackPanel>
</Window>

数据上下文分配如下:

public partial class MainWindow : Window
{
    private readonly MainViewModel _viewModel = new MainViewModel();
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = _viewModel.Child;
    }
}

wpf 查找声明绑定的 xaml 元素的祖先。你可以把它想象成沿着视觉树向上走。

您可以使用它来绑定到那个 Ancestor 上的 属性,或者必须通过它的 DataContext 属性 绑定到视图模型。例如:

<TextBlock x:Name="myText" Text="{Binding DataContext.Child.SomeProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>  

只有在您的示例中将 DataContext 设置为 MainViewModel 时才有效。如果您希望 Binding 朝着您期望的方向发展,那么您需要在 Child 上添加对 Parent Viewmodel 的引用。

relative source within a binding 的声明执行以下操作。

Gets or sets the binding source by specifying its location relative to the position of the binding target.

这意味着它将绑定的 Source 属性 设置为可视化树中的元素。这可以是当前元素 (Self) 或祖先元素(例如 StackPanel 是它包含的 TextBlock 的祖先元素)或在控件模板的情况下是模板化的父元素。这取决于您设置的 Mode。相对源允许您在绑定中的该元素上指定 属性 路径,例如它的 DataContextTag 或任何其他 属性.

您得到的错误转换为:我在从 TextBlock 开始的可视化树中搜索类型 MainViewModel 的实例。然后我检查了下一个祖先StackPanel,它不是MainViewModel。然后我检查了下一个祖先Window,它也不是MainViewModel。没有其他祖先,所以我找不到任何东西。

您在这里滥用相对来源。视图模型不是可视化树的一部分,而是充当元素的数据上下文。对于您使用数据上下文的示例,正​​确的方法就足够了。一旦你在 MainWindow 上设置数据上下文,它在所有子控件中都是 inherited,如果没有另外指定,例如在元素上显式分配不同的数据上下文。因此,MainWindow 的子项 TextBlock 将获得您分配给 MainWindow.[=46 的 DataContext 属性 的相同数据上下文=]

您的示例中的数据上下文是 ChildViewModel 的一个实例,因此为了绑定到它的 SomeProperty,您不需要相对源绑定,只需一个 属性使用相应控件的DataContext(设置为绑定源)自动解析的路径。

<TextBlock x:Name="myText" Text="{Binding SomeProperty}"/>

这将导致以下文本:我是 ChildViewModel

如果你想绑定到MainViewModelSomeProperty,你应该相应地设置DataContext

public MainWindow()
{
   InitializeComponent();
   this.DataContext = _viewModel;
}

TextBlock中的绑定同上,如果要显示MainViewModelSomeProperty

<TextBlock x:Name="myText" Text="{Binding SomeProperty}"/>

如果您想绑定 ChildViewModelSomeProperty,您可以更改路径。

<TextBlock x:Name="myText" Text="{Binding Child.SomeProperty}"/>

在这两个示例中,它都会导致以下文本:I am the MainViewModel