虚拟化TreeView中的滚动问题

9

今天我决定尝试虚拟化TreeView。为此,需要进行绑定。因此,我决定测试两个事情 - 基于类型的HierarchicalDataTemplate + 虚拟化。

我创建了一些数据的基类。从基类创建了2个派生类。制作了2个HierarchicalDataTemplate(每个派生类一个)以获取不同格式的节点,并运行10k个2种类型的节点的填充。

类:

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并展开它时,滚动位置会跳转到其他位置,使展开的节点及其子节点不可见。

我做错了什么?有没有办法解决这个问题?

更新:移除虚拟化也会消除滚动错误。


请参考以下链接:https://dev59.com/G1HTa4cB1Zd3GeqPSpBG - Brian Reichle
我这里也有完全相同的问题。 - Laie
@Jee-heonOh 我认为,TreeView 不太适合虚拟化。我建议使用 ListBox/ListView 替代。可以通过模板轻松模拟树形视图,并通过向普通(可观察)列表添加/删除元素来模拟级别的展开/折叠。有多个 TreeListView 的示例可用。如果您倾向于使用 TreeView(请注意,它只能虚拟化第一级层次结构),请参阅微软网站上的解决方法,由其中一位贡献者发布。他重写了 treeviewitem 的几个事件,以按需将其带入视图,从而解决了滚动问题。 - user2274578
5个回答

1

在使用C# WPF中的Tree虚拟化时遇到了许多问题(包括主要问题,只有第一级会被虚拟化),我无法找到一个合适的修复方法。微软接受了一个错误报告并回答说,滚动问题将在将来的版本中得到修复。

对于我个人而言,最终的解决方案是切换到自己实现的ListTreeView,即使用List和模拟树。这解决了所有虚拟化和滚动行为的问题。唯一的问题是,在折叠树节点后删除多个项目。我不得不实现一个检查,以确定重新创建一个新的列表是否比逐个删除项目更容易/更快速。


0
我在 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>

我尝试了快速滚动,似乎没有注意到任何问题。有趣的是,你甚至不必放弃你的虚拟化。


1
请问您能否添加一个链接,指向您在MSDN上找到的内容?我对默认模板出错的具体位置特别感兴趣。 - Brian Reichle
这似乎对我来说并没有解决问题。 - LuckyLikey

0

尝试关闭(而不是打开)Recycling

VirtualizingStackPanel.VirtualizationMode="Standard"

优化文章是指将其打开

如何:提高TreeView的性能


1
标准模式和回收模式都会在滚动时产生相同的错误,Blam。 - user2274578
我会一直保留它,直到你得到答案,只是让人们知道这不是答案。 - paparazzo

0

0

我希望有人能在他们的环境中测试一下并确认这个问题。此外,稍微尝试了一下,发现另一个奇怪的行为:

  • 将模式更改为标准
  • 实现IsExpanded双向绑定(不确定是否需要)

接下来运行程序:

  • 展开标题,例如1000 - 滚动条跳到其他位置

重新运行程序(为了保证实验的纯洁性)

  • 展开第二个标题之类的第一个标题。
  • 展开标题,例如1000 - 滚动条停留在正确的位置...

展开第一个节点似乎是一种解决方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接