Arun
Arun

Reputation: 375

Stop ContentTemplate Selector invoke on TabControl selection change event

I have a TabControl. Each tab Item i.e newly added Tab is rendered using content template selector. But each time i switch between the tabs, content template selector is getting called.

I wanted to stop this. This is because, In the in Tabcontrol, User have provided actions to change the layout, since in the selection change event of the tab Item content template is getting called, it is getting difficult for me to retain the layout what user has changed during Tab Item Selection change Event.

Following is code i am using for the TabControl

       <TabControl Grid.Column="2" x:Name="MainTabControl" HorizontalAlignment="Stretch" Margin="0,12,0,7"
                        SelectedItem="{Binding SelectedTabItem}"  
                        Visibility="{Binding TabsVisible}"
                        ItemsSource="{Binding ToolsList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                        ItemTemplate="{StaticResource ToolDisplayDataTemplate}"
                        commands:FrameworkUICommandList.TabItemChangedCommand="{Binding Path=TabItemChangedCommand}"
        >
        <TabControl.ContentTemplate>
            <DataTemplate>
                <ContentControl Content="{Binding}" ContentTemplateSelector="{DynamicResource toolTabDataItemTemplateSelector}"/>
            </DataTemplate>
        </TabControl.ContentTemplate>
        <TabControl.ItemContainerStyle>
            <Style TargetType="TabItem" BasedOn="{StaticResource TabItemStyle}">
                <Setter Property="AutomationProperties.AutomationId" Value="{Binding ToolID}" />
                <Setter Property="ToolTip" Value="{Binding ToolID,Converter={StaticResource ResourceKey=tabItemTooltipConverter}}"/>
            </Style>
        </TabControl.ItemContainerStyle>
    </TabControl>

UPDATE : I have created a sample project to explain my problem. Could not share the project anywhere, so updating the code snippet in main question. sorry for that.

In the sample project, user can add TabItem dynamically to TabControl. Eash Tabcontent can show two Grid panel and they are separated by Grid Splitter. Display of second grid is Based on some flag (in this example ShowSecondPanel). If user click on "Show / Hide Second Panel" button, then second panel content will be shown for the current selected tab.

The Problem is, user can re-size the panels using Grid Splitter, but when user navigates to some other tab and comes back to previous one, the grid splitter position changes to original position.

Hope I am clear on describing the problem.

Code for MainWindow.xaml

     <Window x:Class="TabControlTestApp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TabControlTestApp"
    Title="MainWindow" Height="500" Width="700">
<Window.DataContext>
    <local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
    <local:TabContentTemplateSelector x:Key="tabContentTemplateSelector" />
    <DataTemplate x:Key="TabitemDataTemplate">
        <StackPanel Width="50" Height="50">
            <TextBlock Text="{Binding TabFirstPanel.Name}"></TextBlock>
        </StackPanel>
    </DataTemplate>
    <DataTemplate x:Key="TabContentWithFirstPanel">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Row="0" Grid.Column="0">Name :</TextBlock>
            <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabFirstPanel.Name}"></TextBlock>
        </Grid>
    </DataTemplate>
    <DataTemplate x:Key="TabContentWithBothPanel">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="200"></ColumnDefinition>
                <ColumnDefinition Width="5"></ColumnDefinition>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Grid Grid.Row="0" Grid.Column="0">
                <Grid.RowDefinitions>
                    <RowDefinition></RowDefinition>
                    <RowDefinition></RowDefinition>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Row="0" Grid.Column="0">Name :</TextBlock>
                <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabFirstPanel.Name}"></TextBlock>
            </Grid>
            <GridSplitter Grid.Row="0" Grid.Column="1"
                          VerticalAlignment="Stretch"
                          Width="5"
                          Height="Auto"
                           ResizeDirection="Columns"
                    ResizeBehavior="PreviousAndNext"
                          ></GridSplitter>
            <Grid Grid.Row="0" Grid.Column="2">
                <Grid.RowDefinitions>
                    <RowDefinition></RowDefinition>
                    <RowDefinition></RowDefinition>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Row="0" Grid.Column="0">Additional Detail :</TextBlock>
                <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabSecondPanel.AdditionalDetails}"></TextBlock>
            </Grid>
        </Grid>
    </DataTemplate>
