停止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 "