XAML DataTemplate only ever creates one instance
The next one only ever creates one instance MyTabView
. I confirmed this by putting a constructor breakpoint in MyTabView.xaml.cs. The view is displayed in a tab, and no matter how much I created tabs, I only removed this constructor once.
<DataTemplate DataType="{x:Type vm:MyTabViewModel}" x:Shared="false">
<vw:MyTabView />
</DataTemplate>
Tab management:
<TabControl
Grid.Row="1"
ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTab}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl>
This causes all the tabs to reflect the shared access of any state that is not model-specific: When you move GridSplitter
, that is the same GridSplitter
in all the other tabs, so it appears to the user that you have moved all of them. This is absurd.
I do not understand. Is there a way to use it TabControl
with multiple elements of the same type?
EDIT: Added x:Shared="false"
in DataTemplate
.
UPDATE:
So, I found a couple of fixes, but I don't really like them. I'm going to take a look at writing a converter that converts ObservableCollection<Object>
to <T29> - sort of a live updatable version
coll.Select(vm => new TabItem() { Content = vm });
... but we'll see if he likes getting instances TabItem
from ItemsSource
. My money says don't bet on it. But let's see.
UPDATE 2: Took a while to get back to this. The trick of replacing tabs in a collection works, although it SelectedItem
is a problem. It turns out there is another solution (below) that doesn't create this problem, and also avoids the complexity and goofiness of creating a "mediated" collection that should reflect changes to the original collection.
source to share
UPDATE: Pretty sure the real problem here was the usual TabControl
bu ^ H ^ Hfeature virtualization.
The solution is to log in when each tab is loaded and explicitly instantiate the template.
XAML
<TabControl
Grid.Row="1"
ItemsSource="{Binding Tabs}"
>
<TabControl.Resources>
<Style TargetType="TabItem">
<EventSetter Event="Loaded" Handler="TabItem_Loaded" />
</Style>
</TabControl.Resources>
</TabControl>
FROM#
private void TabItem_Loaded(object sender, RoutedEventArgs e)
{
var tabItem = (sender as TabItem);
if (null != tabItem.DataContext)
{
// Hey, what about TabControl.ItemTemplate, eh?
var dataTemplateKey = new System.Windows.DataTemplateKey(tabItem.DataContext.GetType());
var dataTemplate = (DataTemplate)tabItem.FindResource(dataTemplateKey);
tabItem.Content = dataTemplate.LoadContent();
(tabItem.Content as FrameworkElement).DataContext = tabItem.DataContext;
}
}
... but with better error handling, of course.
You cannot handle DataContextChanged
through EventSetter
because it is not a routed event. You will get this:
System.Windows.FrameworkElement.DataContextChanged = "TabItem_DataContextChanged" is invalid. "DataContextChanged" must be a RoutedEvent registered with a name that ends with the keyword "Event".
But Loaded
does the job well enough in this case.
source to share
I had problems with bindings ElementName
in DataTemplate
using the accepted approach with dataTemplate.LoadContent
.
Based on the decision of @EdPlunkett, I tried to use an intermediate ContentPresenter
for TabItem
, and it works well for my use case.
- Multiple tabs with the same template and the same datacontext datatype, but different actual data contained
- GridSplitter inside DataTemplate
- ElementName Binding inside DataTemplate
- I don't care if virtualization works
TabControl
for my number of tabs (and I haven't checked what's going on with virtualization in my case)
Xaml:
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<EventSetter Event="Loaded" Handler="TabItem_Loaded"/>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.Resources>
<DataTemplate x:Key="MyTemplateKey">
...
</DataTemplate>
</TabControl.Resources>
The code behind (I changed DataTemplateKey
to a string key just for my use case with a named template resource. The approach DataTemplateKey
for selecting a template should work as well)
private void TabItem_Loaded(object sender, RoutedEventArgs e)
{
var tabItem = (sender as TabItem);
if (null != tabItem.DataContext)
{
var dataTemplate = (DataTemplate)tabItem.FindResource("MyTemplateKey");
var cp = new ContentPresenter();
cp.ContentTemplate = dataTemplate;
cp.Content = tabItem.DataContext;
tabItem.Content = cp;
}
}
source to share