</Window.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="50"></RowDefinition>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="150"></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <StackPanel Grid.Row="1" Grid.Column="0">
    <Button Content="Load Tab : 1" Name="LoadTab1"  Width="100" Height="50" Command="{Binding LoadTabCommand}" CommandParameter="{Binding ElementName=LoadTab1}"></Button>
        <Button Content="Load Tab : 2" Name="LoadTab2"  Width="100" Height="50" Command="{Binding LoadTabCommand}" CommandParameter="{Binding ElementName=LoadTab2}"></Button>
        <Button Content="Load Tab : 3" Name="LoadTab3"  Width="100" Height="50" Command="{Binding LoadTabCommand}" CommandParameter="{Binding ElementName=LoadTab3}"></Button>
    </StackPanel>
    <Button Content="Close All tab" Width="100" Height="40" x:Name="CloseAllTab"></Button>
    <Button Content="Show / Hide Second Panel" x:Name="ShowHideSecondPanelInTab" 
            Grid.Row="0" Grid.Column="1" Width="150"
            Command="{Binding LoadTabCommand}" CommandParameter="{Binding ElementName=ShowHideSecondPanelInTab}"></Button>
    <TabControl Grid.Row="1" Grid.Column="1"
          ItemsSource="{Binding TabContentList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"      
           SelectedItem="{Binding SelectedTabItem}"
                ItemTemplate="{StaticResource ResourceKey=TabitemDataTemplate}"
                ContentTemplateSelector="{StaticResource ResourceKey=tabContentTemplateSelector}"
                >

    </TabControl>
</Grid>
   </Window>

