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
中设置Tab1
的DataContext
呢?
您不必在 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 Tab1ViewModel1
as 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
我遇到了 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
中设置Tab1
的DataContext
呢?
您不必在 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 haveTab1ViewModel1
asDataContext
and andTab2
will haveTab1ViewModel2
asDataContext
. You need not specifyDataContext
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 并根据命名约定为其创建视图。这在基于页面的导航场景中特别有用,但它是适用于许多情况(导航、列表框、项目控件、动态内容呈现器等)的通用方法。
可以找到转换器的示例 Activator.CreateInstance