WPF DataGrid:CanContentScroll属性导致奇怪的行为

Son*_*oul 14 wpf binding wpfdatagrid

我有一个解决方案,我根据用户标准生成一个DataGrid(或多个实例)..每个网格通过ObservableCollection继续接收数据

我遇到的问题是滚动表现得很奇怪.这是不稳定的,滚动条会在滚动时调整自己的大小.

比我发现.. CanContentScroll属性!它完全修复了奇怪的滚动行为,带给我暂时的幸福和快乐.

然而,它会导致2个不幸的副作用.

  1. 每当我重新创建网格实例并将它们绑定到我的可观察集合时,它会冻结整个窗口5秒钟.当我的网格增长到一个大尺寸时,这种延迟可以持续30秒.

  2. 当我调用TradeGrid.ScrollIntoView(TradeGrid.Items(TradeGrid.Items.Count - 1))滚动到底部时,它会跳到底部而不是回到顶部.

还有另一种方法可以实现平滑滚动吗?

Ray*_*rns 38

您遇到了物理滚动和逻辑滚动之间的差异.

正如您所发现的,每个都有其权衡.

物理滚动

物理滚动(CanContentScroll = false)只是以像素为单位,因此:

  • 视口始终表示滚动范围的完全相同部分,为您提供平滑的滚动体验,以及

  • DataGrid的全部内容必须完全应用所有模板,并进行测量和排列以确定滚动条的大小,从而导致加载期间的长时间延迟和高RAM使用率,以及
  • 它并不真正滚动项目,因此它不能很好地理解ScrollIntoView

逻辑滚动

逻辑滚动(CanContentScroll = true)按项而不是像素计算其滚动视口和范围,因此:

  • 视口可以在不同时间显示不同数量的项目,这意味着视口中的项目数量与范围中的项目数量相比会发生变化,从而导致滚动条长度发生变化,以及

  • 滚动从一个项目移动到下一个项目,从不介于两者之间,导致"生涩"滚动

  • 只要您在底层使用VirtualizingStackPanel,它只需要应用模板并测量和排列此刻实际可见的项目,以及

  • ScrollIntoView要简单得多,因为它只需要将正确的项索引放入视图中

在他们之间选择

这是WPF提供的唯一两种滚动.您必须根据上述权衡选择它们.通常,逻辑滚动最适合中型到大型数据集,物理滚动最适合小型数据集.

物理滚动期间加速加载的一个技巧是使物理滚动更好的方法是将项目包装在具有固定大小的自定义装饰器中,并在不可见时将其子项的可见性设置为隐藏.这可以防止ApplyTemplate,Measure和Arrange在该项的后代控件上发生,直到您准备好它为止.

使物理滚动的ScrollIntoView更可靠的一个技巧是调用它两次:一次在DispatcherPriority.ApplicationIdle的调度程序回调中立即执行一次.

使逻辑滚动滚动条更稳定

如果所有项目的高度相同,则视口中随时可见的项目数将保持不变,从而导致滚动缩略图大小保持不变(因为如果项目未更改,则与总数的比率).

也可以修改ScrollBar本身的行为,以便始终将拇指计算为固定大小.要做到这一点,没有任何hacky代码隐藏:

  • Subclass Track用您自己的MeasureOverride替换Thumb位置和大小的计算
  • 更改用于逻辑滚动ScrollBar的ScrollBar模板以使用子类轨道而不是常规轨道
  • 更改ScrollViewer模板以在逻辑滚动ScrollBar上显式设置自定义ScrollBar模板(而不是使用默认模板)
  • 更改ListBox模板以使用在其创建的ScrollViewer上显式设置自定义ScrollViewer模板

这意味着在内置的WPF模板中复制了大量的模板代码,因此它不是一个非常优雅的解决方案.但是替代方法是使用hacky代码隐藏等待所有模板扩展,然后找到ScrollBar并将ScrollBar模板替换为使用自定义Track的模板.此代码以一些非常棘手的代码为代价保存了两个大型模板(ListBox,ScrollViewer).

使用不同的Panel会有更多的工作量:VirtualizingStackPanel是唯一虚拟化的Panel,只有它和StackPanel才能进行逻辑滚动.由于您正在利用VirtualizingStackPanel的虚拟化功能,因此您必须重新实现所有这些以及所有IScrollInfo信息功能以及常规Panel功能.我可以做类似的事情,但我会分配好几天,也许很多天才能做到.我建议你不要尝试.