WPF 扩展 TabControl 可保存显示的标签页

一 功能描述:

扩展的 TabControl 可保存显示的项目,这样切换选项卡时就不会因卸载和重新加载 VisualTree 而影响性能

csharp 复制代码
 [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控制显示!

csharp 复制代码
 <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" 或者 不写这两行代码

csharp 复制代码
<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>
csharp 复制代码
  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 的后台代码:

csharp 复制代码
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);
}
相关推荐
Macbethad6 小时前
工业设备数据记录程序技术方案
wpf·信息与通信
zzyzxb19 小时前
WPF 中隧道事件和冒泡事件
wpf
闲人编程19 小时前
API限流、鉴权与监控
分布式·python·wpf·限流·集群·令牌·codecapsule
TA远方21 小时前
【WPF】桌面程序使用谷歌浏览器内核CefSharp控件详解
wpf·浏览器·chromium·控件·cefsharp·cefsharp.wpf
Macbethad1 天前
工业设备数据采集主站程序技术方案
wpf
关关长语2 天前
HandyControl 3.5.x 版本 ListViewItem不显示问题
windows·wpf
Macbethad2 天前
工业设备维护程序技术方案
wpf
Macbethad2 天前
工业设备配方管理系统技术方案
wpf
喵叔哟2 天前
7.日志系统深入
wpf
清风徐来Groot2 天前
WPF布局之Grid
wpf