今天我决定终于尝试一下虚拟化TreeView。为此,需要进行绑定。所以我决定进行两件事测试 - 基于类型+虚拟化的 HierarchicalDataTemplate。
我为一些数据创建了一个基类。从基类创建了 2 个派生类。创建 2 个 HierarchicalDataTemplate(每个派生类 1 个)以获得不同格式的节点。并运行 2 种类型的 10k 个节点。
课程:
public class ListItem_Generic
{
public string Name { get; protected set; }
public ListItem_Generic(string Name = "") { this.Name = Name; }
}
public class ListItem_Single : ListItem_Generic
{
public ListItem_Single(string Name = "") : base(Name) { }
}
public class ListItem_Multi : ListItem_Generic
{
public List<ListItem_Generic> Items { get; protected set; }
public ListItem_Multi(string Name = "", List<ListItem_Generic> Items = null)
: base(Name)
{
if (Items == null)
this.Items = new List<ListItem_Generic>();
else
this.Items = new List<ListItem_Generic>(Items);
}
}
生成 10k 个第一级节点和一些子节点,绑定:
public MainWindow()
{
InitializeComponent();
// Create a list of sample items and populate them
var lst = new List<ListItem_Generic>();
int MaxHeaders = 10000;
var rnd = new Random();
// Now generate 10 000 records. First select random amount of headers
int HeadersCount = rnd.Next(MaxHeaders);
for (int i = 0; i < HeadersCount; i++)
{
var Childrencount = rnd.Next(100);
var children = new List<ListItem_Generic>();
for (int j = 0; j < Childrencount; j++)
children.Add(new ListItem_Single("Child #"+j+" of parent #"+i));
lst.Add(new ListItem_Multi("Header #" + i + " (" + Childrencount + ")", children));
}
for (int i = 0; i < MaxHeaders - HeadersCount; i++)
lst.Add(new ListItem_Single("Line #" + i));
// Bind lstView to lst
lstView.ItemsSource = lst;
lstView.UpdateLayout();
}
带有分层数据模板的 XAML:
<Window x:Class="Test_DataTemplates.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:Test_DataTemplates"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView Name="lstView" VirtualizingPanel.IsVirtualizing="True">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type loc:ListItem_Multi}" ItemsSource="{Binding Path=Items}">
<Border Background="RosyBrown">
<TextBlock Text="{Binding Path=Name}" Foreground="White" FontWeight="Bold"/>
</Border>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:ListItem_Single}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
一切正常:
但是,当滚动到标题 #1000 并展开它时 - 滚动位置将跳转到其他位置,使得展开的节点及其子节点不可见。
我做错了什么?有什么办法可以解决这个问题吗?
更新: 删除虚拟化也会删除滚动错误。
在 C# WPF 中的树虚拟化出现许多问题(包括主要问题,即只有第一级被虚拟化)之后,我无法找到正确的修复方法。微软接受了一份错误报告并回答说,滚动问题将在未来的版本之一中得到解决。
至于我个人的最终解决方案 - 我已经切换到自己的 ListTreeView 实现,即使用 List 和模拟树。这解决了虚拟化和滚动行为的所有问题。唯一的问题是 - 折叠树节点后删除了许多项目。我必须检查重新创建一个新列表而不是逐个删除项目是否更容易/更快。
根据 https://msdn.microsoft.com/en-us/library/system.windows.controls.scrollunit(v=vs.110).aspx(ScrollUnit 枚举),有两个可能的值。默认值似乎是“像素”,这会导致滚动问题。如下所示切换到“项目”即可解决该问题:
VirtualizingStackPanel.ScrollUnit="Item"
如果有人可以在他们的环境中对此进行测试并确认问题,我将不胜感激。 另外,玩了一下发现了另一个奇怪的行为:
接下来运行程序:
重新运行程序(为了实验的纯度)
扩展第一个节点似乎是一种解决方法。
我在MSDN上找到了解决方案。显然这与默认的 TreeView 模板有关。以下修复了滚动条闪烁并阻止随机节点在快速滚动时扩展。
<Style x:Key="{x:Type TreeView}" TargetType="{x:Type TreeView}">
<Setter Property="TreeView.Background" Value="Transparent"/>
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"/>
<Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling"/>
<Setter Property="TreeView.SnapsToDevicePixels" Value="True" />
<Setter Property="TreeView.OverridesDefaultStyle" Value="True" />
<Setter Property="ItemsControl.ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="TreeView.Template">
<Setter.Value>
<ControlTemplate TargetType="TreeView">
<ScrollViewer Focusable="False" CanContentScroll="True" Padding="4">
<ItemsPresenter HorizontalAlignment="Stretch"/>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
我尝试快速滚动,但似乎没有再注意到问题。最棒的是您甚至不必放弃虚拟化。
我在使用虚拟化 TreeView 时遇到了很多问题,主要是:
谷歌搜索刚刚证实人们有很多类似且未解决的问题。谷歌搜索将此案例作为第一个命中,所以,我会在这里说出对我最有帮助的内容(当然,除了设置 VirtualizingStackPanel.ScrollUnit="Item" 之外):
尽管我所有的 TreeViewItems 都具有相同的高度(我已经检查过),但我需要将 Height 显式设置为 TreeViewItem 下顶部元素上的某个值。这样做后,前两个问题减少到最低限度,另外两个问题完全消失。
也许它会对某人有所帮助...
附注我在一条评论中看到了这个建议,但在我在谷歌搜索时遇到的一个线程中,这条评论并没有引起太多关注......