停止TabControl重新创建其子项

我有一个视图模型的IList ,它绑定到TabControl 。 此IList不会在TabControl的生命周期内更改。

        

每个viewmodel都有一个DataTemplate ,它在ResourceDictionary指定。

    

DataTemplate中指定的每个视图都是资源密集型的,足以创建我只想创建每个视图一次,但是当我切换选项卡时,会调用相关视图的构造函数。 根据我的阅读,这是TabControl的预期行为,但我不清楚调用构造函数的机制是什么。

我已经看过一个使用UserControl的类似问题,但是那里提供的解决方案需要我绑定到不受欢迎的视图。

默认情况下, TabControl共享一个面板来呈现它的内容。 要做你想要的(以及许多其他WPF开发人员),你需要像这样扩展TabControl

TabControlEx.cs

 [TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] public class TabControlEx : TabControl { private Panel ItemsHolderPanel = null; public TabControlEx() : base() { // This is necessary so that we get the initial databound selected item ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; } ///  /// If containers are done, generate the selected item ///  ///  ///  private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) { if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; UpdateSelectedItem(); } } ///  /// Get the ItemsHolder and generate any children ///  public override void OnApplyTemplate() { base.OnApplyTemplate(); ItemsHolderPanel = GetTemplateChild("PART_ItemsHolder") as Panel; UpdateSelectedItem(); } ///  /// When the items change we remove any generated panel children and add any new ones as necessary ///  ///  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) { ContentPresenter 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 TabItem item = GetSelectedTabItem(); if (item != null) CreateChildContentPresenter(item); // show the right child foreach (ContentPresenter child in ItemsHolderPanel.Children) child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed; } private ContentPresenter CreateChildContentPresenter(object item) { if (item == null) return null; ContentPresenter cp = FindChildContentPresenter(item); if (cp != null) return cp; // the actual child to be added. cp.Tag is a reference to the TabItem cp = new ContentPresenter(); cp.Content = (item is TabItem) ? (item as TabItem).Content : item; cp.ContentTemplate = this.SelectedContentTemplate; cp.ContentTemplateSelector = this.SelectedContentTemplateSelector; cp.ContentStringFormat = this.SelectedContentStringFormat; cp.Visibility = Visibility.Collapsed; cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item)); ItemsHolderPanel.Children.Add(cp); return cp; } private ContentPresenter FindChildContentPresenter(object data) { if (data is TabItem) data = (data as 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 TabItem GetSelectedTabItem() { object selectedItem = base.SelectedItem; if (selectedItem == null) return null; TabItem item = selectedItem as TabItem; if (item == null) item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem; return item; } } 

XAML

  

注意:我没有提出这个解决方案。 它已经在编程论坛上分享了好几年,并且相信它现在是WPF食谱书籍之一。 我认为最古老或最原始的来源是PluralSight .NET博客文章和StackOverflow上的这个答案 。

HTH,

Dennis的答案非常棒,对我来说非常好。 但是,他的post中提到的原始文章现在已经丢失,因此他的答案需要更多信息才能开箱即用。

这个答案是从MVVM的角度给出的,并在VS 2013下进行了测试。

首先,有点背景。 Dennis的第一个答案的工作方式是,每当用户切换标签时,它隐藏并显示标签内容,而不是销毁和重新创建所述标签内容。

这具有以下优点:

  • 切换选项卡时,编辑框的内容不会消失。
  • 如果在选项卡中使用树视图,则不会在选项卡更改之间折叠。
  • 任何网格的当前选择都保留在制表符开关之间。
  • 这段代码更适合MVVM编程风格。
  • 我们不必编写代码来在选项卡更改之间的选项卡上保存和加载设置。
  • 如果您使用的是第三方控件(如Telerik或DevExpress),则会在制表符开关之间保留网格布局等设置。
  • 卓越的性能改进 – 选项卡切换几乎是即时的,因为我们不会在每次选项卡更改时重绘所有内容。

TabControlEx.cs

 // Copy C# code from @Dennis's answer, and add the following property after the // opening " 

这与DataContext指向的类相同。

XAML

 // Copy XAML from @Dennis's answer. 

这是一种风格。 它进入XAML文件的标题。 此样式永远不会更改,并且由所有选项卡控件引用。

原始标签

您的原始标签可能看起来像这样。 如果切换选项卡,您会注意到编辑框的内容将消失,因为选项卡的内容将被删除并再次重新创建。

   Hello   Hello 2  

自定义标签

更改选项卡以使用我们的新自定义C#类,并使用Style标记将其指向我们的新自定义样式:

   Hello   Hello 2  

现在,当您切换标签时,您会发现编辑框的内容被保留,这certificate一切都运行良好。

更新

该解决方案非常有效。 但是,有一种更加模块化和MVVM友好的方式来实现这一点,它使用附加行为来实现相同的结果。 请参阅代码项目:WPF TabControl:关闭选项卡虚拟化 。 我已将此添加为附加答案。

更新

如果您恰好使用DevExpress ,则可以使用CacheAllTabs选项获得相同的效果(这会关闭选项卡虚拟化):

   Hello   Hello 2   

为了记录,我与DevExpress无关,我确信Telerik具有相同的function。

@Dennis现有的解决方案(@Gravitas的附加说明)非常有效。

但是,还有另一个更加模块化和MVVM友好的解决方案,因为它使用附加行为来实现相同的结果。

请参阅代码项目:WPF TabControl:关闭选项卡虚拟化 。 由于作者是路透社的技术主管,代码可能很扎实。

演示代码实际上很好地组合在一起,它显示了一个常规的TabControl,以及带有附加行为的那个。

在此处输入图像描述

请在SO中查看我的回答。 希望它能解决问题,但它有点偏离MVVM的道路。 链接