我有一个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;
}
}
}
}
答案 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对于释放非托管内存非常重要。