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

news/2025/1/8 14:33:08 标签: wpf

一 功能描述:

扩展的 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);
}

http://www.niftyadmin.cn/n/5816443.html

相关文章

前端状态管理的智能化革命:告别混乱,拥抱高效

引言 现代前端应用日益复杂&#xff0c;交互日趋精细&#xff0c;随之而来的是前端状态管理的巨大挑战。庞大的数据流、错综复杂的组件交互以及频繁的数据更新&#xff0c;让开发者疲于应付代码的冗余和难以维护的问题&#xff0c;严重影响了开发效率和项目进度。 面对这种现状…

面试题:并发与并行的区别?

并发&#xff08;Concurrency&#xff09;和并行&#xff08;Parallelism&#xff09;是计算机科学中两个相关但不同的概念&#xff0c;它们都涉及到同时处理多个任务&#xff0c;但在实现方式和效果上有显著的区别。理解这两者的区别对于编写高效的多任务程序非常重要。 并发&…

蓝桥杯 第十五届 研究生组 第二题 召唤数学精灵

问题描述&#xff1a; 数学家们发现了两种用于召唤强大的数学精灵的仪式&#xff0c;这两种仪式分别被称为累加法仪式 A(n) 和累乘法仪式 B(n)。累加法仪式 A(n) 是将从 1 到 n 的所有数字进行累加求和&#xff0c;即&#xff1a;A(n)12⋯n累乘法仪式 B(n) 则是将从 1 到 n 的所…

蓝桥杯算法|练习记录

位运算 按位与运算符&#xff08;&&#xff09; 运算规则&#xff1a;两位同时为1&#xff0c;结果才为1&#xff0c;否则结果为0。例如&#xff0c; -3&#xff08;在计算机中表示为1101&#xff09;&5&#xff08;0101&#xff09; 0101&#xff08;即十进制的1&…

十年后LabVIEW编程知识是否会过时?

在考虑LabVIEW编程知识在未来十年内的有效性时&#xff0c;我们可以从几个角度进行分析&#xff1a; ​ 1. 技术发展与软件更新 随着技术的快速发展&#xff0c;许多编程工具和平台不断更新和改进&#xff0c;LabVIEW也不例外。十年后&#xff0c;可能会有新的编程语言或平台…

操作手册:集成钉钉审批实例消息监听配置

此文档将记录在慧集通平台怎么实现钉钉审批实例结束或发起或取消时&#xff0c;能够实时的将对应的实例数据抓取出来送入第三方系统 集成平台配置 1、配置中心库&#xff0c;存储钉钉发送的消息&#xff0c;可以忽略&#xff0c;若不配置&#xff0c;则钉钉的消息将不再记录到…

Keil C51 与 Keil MDK(ARM-stm32?):嵌入式开发的利器

Keil C51 与 Keil MDK&#xff08;ARM&#xff09;&#xff1a;嵌入式开发的利器 引言 在嵌入式系统开发领域&#xff0c;Keil 软件套件是广受开发者欢迎的工具之一。Keil 提供了多种开发环境&#xff0c;其中最著名的两个是 Keil C51 和 Keil MDK&#xff08;Microcontrolle…

0 Token 间间隔 100% GPU 利用率,百度百舸 AIAK 大模型推理引擎极限优化 TPS

1. 什么是大模型推理引擎 大模型推理引擎是生成式语言模型运转的发动机&#xff0c;是接受客户输入 prompt 和生成返回 response 的枢纽&#xff0c;也是拉起异构硬件&#xff0c;将物理电能转换为人类知识的变形金刚。 大模型推理引擎的基本工作模式可以概括为&#xff0c…