当前位置: 首页 > article >正文

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

http://www.kler.cn/a/470800.html

相关文章:

  • STM32-笔记35-DMA(直接存储器访问)
  • 前端使用Get传递数组形式的数据
  • 获取IP地区
  • thinkphp通过html生成pdf
  • 【C++数据结构——查找】二叉排序树(头歌实践教学平台习题)【合集】
  • 25上软考中级【嵌入式系统设计师】易混淆知识点
  • Flutter鸿蒙化 在鸿蒙应用中添加Flutter页面
  • Spring Security(maven项目) 3.0.2.4版本
  • 期末速成C++【大题汇总完】
  • 【工具推荐】XSS 扫描器-XSStrike
  • 基于fMRI数据计算脑脊液(CSF)与全脑BOLD信号的时间耦合分析
  • 进行电商系统的开发
  • 使用 Nginx 轻松处理跨域请求(CORS)
  • 在vue3中根据需要展示特定国家的国旗
  • Postman + Jenkins + Report 集成测试
  • 在 ASP.NET CORE 中上传、下载文件
  • train_args = TrainingArguments()里面的全部参数使用
  • 中电金信携手华为发布“全链路实时营销解决方案”,重塑金融营销数智新生态
  • 设计模式-结构型-适配器模式
  • flutter 专题二十四 Flutter性能优化在携程酒店的实践
  • 计算机毕业设计Python+Vue.js游戏推荐系统 Steam游戏推荐系统 Django Flask 游 戏可视化 游戏数据分析 游戏大数据 爬虫
  • AI巡检系统在安全生产管理中的创新应用
  • 游戏引擎学习第74天
  • Redis 数据库源码分析
  • Opencv实现Sobel算子、Scharr算子、Laplacian算子、Canny检测图像边缘
  • stm32 移植RTL8201F(正点原子例程为例)