在 WPF TabControl 中切换选项卡时保持 Visual Tree (优化版)






4.83/5 (9投票s)
这是“在 WPF TabControl 中切换标签页时持久化视觉树”的一个替代方案。
引言
很久以前,我偶然参与了一个项目,该项目使用 TabControl
在视图之间切换。一切都很顺利,除了在视图之间切换花费的时间太长。每次切换视图时,都会重新创建整个视觉树。快速搜索后,我找到了 Jason Ching 写的这篇精彩文章:在 WPF TabControl 中切换标签页时持久化视觉树。它正好满足了我的需求。我开始使用他的代码,性能得到了显著提升,但仍有改进的空间。本文将介绍如何在现有优秀代码的基础上进行改进。
背景
用简单的几句话来说,Jason 实现的思路是这样的:创建一个行为(behavior),将其附加到 TabControl
上,使其能够等待新的源项被创建(或者整个新的 ItemsSource 容器被附加)。一旦创建了新项,该行为就会创建一个 TabItem
实例,并将其包装在源项中。这个简单的技巧可以防止 TabControl
丢弃 TabItem
的实例,从而持久化 Tab 的视觉树。
实现
该行为由三个类实现:PersistTabBehavior
、PersistTabItemsSourceHandler
和 PersistTabSelectedItemHandler
。
PersistTabBehavior
– 处理附加属性,并维护两个静态字典,其中 Tab 控件的实例与 ItemsSource 和 SelectedItem 处理程序相关联。PersistTabSelectedItemHandler
– 将内部TabControl
选择转换为外部选择。PersistTabItemsSourceHandler
– 处理与数据源项关联的TabItem
的创建和操作。
有关更多详细信息,请参阅 原始文章。
改进
我首先想到可以提高性能的方法是摆脱字典及其关联的查找过程。取而代之的是,该行为的实例应该同时持有对 TabControl
和 itemsource 的引用。
我个人认为,为一个简单的控件行为使用三个不同的类有点小題大作。如果所有引用都保存在一个地方,那么访问一个对象实例比访问多个对象实例要容易得多,所以我将所有这些类合并成了一个。
引用外部项源容器很简单;我们可以简单地将其存储在一个字段中。
挑战在于如何让 TabControl
引用正确的处理程序实例,该处理程序响应内部 TabControl.SelectedItem
属性的变化。TabControl.SelectedItem
是 TabControl
类的一个 附加属性。因此,我们可以通过使用 WPF Binding 来解决引用问题,毕竟 Binding 就是为此目的而设计的。Binding
包含对目标属性实例和源实例的引用。这使得我们只需检查 Binding
对象即可检索到正确的实例。这有效地消除了对字典的需求。
现在,任何时候将 TabItemGeneratorBehavior.SelectedItemProperty
或 TabItemGeneratorBehavior.ItemsSourceProperty
附加到控件时,我们都会创建 TabItemGeneratorBehavior
的实例,并使用相应对象的引用初始化字段 _innerSelection
、_tabControl
、_itemsSource
,附加到所需事件等。
同时,我们在 TabControl.SelectedItem
和行为的公共属性 TabItemGeneratorBehavior.SelectedTabItem
之间创建绑定。
_tabControl = tab;
_tabControl.Loaded += OnTabLoaded;
_tabControl.SetBinding(TabControl.SelectedItemProperty,
new Binding("SelectedTabItem") { Source = this });
在此之后,该行为的操作与原始行为完全相同。
Using the Code
我提供了一个示例项目来演示该行为的功能。
屏幕被分割成两个 TabControls
来填充区域。左侧的控件未使用该行为,而右侧的控件使用了该行为。选项卡视图区域显示了与活动选项卡关联的 TabItem
实例的唯一 ID。
正如你在左侧看到的,TabControl
重用了 TabItem
类的同一个实例来显示所有项。因此,每次显示新选项卡时,该选项卡上的所有视觉项都会被丢弃,并为选定的项创建新的视觉元素。
在右侧,每个项都有自己的 TabItem
实例。当控件在选项卡之间切换时,这些 TabItem
s 会被保留,视觉树不会被重新创建。
使用原始示例
如果你想尝试原始文章示例项目中的此实现,你需要按照以下步骤操作:
- 将 TabItemGeneratorBehavior.cs 文件添加到项目中
- 修改 MainWindow.xaml 文件以包含对新行为的引用
- 在
<Window…>
标签中添加xmlns:beh="clr-namespace:System.Windows.Controls"
。 - 将
<TabControl...
标签上的b:PersistTabBehavior
替换为beh:TabItemGeneratorBehavior
。
历史
- 04/05/2012 - 首次发布。