WPF DataGrid : CanContentScroll property causing odd behavior
Asked Answered
S

2

14

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 an ObservableCollection.

The problem I had was that the scroll acted weird. It was choppy, and scrollbar would resize itself while scrolling.

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

However, it causes two unfortunate side effects:

  1. Whenever I re-create DataGrid instances and bind them to my ObservableCollection, it freezes my entire window for 5 seconds. When my DataGrid grows to a big size, this delay can last for 30 seconds.

  2. When I call TradeGrid.ScrollIntoView(TradeGrid.Items(TradeGrid.Items.Count - 1)) to scroll to the bottom, it jumps to bottom and then back to the top.

Is there another way to achieve smooth scrolling perhaps?

Springhouse answered 17/6, 2010 at 14:27 Comment(0)
R
39

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

As you have discovered, each has its tradeoffs.

Physical scrolling

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

  • The viewport always represents exactly the same portion of your scroll extent, giving you a smooth scrolling experience, and

but

  • The entire contents of the DataGrid must have all templates fully applied and be measured and arranged to determine the size of the scrollbar, leading to long delays during loading and high RAM usage, and
  • It doesn't really scroll items so it doesn't understand ScrollIntoView very well

Logical scrolling

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

but

  • 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 is much simpler since it just needs to get the right item index into view

Choosing between them

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.

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.

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.

Making logical scroll scrollbar more stable

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).

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:

  • Subclass Track to replace the calculation of Thumb position and size in MeasureOverride with your own
  • Change the ScrollBar template used for the logical-scrolling ScrollBar to use your subclassed Track instead of the regular one
  • Change the ScrollViewer template to explicitly set your custom ScrollBar template on the logical-scrolling ScrollBar (instead of using the default template)
  • Change the ListBox template to use explicitly set your custom ScrollViewer template on the ScrollViewer it creates

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.

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.

Rakia answered 17/6, 2010 at 14:43 Comment(5)
great thanks. anything i can do to make the scrollbar size more stable in logical scrolling? perhaps use a different scrolling container etc..Springhouse
You have three choices to improve this: Adjust your item heights, re-template the ListBox/ScrollViewer/ScrollBar to use a custom Track control, or write your own virtualizing panel. I've added details on all three of these to the answer.Rakia
crap.. i tried to UP your answer one more time, but that just removed my initial UP.. if you edit your answer i'll be able to re-apply it :)Springhouse
any way to at least offload the processing of physical scrolling off the UI thread? it ties up the whole app..Springhouse
Yes, see 2nd paragraph under "Choosing between them". This will achieve virtualization-like loading (but no recycling), allowing you to get past the UI lockup. An incremental improvement is to set dispatcher callback at SystemIdle priority that triggers the first unloaded item to load then returns after setting a callback to do the next one. As long as each single item loads quickly this will give you snappy performance and what appears to be background loading.Rakia
B
2

I also have same issue with my DataGrid and finally I did:

ScrollViewer.CanContentScroll="True"   
EnableRowVirtualization="True"
VirtualizingPanel.VirtualizationMode="Standard"

Now everything is working fine in my DataGrid.

Backsheesh answered 16/12, 2016 at 7:53 Comment(1)
I found better performance without setting VirtualizingPanel.VirtualizationMode at all. Setting it to "Recycling" result in jerky scroll (forcing the scrollbar to physically scroll through) and setting to "Standard" somehow result in slower scroll, which is strange, as I would of thought the default value is Standard but giving it an explicit setting lag the scroll. Not understanding why.Departmentalize

© 2022 - 2024 — McMap. All rights reserved.