2

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 は、既定で双方向にバインドする DependencyProperty として登録されている ParentModelVM です。タイプ ChildModelVM の ViewModel プロパティを持つ EditChildModel と呼ばれる同様の UserControl があり、デフォルトで双方向にバインドする DependencyProperty としても登録されています。

このロジックは私には理にかなっているようです。ParentModelVM には、Text プロパティがバインドされている TextBox コントロールで編集される文字列があり、ViewModel プロパティがバインドされている EditChildModel コントロールで編集される ChildModelVM があります。

ParentModelVM.Name はそのテキスト ボックスに正しくバインドされ、2 つの 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

テキストの代わりに「Says」と呼ばれる DependencyProperty を使用して、TextBox とまったく同じように機能する「SuperTextControl」と呼ばれる UserControl を作成しました。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

次に、SimpleParent DependencyProperty を持つ SimpleParentControl を作成しました。SimpleParent プロパティにバインドする他のコントロール内にこのコントロールを入れ子にする可能性があるため、DP として持っています。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」と表示されます。local:SuperTextControl には何も表示されません。これは、再利用可能な UserControl の作成について考えられる最も単純な例ですが、うまくいきません。確かに、再利用可能な UserControl をカスタム テキスト ボックスと同じくらい簡単に作成することに成功した人はいますが、これを行う方法について具体的に説明しているオンライン チュートリアルはありません。これは、一見理由もなく失敗する非常に些細な例です。これについての洞察をいただければ幸いです。ありがとう。

4

1 に答える 1

6

全体の問題は、DataContext を UserControl レベルで設定していたことでした。これは、親コントロールを「覗き見」ていました。問題を明確にしたブログ投稿を指摘してくれた LPL に感謝します: A Simple Pattern for Creation 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 自体ではなく、UserControl の最も外側の子に「内部」DataContext を設定します。

<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 に答える