我有一个非常简单的复合模型,由两个类组成:
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,就像自定义文本框一样简单,但没有在线教程具体讨论如何执行此操作。这是一个非常微不足道的例子,似乎没有理由失败。我非常感谢对此的任何见解。感谢。
答案 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>