wpflistboxvirtualizationscrollvieweractualheight

ScrollViewer's Viewport Height VS Actual Height


Both are quite general terms but I'm curious to know when these height will be different apart from the case we're using Virtualization?

One more question: I read on MSDN:

If CanContentScroll is true, the values of the ExtentHeight, ScrollableHeight, ViewportHeight, and VerticalOffset properties are number of items. If CanContentScroll is false, the values of these properties are Device Independent Pixels.

However I'm facing an issue with ViewPort Height: I've 2 listbox in application:
1. Which have Virtualization Enabled and CanContentScroll = True.
2. Which have no virtualization and CanContentScroll = True.

In ListBox 1 while drag-drop Viewport Height comes to 4/5 (Number of elements currently visible). However in ListBox 2 i get Viewport Height equal to Actual Height of Listbox.

Why this difference?

Few more findings:
1. Scrollable Height is number of items not visible in scrollviewer
2. Viewport Height is number of items visible in scrollviewer.
Thus Viewport Height + ScrollableHeight = Extent Height

Can someone please explain what's the difference between two listboxes? I need ViewPort hieght in case of Listbox 1


Solution

  • Finally This was the root cause:

    Quoting from https://stackoverflow.com/a/3062692/3195477:

    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.