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.

+3


source to share


3 answers


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.

+1


source


Try adding an attribute to your template x:Shared="false"

.



0


source


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;
    }
}

      

0


source







All Articles