最后这是根本原因:
引用https://stackoverflow.com/a/3062692/3195477:
您遇到了物理滚动和逻辑滚动之间的差异。
正如您所发现的,每个都有其权衡。
物理滚动
物理滚动 (CanContentScroll=false) 只是按像素进行,所以:
视口始终代表滚动范围的完全相同的部分,为您提供流畅的滚动体验,但
DataGrid 的全部内容必须完全应用所有模板并进行测量和排列以确定滚动条的大小,导致加载过程中的长时间延迟和高 RAM 使用率,并且它并没有真正滚动项目所以它不理解ScrollIntoView 很好 逻辑滚动
逻辑滚动(CanContentScroll=true) 通过项目而不是像素计算其滚动视口和范围,因此:
视口可能在不同时间显示不同数量的项目,这意味着视口中的项目数量与范围中的项目数量相比会发生变化,从而导致滚动条长度发生变化,并且
滚动从一个项目移动到下一个项目,从不介于两者之间,导致“生涩”滚动
但
只要您在后台使用 VirtualizingStackPanel,它只需要应用模板并测量和排列当前实际可见的项目,并且
ScrollIntoView 要简单得多,因为它只需要获取正确的项目索引即可
在他们之间选择
这是 WPF 提供的仅有的两种滚动方式。您必须根据上述权衡在它们之间进行选择。一般来说,逻辑滚动最适合中大型数据集,而物理滚动最适合小型数据集。
在物理滚动期间加速加载的一个技巧是使物理滚动更好,是将您的项目包装在具有固定大小的自定义装饰器中,并在不可见时将其子项的可见性设置为隐藏。这可以防止 ApplyTemplate、Measure 和 Arrange 在该项目的后代控件上发生,直到您准备好它发生。
使物理滚动的 ScrollIntoView 更可靠的一个技巧是调用它两次:一次立即调用,一次在 DispatcherPriority.ApplicationIdle 的调度程序回调中调用。
使逻辑滚动滚动条更稳定
如果您的所有项目的高度相同,则视口中可见的项目数将保持不变,从而导致滚动拇指大小保持不变(因为如果项目不变,则与总数的比率)。
也可以修改 ScrollBar 本身的行为,以便始终将拇指计算为固定大小。要做到这一点,无需任何 hacky 代码隐藏:
子类 Track 用您自己的替换 MeasureOverride 中的 Thumb 位置和大小的计算
更改用于逻辑滚动 ScrollBar 的 ScrollBar 模板以使用您的子类 Track 而不是常规的
更改 ScrollViewer 模板以在逻辑滚动 ScrollBar 上显式设置您的自定义 ScrollBar 模板(而不是使用默认模板)
更改 ListBox 模板以使用在它创建的 ScrollViewer 上显式设置您的自定义 ScrollViewer 模板
这意味着从内置 WPF 模板中复制大量模板代码,因此这不是一个非常优雅的解决方案。但另一种方法是使用 hacky 代码隐藏等到所有模板都展开后,然后找到 ScrollBar 并将 ScrollBar 模板替换为使用自定义 Track 的模板。此代码以一些非常棘手的代码为代价保存了两个大型模板(ListBox、ScrollViewer)。
使用不同的Panel 的工作量会大得多:VirtualizingStackPanel 是唯一可以虚拟化的Panel,并且只有它和StackPanel 才能进行逻辑滚动。由于您正在利用 VirtualizingStackPanel 的虚拟化功能,您将不得不重新实现所有这些以及所有 IScrollInfo 信息功能以及您的常规面板功能。我可以做类似的事情,但我会分配几天,也许很多天来把它做好。我建议你不要尝试。