停止ContentTemplate Selector在TabControl选择更改事件上调用

时间:2013-12-06 08:43:15

标签: c# wpf mvvm tabcontrol

我有一个TabControl。使用内容模板选择器呈现每个选项卡项,即新添加的选项卡。但每次我在选项卡之间切换时,都会调用内容模板选择器。

我想阻止这一点。这是因为,在Tabcontrol中,用户已经提供了更改布局的操作,因为在选项卡项目内容模板的选择更改事件被调用时,我很难保留用户在Tab期间更改的内容项目选择更改事件。

以下是我用于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>

更新:我已经创建了一个示例项目来解释我的问题。无法在任何地方共享项目,因此请更新主要问题中的代码段。对不起。

在示例项目中,用户可以动态地将TabItem添加到TabControl。 Eash Tabcontent可以显示两个Grid面板,它们由Grid Splitter分隔。显示第二个网格是基于某个标志(在此示例中为ShowSecondPanel)。如果用户单击“显示/隐藏第二个面板”按钮,则将显示当前所选选项卡的第二个面板内容。

问题是,用户可以使用Grid Splitter重新调整面板的大小,但是当用户导航到其他某个标签并返回到前一个标签时,网格分割器位置会更改为原始位置。

希望我能清楚地描述这个问题。

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>

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

}

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

}

TabContent数据对象的代码

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

}

LoadTabCommand类的代码

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

}

2 个答案:

答案 0 :(得分:1)

我不相信你可以避免这种行为,除非你使用TabControl以外的其他控件。

TabControl是一个ItemControl派生的组件:如果它的内容是通过DataTemplate创建的(就像你做的那样),那么每次当前选择时都会生成实际的内容。

另一种选择是使用直接内容填充TabControl,并且应该在选择中保留内部控件。

看看这里: http://msdn.microsoft.com/en-us/library/system.windows.controls.tabcontrol(v=vs.110).aspx

更新:首先,我不确定你理解你想要什么,但这是一个基于我的意思的解决方案。

根本技巧是直接托管标签内容,而不是通过数据模板创建。那就是:

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

上面的代码段应该“保留”翻转标签上的复选框值。

但是你需要(或希望)某种模板或动态的方式来创建添加到tabcontrol的特定数据的正确内容。

以下技巧可以解决您的问题:

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

这是使用一组简单的模板如下:

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

后面的代码更加微不足道:

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

如果您尝试这个小例子,输入文本框的文本将在翻转的标签页中保留。也就是说,模板只会被调用一次。

让我知道。

答案 1 :(得分:1)

我看了你的例子。感谢您发布代码。现在我完全理解你的问题。在我建议将DynamicResource更改为StaticResource之前,我认为您只想查找一次DataTemplate。

现在我看到你希望保持DataTemplate的实例处于活动状态,因此TabControl在更改标签时不会销毁它。

以下是解决方案:

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

如果你只是复制过去,你就可以了。

顺便说一句,就像你的旁注一样,在wpf中销毁和构建DataTemplates对于释放非托管内存非常重要。