WPF MVVM:设置选项卡视图的 DataContext

WPF MVVM: Setting DataContext of Tab Views

我遇到了 描述的奇怪的绑定行为。我做了很多故障排除,得出的结论是最可能的问题在于我如何设置每个选项卡视图的 DataContext

我有一个 TabControl,其 ItemsSource 绑定到 ViewModels 的列表。

MainView:
<TabControl ItemsSource="{Binding TabList}">
    <TabControl.Resources>
        <DataTemplate DataType="{x:Type vm:Tab1ViewModel}">
            <v:Tab1 />
        </DataTemplate>
    </TabControl.Resources>
...
</TabControl>

MainViewModel:
public ObservableCollection<TabViewModelBase> TabList { get; set; }
public MainViewModel()
{
    this.TabList = new ObservableCollection<TabViewModelBase>();

    // Tab1ViewModel is derived from TabViewModelBase
    this.TabList.Add(new Tab1ViewModel()); 
}

所以,现在 MainViewModel 有一个 TabViewModelBase 的列表,我相信这是执行此操作的正确 MVVM 方法。 TabViewModelBase 的视图 (Tab1) 是使用 DataTemplate.

定义的

这就是问题所在:

Tab1:
<UserControl.Resources>
    <vm:Tab1ViewModel x:Key="VM" />
</UserControl.Resources>
<UserControl.DataContext>
    <StaticResourceExtension ResourceKey="VM" />
</UserControl.DataContext>

我想大多数人也会这样做,但是... 这种方法有些地方非常错误

MainViewModel中,我手动实例化了一个Tab1ViewModel。在 MainView 中,我使用 DataTemplate 告诉视图在看到 Tab1ViewModel 时使用 Tab1。这意味着 MainView 将实例化 Tab1 class.

的对象

现在,Tab1需要它的DataContext和它自己的Tab1ViewModel做绑定,所以我们用StaticResource加一个Tab1ViewModel,除了这是一个全新的实例!

我需要将 DataContext 设置回我在 MainViewModel 中实例化的原始值。那么,如何在DataTemplate中设置Tab1DataContext呢?

您不必在 XAML 中指定 vm:Tab1ViewModel 新实例。您也不需要明确定义 DataContext 。只要 ViewModel 的类型与您在 DataTemplate 中定义的类型匹配,列表中的每个项目都是 ViewModel 将呈现微粒 view 并且具有相同的 DataContext 作为 ViewModel。例如,如果列表有如下两个对象:

public ObservableCollection<TabViewModelBase> TabList { get; set; }
public MainViewModel()
{
    this.TabList = new ObservableCollection<TabViewModelBase>();
    this.TabList.Add(new Tab1ViewModel1()); 
    this.TabList.Add(new Tab1ViewModel2()); 
}

你的 DataTemplate 是:

<TabControl ItemsSource="{Binding TabList}">
<TabControl.Resources>
    <DataTemplate DataType="{x:Type vm:Tab1ViewModel}">
        <v:Tab1 />
    </DataTemplate>
   <DataTemplate DataType="{x:Type vm:Tab1ViewModel2}">
        <v:Tab2 />
    </DataTemplate>
</TabControl.Resources>

...

then Two tabs will be renders Tab1 & Tab2 (cause list has 2 items). Tab1 will have Tab1ViewModel1as DataContext and and Tab2 will have Tab1ViewModel2 as DataContext. You need not specify DataContext explicitly.

只是@KyloRen 回答的补充:这就是所谓的“ViewModel-First approach”。根据您的视图模型,选择视图 -> 您首先拥有视图模型。

但是,您的视图甚至不需要数据模板。为每个视图编写数据模板可能很烦人。

有相同 "ViewModel-First" 原则的替代实现:

<TabControl ItemsSource="{Binding TabList}">
  <TabControl.ItemTemplate>
     <DataTemplate>
        <ContentPresenter Content={Binding Converter={ViewModelToViewConverter}} />
     </DataTemplate>
  </TabControl.ItemTemplate>
</TabControl>

ViewModelToViewConverter 采用 ViewModel 并根据命名约定为其创建视图。这在基于页面的导航场景中特别有用,但它是适用于许多情况(导航、列表框、项目控件、动态内容呈现器等)的通用方法。

可以找到转换器的示例 - 只需将 IocContainer 替换为 Activator.CreateInstance