您正在遇到物理滚动和逻辑滚动之间的差异。
正如您发现的那样,它们各有利弊。
物理滚动
物理滚动(CanContentScroll=false)只按像素移动,因此:
- 视口始终表示滚动范围的完全相同部分,为您提供流畅的滚动体验,并且
但是
- 必须对DataGrid的整个内容应用所有模板,并测量和排列以确定滚动条的大小,从而导致加载时间过长和高RAM使用率,并且
- 它实际上不会滚动项,因此对ScrollIntoView的理解不佳
逻辑滚动
逻辑滚动(CanContentScroll=true)通过项目而不是像素计算其滚动视口和范围,因此:
但是
选择它们之间的差异
这是WPF提供的唯一两种滚动方式。您必须根据上述利弊来选择它们。通常,对于中到大型数据集,逻辑滚动最好,而对于小型数据集,物理滚动最好。
加快物理滚动加载速度的一个技巧是,将您的项包装在自定义的Decorator中,该Decorator具有固定的大小,并在不可见时将其子元素的Visibility设置为Hidden。 这可以防止后代控件上的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
函数和普通的 Panel
函数。我可以做类似的事情,但是我将花费几天甚至很多天来完善它。我建议你不要尝试它。