使用MVVM模式创建用户控件-DataContext和绑定到父级

4

我使用MVVM模式,所以我的自定义控件包含View和ViewModel。
ViewModel通过DataContext属性与View连接。这会导致绑定问题。为什么呢?

假设有这样一种情况:
我创建了一个新的用户控件-例如-"SuperTextBox"。它有一个属性"SuperValue"。
现在我做了这样的事情:

<Window>
    <Window.DataContext>
        <vm:WindowViewModel/>
    </Window.DataContext>

    <local:SuperTextBox SuperValue="{Binding Test}"/>
</Window>

我认为"绑定过程"将SuperTextBox.SuperValue与Window.DataContext.Test连接起来,但实际上,"绑定过程"将SuperTextBox.SuperValue与SuperTextBox.DataContext.Test连接起来,这对我来说是不自然和具有误导性的。
像"TextBox"这样的其他控件,我可以使用上述方式,因为它们没有它们自己的DataContext。
如何使用MVVM模式创建UserControl并保持自然绑定(到父控件的DataContext)?
编辑:
我得到了许多关于绑定到父级的答案,但这是我早就知道的。 问题是 - 如何通过MVVM模式创建UserControl(具有ViewModel),并保持自然绑定 - 默认绑定到父级DataContext。
我想拥有ViewModel,仍然可以像这样进行绑定:
<local:SuperTextBox SuperValue="{Binding Test}"/>

"是可能的吗?"

当您创建具有依赖属性的真实用户控件时,就可以像我发布的那样实现。但是,我将编辑我的答案以展示另一种“不自然”的方法 :) - blindmeis
4个回答

3

所有应用于任何控件的绑定都首先在其DataContext中查找绑定。如果没有为控件设置DataContext,则它会沿着可视化树向上走直到找到DataContext。

即使您将DataContext设置为与窗口的DataContext不同的某个值,它也始终会在该特定DataContext上搜索Test属性,而不是在窗口的DataContext上搜索。

<TextBox>
   <TextBox.DataContext>
      <vm:ViewModelForTextBox/>
   </TextBox.DataContext>
   <TextBox.Text>
      <Binding Path="Test"/>
   </TextBox.Text>
</TextBox>

现在,xaml将在ViewModelForTextBox类中寻找Test属性,而不是在WindowViewModel类中查找。如果在ViewModelForTextBox类中没有找到Test属性,binding将会静默失败,并且不会查找到Window的DataContext类。
如果您仍然想为自定义UserControl设置DataContext,但仍要绑定到父级(Window)的DataContext,则必须在绑定中使用RelativeSource MarkupExtension,如下所示:
<local:SuperTextBox SuperValue="{Binding Path=DataContext.Test,
                                RelativeSource={RelativeSource Mode=FindAncestor,
                                                 AncestorType={x:Type Window}}}">

请参考MSDN文章这里以获得更多解释。

2

你应该发布你的SuperTextBox代码,因为你的错误可能在其中。

通常情况下,你会创建一个带有依赖属性的用户控件 - 在你的情况下是“SuperValue” - 现在最重要的事情是你没有将SuperTextBox的数据上下文设置为自身。

你需要在SuperTextBox中使用元素名称绑定来绑定到“SuperValue”。

 <SuperTextBox x:Name="uc">
   <TextBox Text="{Binding ElementName=uc, Path=SuperValue}/>
 </superTextBox>

如果您以这种方式进行操作 - 您的

 <local:SuperTextBox SuperValue="{Binding Test}"/>

应该能够工作,并绑定到您的vm:WindowViewModel的Test属性。这是写上述绑定的唯一方法。

编辑:如果您想为用户控件创建一个视图模型,比如说SuperTextViewmodel。那么它将有一个名为"SuperValue"的属性。现在您不能设置数据上下文两次,所以我建议您在WindowViewmodel中添加一个SuperTextViewmodel类型的属性,并根据需要处理属性。

你的绑定看起来像这样:

 <local:SuperTextBox DataContext="{Binding MySuperTextViewmodelInstanceOnWindowViewmodel}"/>

我会选择我的第一个答案 :) 我经常说视图需要一个视图模型,但用户控件需要依赖属性。

谢谢您的回答,但当我使用MVVM模式创建用户控件时,我必须拥有(或者我想拥有)ViewModel。 因此,要将ViewModel注入View中,我必须使用DataContext,这使绑定变得不自然。我的问题更多是如何避免这种情况 - 我可以用另一种方式加入ViewModel吗(不会破坏绑定)? - Never

1

我觉得回答自己的问题有些奇怪,但是...
在我的掌控下,我做了这样的事情:

<UserControl>
    <Grid>
        <Grid.DataContext>
             <vm:UserControlViewModel />
        </Grid.DataContext>
        // here realy code of control
    </Grid>
</UserControl>

现在我可以在控件内外使用“自然”绑定了。 :)

1

您需要“查找”其窗口祖先的数据上下文。您的绑定应该像这样:

<local:SuperTextBox SuperValue="{Binding Path=DataContext.Test, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">

谢谢,但我已经知道了。 :) 我需要更多关于通过MVVM模式创建UserControl的“模式和实践”的信息。我能否使用MVVM并仍然像“TextBox”控件一样拥有正常/自然的绑定? - Never
这并不是什么不自然的事情。对于任何具有数据上下文为其ItemsSource中单个项目的控件元素,您都必须执行相同的操作。例如,如果您想将ListViewItem的可见性绑定到视图模型中的某个值,但ListView具有设置的ItemsSource,则必须使用此绑定方法。 - Lee O.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接