Code For MainWindowViewModel

     using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Input;
    using System.ComponentModel;
    using System.Collections.ObjectModel;

     namespace TabControlTestApp
   {
class MainWindowViewModel : INotifyPropertyChanged
{
    public MainWindowViewModel()
    {
        this.loadTabCommand = new LoadTabCommand(this);
        tabContentList = new ObservableCollection<TabContent>();
    }

    public ICommand LoadTabCommand
    {
        get
        {
            return this.loadTabCommand;
        }
        set
        {
            this.loadTabCommand = value;
        }
    }

    public ObservableCollection<TabContent> TabContentList
    {
        get
        {
            return tabContentList;
        }
        set
        {
            tabContentList = value;
            OnPropertyChanged("TabContentList");
        }
    }

    public TabContent SelectedTabItem
    {
        get
        {
            return selectedTabItem;
        }
        set
        {
            selectedTabItem = value;
            OnPropertyChanged("SelectedTabItem");
        }

    }

    public void LoadFirstTab()
    {
        TabFirstPanel firstPanel = new TabFirstPanel() { Name = "John-1", Age = "31" };
        TabSecondPanel secondPanel = new TabSecondPanel() { AdditionalDetails="Some Details for First Tab" };
        TabContent firstTabContent = new TabContent() { TabFirstPanel = firstPanel, TabSecondPanel = secondPanel, ShowSecondPanel = false };
        tabContentList.Add(firstTabContent);
        SelectedTabItem = firstTabContent;
    }
    public void LoadSecondTab()
    {
        TabFirstPanel firstPanel = new TabFirstPanel() { Name = "John-2", Age = "31" };
        TabSecondPanel secondPanel = new TabSecondPanel() { AdditionalDetails = "Some Details for second Tab" };
       TabContent secondTabContent= new TabContent() { TabFirstPanel = firstPanel, TabSecondPanel = secondPanel, ShowSecondPanel=false };
       tabContentList.Add(secondTabContent);
       SelectedTabItem = secondTabContent;
    }
    public void LoadThirdTab()
    {
        TabFirstPanel firstPanel = new TabFirstPanel() { Name = "John-3", Age = "31" };
        TabSecondPanel secondPanel = new TabSecondPanel() { AdditionalDetails = "Some Details for Third Tab" };
        TabContent ThirdTabContent = new TabContent() { TabFirstPanel = firstPanel, TabSecondPanel = secondPanel, ShowSecondPanel = false };
        tabContentList.Add(ThirdTabContent);
        SelectedTabItem = ThirdTabContent;
    }

    public void ShowHideSecondPanelInTab()
    {
        TabContent currentTabContent = SelectedTabItem;   
        int currentIndex = tabContentList.IndexOf(SelectedTabItem);

        if (currentTabContent.ShowSecondPanel)
        {
            currentTabContent.ShowSecondPanel = false;
        }
        else
        {
            currentTabContent.ShowSecondPanel = true;
        }
        TabContentList.RemoveAt(currentIndex);
        TabContentList.Insert(currentIndex, currentTabContent);
        OnPropertyChanged("TabContentList");
        SelectedTabItem = currentTabContent;
    }

    private TabContent selectedTabItem = null;

    private ObservableCollection<TabContent> tabContentList = null;

    private ICommand loadTabCommand = null;


    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

}

Code for TabContentTemplateSelector.cs

       using System;
       using System.Collections.Generic;
       using System.Linq;
       using System.Text;
       using System.Windows.Controls;
       using System.Windows;

      namespace TabControlTestApp
   {
class TabContentTemplateSelector : DataTemplateSelector
{
    public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
    {
        FrameworkElement element = container as FrameworkElement;
        if (element != null && item != null)
        {
            TabContent tabContent = (TabContent)item;
            if (tabContent.ShowSecondPanel)
            {
                return element.FindResource("TabContentWithBothPanel") as DataTemplate;
            }
            else
            {
                return element.FindResource("TabContentWithFirstPanel") as DataTemplate;
            }
        }
        else
        {
            return base.SelectTemplate(item, container);
        }
    }
}

}

Code for TabContent Data Object

    using System;
  using System.Collections.Generic;
  using System.Linq;
   using System.Text;

  namespace TabControlTestApp
  {
class TabFirstPanel
{
    public string Name { get; set; }

    public string Age { get; set; }
}

class TabSecondPanel
{
    public string AdditionalDetails { get; set; }
}

class TabContent
{
    public TabFirstPanel TabFirstPanel { get; set; }

    public TabSecondPanel TabSecondPanel { get; set; }

    public bool ShowSecondPanel { get; set; }
}

}

Code for LoadTabCommand Class

          using System;
        using System.Collections.Generic;
       using System.Linq;
         using System.Text;
      using System.Windows.Input;

     namespace TabControlTestApp
       {
class LoadTabCommand : ICommand
{
    MainWindowViewModel mainWindowViewModel = null;

    public LoadTabCommand(MainWindowViewModel mainWindowViewModel)
    {
        this.mainWindowViewModel = mainWindowViewModel;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        System.Windows.Controls.Button btn = (System.Windows.Controls.Button)parameter;
        switch (btn.Name)
        {
            case "LoadTab1":
                mainWindowViewModel.LoadFirstTab();
            break;
            case "LoadTab2":
            mainWindowViewModel.LoadSecondTab();
            break;
            case "LoadTab3":
            mainWindowViewModel.LoadThirdTab();
            break;
            case "ShowHideSecondPanelInTab":
            mainWindowViewModel.ShowHideSecondPanelInTab();
            break;
        }
    }
}

}

Upvotes: 1

Views: 1186

Answers (2)

dev hedgehog
dev hedgehog

Reputation: 8801

I took a look at your example. Thanks for posting code. Now I understand your issue completely. Before I suggested to change DynamicResource to StaticResource I thought you wanted to only look up once for your DataTemplate.

Now I see you wish to keep the instance of DataTemplate alive so TabControl doesn't destroy it when changing tabs.

Here is the solution:

<Window.DataContext>
    <local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>

    <local:TabContentTemplateSelector x:Key="tabContentTemplateSelector" />

    <DataTemplate x:Key="TabitemDataTemplate">
        <StackPanel Width="50" Height="50">
            <TextBlock Text="{Binding TabFirstPanel.Name}"></TextBlock>
        </StackPanel>
    </DataTemplate>

    <Grid x:Key="template1">
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0">Name :</TextBlock>
        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabFirstPanel.Name}"></TextBlock>
    </Grid>

    <Grid x:Key="template2">
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"></ColumnDefinition>
            <ColumnDefinition Width="5"></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid Grid.Row="0" Grid.Column="0">
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Row="0" Grid.Column="0">Name :</TextBlock>
            <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabFirstPanel.Name}"></TextBlock>
        </Grid>
        <GridSplitter Grid.Row="0" Grid.Column="1"
                      VerticalAlignment="Stretch"
                      Width="5"
                      Height="Auto"
                      ResizeDirection="Columns"
                      ResizeBehavior="PreviousAndNext"/>
        <Grid Grid.Row="0" Grid.Column="2">
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Row="0" Grid.Column="0">Additional Detail :</TextBlock>
            <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TabSecondPanel.AdditionalDetails}"></TextBlock>
        </Grid>
    </Grid>

    <DataTemplate x:Key="TabContentWithFirstPanel">
        <ContentPresenter Content="{StaticResource template1}"/>
    </DataTemplate>
    <DataTemplate x:Key="TabContentWithBothPanel">
        <ContentPresenter Content="{StaticResource template2}"/>
    </DataTemplate>

