将嵌套用户控件绑定到复合视图模型

时间:2012-04-28 02:35:09

标签: wpf data-binding user-controls

我有一个非常简单的复合模型,由两个类组成:

Public Class ParentModelVM
  Public Property Name As String
  Public Property ChildModel As ChildModelVM

  Public Sub New()
    Name = "A Parent Model"
    ChildModel = New ChildModelVM With {.Name = "A Child Model"}
  End Sub
End Class

Public Class ChildModelVM
  Public Property Name As String
  Public Property Description As String
End Class

两者都实现了我已经缩写的INotifyPropertyChanged。我正在尝试生成用户控件来编辑ParentModelVM:

<UserControl x:Class="EditParentModel" .../>
    <UserControl.DataContext>
        <Binding RelativeSource="{RelativeSource Self}" Path="ViewModel" />
    </UserControl.DataContext>

    <TextBox Name="NameInput" Text="{Binding Path=Name}"/>
    <local:EditChildModel x:Name="ChildModelInput" ViewModel="{Binding Path=ChildModel}"/>
</UserControl>

ViewModel是一个ParentModelVM,它注册为DependencyProperty,默认情况下绑定双向。我有一个名为EditChildModel的类似UserControl,它具有ChildModelVM类型的ViewModel属性,也注册为默认绑定双向的DependencyProperty。

这个逻辑对我来说似乎有意义:ParentModelVM有一个String,它使用TextBox控件编辑,Text控件的Text属性被绑定,它有一个ChildModelVM,使用EditChildModel控件编辑,其ViewModel属性被绑定。

ParentModelVM.Name正确绑定到其文本框,并且两个ChildViewModelVM属性正确绑定到其文本框。但是,EditParentModel.ViewModel.ChildModel 不是与EditChildModel.ViewModel相同的对象,我无法弄清楚原因。如果从EditParentModel UserControl中删除ViewModel="{Binding Path=ChildModel}"属性,整个应用程序的行为完全相同。例如,NameInput使用“A Parent Model”初始化,但EditChildModel.NameInput不会像我期望的那样使用“A Child Model”进行初始化。

对此的任何帮助将不胜感激。谢谢!

- 编辑 -

好的,我已经简化了荒谬,但它仍然无效。我有一个名为SimpleParent的模型。这是整个代码:

Imports System.ComponentModel

Public Class SimpleParent
  Implements INotifyPropertyChanged

  Private _someText As String
  Public Property SomeText As String
    Get
      Return _someText
    End Get
    Set(ByVal value As String)
      _someText = value
      RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("SomeText"))
    End Set
  End Property

  Public Sub New()
    SomeText = "This is some text."
  End Sub

  Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class

我创建了一个名为“SuperTextControl”的UserControl,它的行为与TextBox完全相同,DependencyProperty称为“Says”而不是Text。这是整个XAML:

<UserControl x:Class="SuperTextControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="23" d:DesignWidth="300">
  <UserControl.DataContext>
    <Binding RelativeSource="{RelativeSource Self}"/>
  </UserControl.DataContext>
  <TextBox Name="SaysInput" Text="{Binding Path=Says}" />
</UserControl>

这是代码隐藏:

Public Class SuperTextControl

  Public Shared ReadOnly SaysProperty As DependencyProperty =
      DependencyProperty.Register("Says", GetType(String), GetType(SuperTextControl))

  Public Property Says As String
    Get
      Return CTypeDynamic(Of String)(GetValue(SaysProperty))
    End Get
    Set(ByVal value As String)
      SetValue(SaysProperty, value)
    End Set
  End Property

End Class

然后我创建了一个SimpleParentControl,它有一个SimpleParent DependencyProperty。我将它作为DP,因为我可能希望将此控件嵌套到绑定到SimpleParent属性的其他控件中。这是整个XAML:

<UserControl x:Class="SimpleParentControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfTest"             
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
  <UserControl.DataContext>
    <Binding RelativeSource="{RelativeSource Self}" Path="SimpleParent" />
  </UserControl.DataContext>
  <StackPanel>
    <TextBox Text="{Binding Path=SomeText}" />
    <local:SuperTextControl Says="{Binding Path=SomeText}" />
  </StackPanel>
</UserControl>

整个代码隐藏:

Public Class SimpleParentControl

  Public Shared ReadOnly SimpleParentProperty As DependencyProperty =
    DependencyProperty.Register("SimpleParent", GetType(SimpleParent), GetType(SimpleParentControl))

  Public Property SimpleParent As SimpleParent
    Get
      Return CTypeDynamic(Of SimpleParent)(GetValue(SimpleParentProperty))
    End Get
    Set(ByVal value As SimpleParent)
      SetValue(SimpleParentProperty, value)
    End Set
  End Property

  Public Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.
    SimpleParent = New SimpleParent()

  End Sub

End Class

SimpleParentControl中的TextBox按预期显示“This is some text”。本地:SuperTextControl什么都不显示。这是我可能想到的创建可重用的UserControl的最简单的例子,但它不起作用。当然,有人成功地创建了一个可重复使用的UserControl,就像自定义文本框一样简单,但没有在线教程具体讨论如何执行此操作。这是一个非常微不足道的例子,似乎没有理由失败。我非常感谢对此的任何见解。感谢。

1 个答案:

答案 0 :(得分:6)

整个问题是我在UserControl级别设置了我的DataContext,它正在“窥视”到父控件中。感谢LPL指向我澄清问题的博文:A Simple Pattern for Creating Re-useable UserControls in WPF / Silverlight

它与RelativeSource vs. ElementName相关,而不是在代码隐藏中设置DataContext;这些都是完成同样事情的不同方式。问题在该博客文章底部附近的图表中很明显。在子控件(SuperTextControl)中执行此操作:

<UserControl x:Class="SuperTextControl" ... >
  <UserControl.DataContext>
    <Binding RelativeSource="{RelativeSource Self}"/>
  </UserControl.DataContext>
  ...
</UserControl>

相当于在父母中声明控件:

<local:SuperTextControl Says="{Binding Path=SomeText}">
  <local:SuperTextControl.DataContext>
    <Binding RelativeSource="{RelativeSource Self}" />
  </local:SuperTextControl.DataContext>
</local:SuperTextControl>

这是行不通的。我之前的回答是不正确的:如果以相同的方式定义DataContext,则更改为ElementName会出现同样的问题。要解决这个问题,请在UserControl的最外层子节点上设置“内部”DataContext,而不是UserControl本身:

<UserControl x:Class="SuperTextControl" x:Name="SuperTextControl">
  <Grid>
    <Grid.DataContext>
      <Binding ElementName="SuperTextControl" />
    </Grid.DataContext>
    <TextBox Name="SaysInput" Text="{Binding Path=Says}" />
  </Grid>
</UserControl>