The error you observe is pretty obvious.
You define the ItemsSource
of your TabControl
as a CompositeCollection
that contains elements of different types:
- a
TabItem
"General";
- a bunch of
Person
viewmodels.
So you're just mixing in one collection a view and some viewmodels - that's not neat. WPF informs you about this with the error message. The engine tries to create views (using DataTemplate
s) for the items and suddenly encounters an already specified view (a TabItem
) that is exactly of type of the item container (because for the TabControl
, a view for each viewmodel will be inserted in a TabItem
container). So WPF simply inserts the TabItem
into the TabControl
and notifies that it has not used any ItemTemplate
or ItemTemplateSelector
for creating it.
You could simply ignore this error, because in the end the control should look like you want it to (I suppose).
An alternative (and probably neater) way is not to mix views and viewmodels in one collection, but rather specify a "general" viewmodel for the "General" tab:
<TabControl.ItemsSource>
<CompositeCollection>
<viewModel:GeneralViewModel/>
<CollectionContainer Collection="{Binding Source={StaticResource personCollection}}" />
</CompositeCollection>
</TabControl.ItemsSource>
And of course you then need to tell WPF how to visualize it:
<TabControl.Resources>
<DataTemplate DataType="{x:Type viewModel:GeneralViewModel}">
<local:GeneralView />
</DataTemplate>
<!-- ... -->
</TabControl.Resources>
Update
To address the issues in your comments.
1.
How do I bind the GeneralViewModel to the one that exist in my DataContext?
This is possible, but with some overhead. You have to create a binding proxy for this. (Take a look here.)
The second thing you will need is a markup extension:
class BindingProxyValue : MarkupExtension
{
public BindingProxy Proxy { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Proxy.DataContext;
}
}
Use this markup extension together with the binding proxy in your collection:
<TabControl.Resources>
<local:BindingProxy x:Key="Proxy" DataContext="{Binding GeneralViewModel}"/>
</TabControl.Resources>
<!--...-->
<CompositeCollection>
<local:BindingProxyValue Proxy="{StaticResource Proxy}"/>
<CollectionContainer Collection="{Binding Source={StaticResource personCollection}}" />
</CompositeCollection>
You can extend the markup extension as you like, e.g. in such a way that it can observe the object updates and replace the item in the target CompositeCollection
.
2.
How do I specify general tab's header name?
You can use ItemTemplate
s, but it becomes a little bit complicated. You have to implement a DataTemplateSelector
for your TabControl
:
class YourTabItemDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
if (item is GeneralViewmodel)
{
return (DataTemplate)element.FindResource("GeneralTabItemTemplate");
}
else
{
return (DataTemplate)element.FindResource("PersonTabItemTemplate");
}
}
return null;
}
}
Then you can define the different ItemTemplate
s for different TabItem
s:
<TabControl.Resources>
<!-- ... -->
<DataTemplate x:Key="GeneralTabItemTemplate">
<TextBlock Text="General" />
</DataTemplate>
<DataTemplate x:Key="PersonTabItemTemplate">
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</TabControl.Resources>
The question is: is this effort worth it or are you okay with that error message 26? You decide.