【问题标题】:WPF MVVM: Setting DataContext of Tab ViewsWPF MVVM:设置选项卡视图的 DataContext
【发布时间】:2018-04-14 02:01:00
【问题描述】:

我遇到了here 描述的奇怪绑定行为。我做了很多故障排除,得出的结论是,最可能的问题在于我如何设置每个选项卡视图的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 类的对象。

现在,Tab1 需要它的DataContext 与它自己的Tab1ViewModel 进行绑定,所以我们使用StaticResource 来添加一个Tab1ViewModel,只不过这是一个全新的实例!

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

【问题讨论】:

  • 只需删除您为 Tab1 显示的所有资源和 DataContext 代码。您的 Viewmodel 实例在您的 Collection 中,DataTemplate 在您的 TabControl 资源中定义
  • 那么我的Tab1 将没有DataContext,我将无法在Tab1Tab1ViewModel 之间进行任何绑定。

标签: wpf mvvm datatemplate tabcontrol datacontext


【解决方案1】:

您不必在 XAML 中指定 vm:Tab1ViewModel 新实例。您也不需要明确定义DataContext。每当ViewModel 的类型与您在DataTemplate 中定义的类型匹配时,您列表中的每一项都是ViewModel,一个特定的view 将被渲染,并且DataContextViewModel 具有相同的DataContext。例如,如果列表有两个对象,如下所示:

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>

...

然后将呈现两个选项卡 Tab1Tab2(因为列表有 2 个项目)。 Tab1 将有 Tab1ViewModel1as DataContextTab2 将有 Tab1ViewModel2DataContext。您无需指定DataContext 明确的。

【讨论】:

  • 非常感谢,我不敢相信解决方案是如此简单。现在我想打自己的头-.-
  • @Jai DataTemplate 有时会让人感到困惑,几乎每个人都按照您尝试的方式进行操作。你可以在 SO 上找到无数的等式。 :)
【解决方案2】:

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

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

同样的“ViewModel-First”原则的替代实现:

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

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

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

【讨论】:

  • 其实,我也使用Activator 方法打开新的Windows。但有时我觉得这有点“hackish”。
  • 如果你有充分的理由,例如上面提到的 namig 约定,它不是 hackish。如果您有命名约定,将视图和视图模型耦合起来,并且需要指定再次耦合视图和视图模型的数据模板,这有点违反 DRY 原则
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-15
相关资源
最近更新 更多