本文介绍了WPF DataGrid:CanContentScroll 属性导致奇怪的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

i have a solution where i generate a DataGrid (or multiple instances) based on user criteria.. each grid keeps receiving data as it comes in via ObservableCollection

我遇到的问题是卷轴表现得很奇怪.它断断续续,滚动条会在滚动时自行调整大小.

the problem i had, was that the scroll acted weird. It was choppy, and scrollbar would resize it self while scrolling.

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

than i found.. CanContentScroll property! It completely fixes the weird scrolling behavior bringing me temporary bliss and happiness.

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

however, it causes 2 unfortunate side effects.

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

  1. whenever i re-create grid instances and bind them to my observable collection, it freezes my entire window for 5 seconds. when my grid grows to a big size, this delay can last for 30 seconds.

当我调用 TradeGrid.ScrollIntoView(TradeGrid.Items(TradeGrid.Items.Count - 1)) 滚动到底部时,它会跳到底部然后再回到顶部.

when i call TradeGrid.ScrollIntoView(TradeGrid.Items(TradeGrid.Items.Count - 1)) to scroll to the bottom, it jumps to bottom and than back to the top.

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

is there another way to achieve smooth scrolling perhaps?

推荐答案

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

You are encountering the differences between physical scrolling and logical scrolling.

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

As you have discovered, each has its tradeoffs.

物理滚动

物理滚动 (CanContentScroll=false) 只是按像素进行,因此:

Physical scrolling (CanContentScroll=false) just goes by pixels, so:

  • 视口始终代表滚动范围的完全相同部分,为您提供流畅的滚动体验,并且

但是

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

逻辑滚动

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

Logical scrolling (CanContentScroll=true) calculates its scroll viewport and extent by items instead of pixels, so:

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

  • The viewport may show a different number of items at different times, meaning the number of items in the viewport as compared to the number of items in the extent varies, causing the scrollbar length to change, and

滚动从一个项目移动到下一个项目,从不在中间移动,导致生涩"滚动

Scrolling moves from one item to the next and never in between, leading to "jerky" scrolling

但是

  • 只要你在幕后使用 VirtualizingStackPanel,它只需要应用模板并测量和排列此刻实际可见的项目,并且

  • As long as you're using VirtualizingStackPanel under the hood, it only needs to apply templates and measure and arrange the items that are actually visible at the moment, and

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

ScrollIntoView is much simpler since it just needs to get the right item index into view

在它们之间选择

这是 WPF 提供的仅有的两种滚动方式.您必须根据上述权衡在它们之间进行选择.一般来说,逻辑滚动最适合大中型数据集,物理滚动最适合小数据集.

These are the only two kinds of scrolling provided by WPF. You must choose between them based on the above tradeoffs. Generally logical scrolling is best for medium to large datasets, and physical scrolling is best for small ones.

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

A trick to speed loading during physical scrolling is to make the physical scrolling better is to wrap your items in a custom Decorator that has a fixed size and sets its child's Visibility to Hidden when it is not visible. This prevents the ApplyTemplate, Measure and Arrange from occuring on the descendant controls of that item until you're ready for it to happen.

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

A trick to make physical scrolling's ScrollIntoView more reliable is to call it twice: Once immediately and once in a dispatcher callback of DispatcherPriority.ApplicationIdle.

使逻辑滚动条更稳定

如果你所有的item都是一样的高度,那么任何时候viewport中可见的item的数量都会保持不变,导致scroll thumb size保持不变(因为item与总数的比例没有变化).

If all your items are the same height, the number of items visible in the viewport at any time will stay the same, causing the scroll thumb size to stay the same (because the ratio with total number if items doesn't change).

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

It is also possible to modify the behavior of the ScrollBar itself so the thumb is always calculated to be a fixed size. To do this without any hacky code-behind:

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

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

This means copying a lot of template code fom the built-in WPF templates, so it is not a very elegant solution. But the alternative to this is to use hacky code-behind to wait until all the templates are expanded, then find the ScrollBar and just replace the ScrollBar template with the one that uses your custom Track. This code saves two large templates (ListBox, ScrollViewer) at the cost of some very tricky code.

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

Using a different Panel would be a much larger amount of work: VirtualizingStackPanel is the only Panel that virtualizes, and only it and StackPanel to logical scrolling. Since you are taking advantage of VirtualizingStackPanel's virtualization abilities you would have to re-implement all of these plus all IScrollInfo info function plus your regular Panel functions. I could do something like that but I would allocate several, perhaps many, days to get it right. I recommend you not try it.

这篇关于WPF DataGrid:CanContentScroll 属性导致奇怪的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-22 14:04