I have experienced a weird binding behavior that is described here. I did a lot of troubleshooting and I came to the conclusion that the most likely problem lies at how I set the DataContext
of each of the tab's view.
I have a TabControl
whose ItemsSource
is bound to a list of 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());
}
So, now the MainViewModel
has a list of TabViewModelBase
, which I believe is the correct MVVM way to do this. The view (Tab1
) for TabViewModelBase
is defined using DataTemplate
.
This is where the problem is:
Tab1:
<UserControl.Resources>
<vm:Tab1ViewModel x:Key="VM" />
</UserControl.Resources>
<UserControl.DataContext>
<StaticResourceExtension ResourceKey="VM" />
</UserControl.DataContext>
I think most people would do this as well, but... There is something terribly wrong with this approach!
In MainViewModel
, I have manually instantiated a Tab1ViewModel
. In MainView
, I used DataTemplate
to tell the View to use a Tab1
whenever it sees a Tab1ViewModel
. That means MainView
would instantiate an object of Tab1
class.
Now, Tab1
needs its DataContext
to do binding with its own Tab1ViewModel
, so we use StaticResource
to add one Tab1ViewModel
, except that this is a brand new instance!
I need to set the DataContext
back to the original one that I instantiated in MainViewModel
. So, how do I set the DataContext
of Tab1
within DataTemplate
?
You don't have to specify vm:Tab1ViewModel
new instance in your XAML. Neither do you need to define the DataContext
explicitly. Every item of your list is a ViewModel
whenever the type of a ViewModel
matched with the type you have defined in DataTemplate
a particulate view
will be rendered and with the same DataContext
as ViewModel
. For example if list has two objects like below:
public ObservableCollection<TabViewModelBase> TabList { get; set; }
public MainViewModel()
{
this.TabList = new ObservableCollection<TabViewModelBase>();
this.TabList.Add(new Tab1ViewModel1());
this.TabList.Add(new Tab1ViewModel2());
}
and your DataTemplate
is :
<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.