如何基于DataTemplate的DataContext对象创建ViewModel?

时间:2011-06-30 16:02:32

标签: c# wpf xaml mvvm

我已经和MVVM合作了一段时间,这个问题(如果这是一个问题)让我一直都难过。

我有一个ItemsControl绑定到我的MainViewModel

中的一个集合

视图模型

public class MainViewModel : ViewModelBase
{
    public ObservableCollection<string> Names { get; set; }
}

XAML

<ItemsControl ItemsSource="{Binding Names}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <view:NameView />
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

每个的DataContext属性都是string类型(直接绑定到Model),但如果我想将DataContext绑定到基于该属性的ViewModel,该怎么办呢?我将如何实例化ViewModel并将其提供给Model(字符串)。

我希望这是有道理的。

2 个答案:

答案 0 :(得分:1)

为什么不这样做?

public ObservableCollection<NameViewModel> Names { get; set; }

这看起来有点奇怪,但AFAIK没有具体禁止一个VM了解其他人。

如果您使用DI进行VM分辨率,显然您的设计必须进行调整。例如,您可以创建一个NamesView,它是一个UserControl,其公共DependencyProperty类型为IEnumerable<string>。然后,NamesView的ViewModel绑定到此DP ...

答案 1 :(得分:1)

我认为您在询问数据模板匹配。

如果要绑定以选择用于在运行时呈现对象的数据模板,最简单的方法是将模板放在资源字典中并设置其DataType属性,例如:

<ItemsControl ItemsSource="{Binding Things}">
  <ItemsControl.Resources>
     <DataTemplate DataType="{x:Type Thing1}">
        ...
     </DataTemplate>
     <DataTemplate DataType="{x:Type Thing2}">
        ...
     </DataTemplate>
  </ItemsControl.Resources>
</ItemsControl>

现在,如果您的视图模型(而不是Names属性)具有Things属性:

public ObservableCollection<object> Things { get; set; }

您可以使用Thing1Thing2个对象填充该集合,ItemsControl将为每个对象提供相应的模板。

如果您想根据属性的选择不同的模板,还有几种方法可以完成此操作。一种是编写DataTemplateSelector,它可以让您对选择的模板进行非常细粒度的控制,但需要您实际编写代码(并测试和记录)。

另一种方法是使用样式根据触发器显示和隐藏内容。这实际上并没有选择不同的模板本身,但它实现了或多或少相同的事情。将它放在您的项目模板中,当Name为“Thing1”时将显示一组内容,而当Name为“Thing2”时显示另一组内容。

关于这种方法的非常好处是,与模板选择不同,这是动态的:如果Name属性的值在运行时更改(并且您的视图模型实现了属性更改)通知),视图中显示的内容也是如此。

<StackPanel>
   <ContentControl>
      <ContentControl.Style>
         <Style TargetType="ContentControl">
            <Setter Property="Visibility" Value="Collapsed"/>
            <Style.Triggers>
               <DataTrigger Binding="{Binding Name}" Value="Thing1">
                  <Setter Property="Visibility" Value="Visible"/>
               </DataTrigger>
            </Style.Triggers>
         </Style>
      </ContentControl.Style>
      <!-- content to display when Name = Thing1 goes here -->
   </ContentControl>
   <ContentControl>
      <ContentControl.Style>
         <Style TargetType="ContentControl">
            <Setter Property="Visibility" Value="Collapsed"/>
            <Style.Triggers>
               <DataTrigger Binding="{Binding Name}" Value="Thing2">
                  <Setter Property="Visibility" Value="Visible"/>
               </DataTrigger>
            </Style.Triggers>
         </Style>
      </ContentControl.Style>
      <!-- content to display when Name = Thing2 goes here -->
   </ContentControl>
</StackPanel>

最后,如果你要做的事实上是根据Names集合中字符串的值在集合中创建不同的视图模型,那么你可以在查看模型。你可以这样做:

public IEnumerable<object> ViewModels
{
   get
   {
      foreach (string name in Names)
      {
         switch (name)
         {
            case "Thing1":  yield return new Thing1ViewModel(name);
            case "Thing2":  yield return new Thing2ViewModel(name);
            default:  throw new InvalidOperationException();
         }
      }
   }
}

如果Names中的值在运行时发生变化,则会出现更复杂的问题:您需要将ViewModels实现为ObservableCollection<object>属性,您可能需要去至少处理Names集合上的集合更改事件,并在添加,删除或更改ViewModels中的项目时更新Names集合。