If you just copy past that you will do fine.

By the way, just as sidenote for you, destroying and building up DataTemplates is very important in wpf for releasing unmanaged memory.

Upvotes: 1

Mario Vernari
Mario Vernari

Reputation: 7306

I don't believe you may avoid this behavior, unless you use another control other than the TabControl.

The TabControl is a ItemControl-derived component: if its content is created via DataTemplate (as you done), the actual content is generated every time upon the current selection.

Another option is to fill the TabControl with direct content, and the inner controls should be preserved across the selection.

Have a look here: http://msdn.microsoft.com/en-us/library/system.windows.controls.tabcontrol(v=vs.110).aspx

UPDATE: First off, I am not sure to have understand what you want, but here is a solution based on what I mean.

The fundamental trick is hosting directly the tab-contents, instead of creating via data-templating. That is:

    <TabControl>
        <TabItem Header="Tab1">
            <TextBlock 
                Text="Page 1" 
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                />
        </TabItem>
        <TabItem Header="Tab2">
            <CheckBox 
                Content="check me" 
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                />
        </TabItem>
        <TabItem Header="Tab3">
            <TextBlock 
                Text="Page 3" 
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                />
        </TabItem>
    </TabControl>

The above snippet should "persist" the checkbox value across the tabs flipping.

But you need (or desire) some kind of templating, or a dynamic way to create the right content upon a certain data added to the tabcontrol.

The following trick should solve your problem:

    <TabControl>
        <TabItem 
            Header="Tab1"
            >
            <ContentControl
                Content="{Binding Path=A}"
                ContentTemplateSelector="{StaticResource sel}"
                />
        </TabItem>

        <TabItem 
            Header="Tab2"
            >
            <ContentControl
                Content="{Binding Path=B}"
                ContentTemplateSelector="{StaticResource sel}"
                />
        </TabItem>

        <TabItem 
            Header="Tab3"
            >
            <ContentControl
                Content="{Binding Path=C}"
                ContentTemplateSelector="{StaticResource sel}"
                />
        </TabItem>
    </TabControl>

This is using a trivial set of templates as follows:

<Window.Resources>

    <local:MySelector x:Key="sel" />

    <DataTemplate x:Key="dtplA">
        <StackPanel Margin="30,30">
            <TextBlock Text="A: " />
            <TextBox />
        </StackPanel>
    </DataTemplate>

    <DataTemplate x:Key="dtplB">
        <StackPanel Margin="30,30">
            <TextBlock Text="B: " />
            <TextBox />
        </StackPanel>
    </DataTemplate>

    <DataTemplate x:Key="dtplC">
        <StackPanel Margin="30,30">
            <TextBlock Text="C: " />
            <TextBox />
        </StackPanel>
    </DataTemplate>

</Window.Resources>

And the behind-code is even more trivial:

public class VM
{
    public A A { get; set; }
    public B B { get; set; }
    public C C { get; set; }
}

public class A { }
public class B { }
public class C { }

public class MySelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item != null)
        {
            return (DataTemplate)((FrameworkElement)container).FindResource("dtpl" + item.GetType().Name);
        }
        else
        {
            return base.SelectTemplate(item, container);
        }
    }
}

If you try this small example, the text typed into the textboxes will persist across the tabs flipping. That is, the templates will be called once only.

Let me know.

Upvotes: 1

Related Questions