2012-04-28 3 views
1

У меня есть очень простая составная модель, выполненная из двух классов:переплет Вложенных управлений пользователя в Composite View Model

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, который связывает двухстороннее по умолчанию. У меня есть аналогичный UserControl с именем EditChildModel, у которого есть свойство ViewModel типа ChildModelVM, также зарегистрированное как DependencyProperty, которое по умолчанию связывает двусторонний путь.

Эта логика, по-видимому, имеет смысл для меня: ParentModelVM имеет строку, которая редактируется с помощью элемента управления TextBox, свойство Text которого привязано, и в нем есть ChildModelVM, который редактируется с помощью элемента управления EditChildModel, свойство которого связано с ViewModel.

ParentModelVM.Name правильно привязывается к его текстовому полю, а два свойства ChildViewModelVM корректно привязываются к их текстовым полям. Тем не менее, EditParentModel.ViewModel.ChildModel: не тот же объект, что и EditChildModel.ViewModel, и я не могу понять, почему. Поведение всего приложения точно такое же, если я удалю атрибут ViewModel="{Binding Path=ChildModel}" из EditParentModel UserControl. Например, NameInput инициализирует «родительскую модель», но EditChildModel.NameInput не инициализирует «детскую модель», как я ожидаю.

Любая помощь в этом была бы принята с благодарностью. Благодаря!

--Edit--

Хорошо, я упростил это за абсурд, и он до сих пор не работает. У меня есть модель под названием 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 

Я создал UserControl под названием «SuperTextControl», который действует так же, как TextBox, с DependencyProperty под названием «Говорит» вместо текста. Вот весь 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 отображает "Это какой-то текст", как и ожидалось. Локальный: SuperTextControl ничего не отображает. Это самый простой пример, который я могу придумать о создании многократно используемого UserControl, но все же он не работает. Конечно, кто-то добился успеха, создав повторно используемый UserControl так же просто, как пользовательское текстовое поле, но ни одно онлайн-учебники не обсуждают конкретно, как это сделать. Это чрезвычайно тривиальный пример, который, по-видимому, не имеет причины. Я был бы очень признателен за понимание этого. Благодарю.

+0

вы можете использовать [WPFInspector] (wpfinspector.codeplex.com), чтобы увидеть, что это 'DataContext' вашего пользовательского элемента управления EditChildModel. без этого шаблона и без кода, за которым трудно сказать, что вы пропустили. – stukselbax

+0

Упростите дизайн. Не используйте DP для привязки DataContext. Установите VM непосредственно в DataContext. – LPL

+0

@ LPL - Можете ли вы объяснить это дальше? Я думал, что я уже установил EditChildModel.ViewModel непосредственно в DataContext элемента управления EditParentModel через привязку. Благодарю. –

ответ

4

Вся проблема заключалась в том, что я устанавливал свой 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> 

Это имеет смысл, что это не будет работать. Мой предыдущий ответ неверен: изменение в ElementName имеет ту же проблему, если DataContext определяется таким же образом. Чтобы обойти эту проблему, установите свой «внутренний» DataContext на внешнем потомке UserControl, а не сам 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> 
+0

Ссылка не работает. Вот обновленная версия. http://blog.scottlogic.com/2012/02/06/a-simple-pattern-for-creating-re-useable-usercontrols-in-wpf-silverlight.html – rybo103

+0

Спасибо @ rybo103. Я обновил ссылку в ответе. –