2

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

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 有一个使用 TextBox 控件编辑的 String,它的 Text 属性绑定,它有一个 ChildModelVM,它使用绑定 ViewModel 属性的 EditChildModel 控件进行编辑。

ParentModelVM.Name 正确绑定到其文本框,并且两个 ChildViewModelVM 属性正确绑定到其文本框。但是,EditParentModel.ViewModel.ChildModel与 EditChildModel.ViewModel不是同一个对象,我不知道为什么。ViewModel="{Binding Path=ChildModel}"如果我从 EditParentModel UserControl中删除该属性,则整个应用程序的行为完全相同。例如,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”的用户控件,它的作用与 TextBox 完全一样,具有一个名为“Says”的 DependencyProperty,而不是 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 按预期显示“这是一些文本”。local:SuperTextControl 什么也不显示。这是我能想到的创建可重用 UserControl 的最简单示例,但它不起作用。当然,有人已经成功地创建了一个像自定义文本框一样简单的可重复使用的 UserControl,但是没有在线教程专门讨论如何做到这一点。这是一个极其微不足道的示例,似乎无缘无故地失败了。我非常感谢对此的任何见解。谢谢。

4

1 回答 1

6

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

它与 RelativeSource 与 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>
于 2012-05-01T23:58:53.173 回答