WPF 扩展 TabControl 可保存显示的标签页
一 功能描述:
扩展的 TabControl 可保存显示的项目,这样切换选项卡时就不会因卸载和重新加载 VisualTree 而影响性能
[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]
public class TabControlEx : HandyControl.Controls.TabControl
{
private Panel _itemsHolderPanel;
public TabControlEx()
{
// This is necessary so that we get the initial databound selected item
ItemContainerGenerator.StatusChanged += ItemContainerGeneratorStatusChanged;
}
/// <summary>
/// If containers are done, generate the selected item
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void ItemContainerGeneratorStatusChanged(object sender, EventArgs e)
{
if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorStatusChanged;
UpdateSelectedItem();
}
}
/// <summary>
/// Get the ItemsHolder and generate any children
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_itemsHolderPanel = GetTemplateChild("PART_ItemsHolder") as Panel;
UpdateSelectedItem();
}
/// <summary>
/// When the items change we remove any generated panel children and add any new ones as necessary
/// </summary>
/// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (_itemsHolderPanel == null)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Reset:
_itemsHolderPanel.Children.Clear();
break;
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
var cp = FindChildContentPresenter(item);
if (cp != null)
{
_itemsHolderPanel.Children.Remove(cp);
}
}
}
// Don't do anything with new items because we don't want to
// create visuals that aren't being shown
UpdateSelectedItem();
break;
case NotifyCollectionChangedAction.Replace:
throw new NotImplementedException("Replace not implemented yet");
}
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
UpdateSelectedItem();
}
private void UpdateSelectedItem()
{
if (_itemsHolderPanel == null)
{
return;
}
// Generate a ContentPresenter if necessary
var item = GetSelectedTabItem();
if (item != null)
{
CreateChildContentPresenter(item);
}
// show the right child
foreach (ContentPresenter child in _itemsHolderPanel.Children)
{
child.Visibility = ((child.Tag as HandyControl.Controls.TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
}
}
private ContentPresenter CreateChildContentPresenter(object item)
{
if (item == null)
{
return null;
}
var cp = FindChildContentPresenter(item);
if (cp != null)
{
return cp;
}
var tabItem = item as HandyControl.Controls.TabItem;
cp = new ContentPresenter
{
Content = (tabItem != null) ? tabItem.Content : item,
ContentTemplate = this.SelectedContentTemplate,
ContentTemplateSelector = this.SelectedContentTemplateSelector,
ContentStringFormat = this.SelectedContentStringFormat,
Visibility = Visibility.Collapsed,
Tag = tabItem ?? (this.ItemContainerGenerator.ContainerFromItem(item))
};
_itemsHolderPanel.Children.Add(cp);
return cp;
}
private ContentPresenter FindChildContentPresenter(object data)
{
if (data is HandyControl.Controls.TabItem)
{
data = (data as HandyControl.Controls.TabItem).Content;
}
if (data == null)
{
return null;
}
if (_itemsHolderPanel == null)
{
return null;
}
foreach (ContentPresenter cp in _itemsHolderPanel.Children)
{
if (cp.Content == data)
{
return cp;
}
}
return null;
}
protected HandyControl.Controls.TabItem GetSelectedTabItem()
{
var selectedItem = SelectedItem;
if (selectedItem == null)
return null;
return selectedItem as HandyControl.Controls.TabItem ?? ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as HandyControl.Controls.TabItem;
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new TabControlAutomationPeer(this);
}
}
二 使用示例:
注意注释地方和系统样式不一样!
修改内容:系统样式使用ContentPresenter 作为承载容器,切换标签页使会重新加载并且标签页内容会互相影响。这里使用Grid x:Name="PART_ItemsHolder"作为承载容器,内容通过Visibility控制显示!
<control:TabControlEx ItemsSource="{Binding TabItems, Mode=TwoWay}"
SelectedItem="{Binding SelectedTab, Mode=TwoWay}">
<control:TabControlEx.Style>
<Style TargetType="control:TabControlEx">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="control:TabControlEx">
<Border BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding hc:BorderElement.CornerRadius}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{DynamicResource Background2}">
<Grid Name="templateRoot"
ClipToBounds="true"
SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"
x:Name="column1" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TabPanel Name="PART_HeaderPanel"
IsItemsHost="true"
ZIndex="1" />
<Border x:Name="contentPanel"
Background="{DynamicResource WhiteBackground}"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="1">
<Grid x:Name="PART_ItemsHolder"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
<!--<ContentPresenter Name="PART_SelectedContentHost"
ContentSource="SelectedContent"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />-->
</Grid>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</control:TabControlEx.Style>
</control:TabControlEx>
三 原文连接:stackoverflow
四 TabControl 扩展(模板选择器)
本教程基于Prism + HandyControl + .Net Framework 4.8
。
要手动添加标签页,并且每个标签页不同,可使用模板选择器实现,示例:
这里每个标签页有自己的后台处理逻辑,即FrontPageView.xaml的后台是FrontPageViewModel.cs
注意点:由于标签页有自己的处理逻辑,并且是嵌入在TabControl中,所以
不能在标签页中开启Prism的自动查找功能 即xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="False" 或者 不写这两行代码
<UserControl.Resources>
<!-- 首页模板 -->
<DataTemplate x:Key="FrontPageTemplate"
DataType="{x:Type pageview:FrontPageViewModel}">
<pageview:FrontPageView />
</DataTemplate>
<!-- 其他页面模板 -->
<DataTemplate x:Key="WebPageTemplate"
DataType="{x:Type pageview:WebPageViewModel}">
<pageview:WebPageView />
</DataTemplate>
<!-- 模板选择器 -->
<helper:TabContentTemplateSelector x:Key="TabContentTemplateSelector"
FrontPageTemplate="{StaticResource FrontPageTemplate}"
WebPageTemplate="{StaticResource WebPageTemplate}" />
</UserControl.Resources>
<control:TabControlEx ItemsSource="{Binding TabItems, Mode=TwoWay}"
SelectedItem="{Binding SelectedTab, Mode=TwoWay}"
ContentTemplateSelector="{StaticResource TabContentTemplateSelector}">
</control:TabControlEx>
internal class TabContentTemplateSelector : DataTemplateSelector
{
public DataTemplate FrontPageTemplate { get; set; }
public DataTemplate WebPageTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is FrontPageViewModel)
return FrontPageTemplate;
if (item is WebPageViewModel)
return WebPageTemplate;
return base.SelectTemplate(item, container);
}
}
TabControlEx 的后台代码:
private ObservableCollection<object> _allTabItems;
public ObservableCollection<object> TabItems
{
get { return _allTabItems; }
set { SetProperty(ref _allTabItems, value); }
}
private object _selectedTab;
public object SelectedTab
{
get { return _selectedTab; }
set { SetProperty(ref _selectedTab, value); }
}
//添加标签页
private void OnAddTabCommand()
{
WebPageViewModel webPage = new WebPageViewModel();
//TODO:
TabItems.Add(webPage);
}