ScrollViewer's Viewport Height VS Actual Height [duplicate]
Asked Answered
C

3

9

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

Consolatory answered 20/4, 2011 at 16:42 Comment(0)
C
9

Finally This was the root cause:

Quoting from https://mcmap.net/q/793942/-wpf-datagrid-cancontentscroll-property-causing-odd-behavior:

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.

Consolatory answered 25/4, 2011 at 8:35 Comment(1)
next time you're posting a question post all relevant information right from the start. You could have for example stated that you are talking about a ScrollViewer inside an ItemsControl which is a totally different scenario than the usual ScrollViewer that just lets you scroll through some content.Alpine
C
12

the ActualHeight is the actual height of the ScrollViewer. The Viewport is what is visible from the ScrollViewers Content. So to answer your question: ViewportHeight differs from ActualHeight if the horizontal Scrollbar is visible by the Height of the Scrollbar.

so, to sum this up:

ActualHeight = ViewportHeight + HorizontalScrollbarHeight
Countertype answered 20/4, 2011 at 20:5 Comment(6)
Can you please explain the relation between horizontal scrollbar visibility with Viewport Height ????Consolatory
@Consolatory didn't I already? Here a specific example: The ActualHeight is 200px. The Horizontal Scrollbar has a height of 12px. If the Horizontal Scrollbar is shown, the Viewport Height is 188px, if it is not shown the Viewport height is 200px. And whether the Scrollbar is shown depends on the Property HorizontalScrollBarVisibility.Alpine
Thanks for reply. I understand this thing (said above) but I'm curious to know how these things are related to each other. As much i understand Viewport Height should work considering ActualHeight & Vertical Scrollbar visibility.Consolatory
@Consolatory I don't really know what more to explain. The Viewport's size is that of the ScrollViewer minus the area of the Scrollbars. you can only get the Viewport's dimensions (not set it). It gets calculated depending on the ActualHeight (whicht itsself is readonly and also gets calculated (by the Height property and restraints on the control)) and the scrollbars. It exists so you can bind to it and don't have to use a valueconverter to calculate ActualHeight - (IsHorizontalScrollBarVisible ? HorizontalScrollbarHeight : 0).Alpine
This asnwer is only correct for the cases when CanContentScroll is false The viewport height of a ScrollViewer in a ListView for example will always be the number of items the ScrollViewercan display.Thirzia
@Thirzia this answer addressed the question in its original form. It wasn't apparent until after OP edited it, that he was actually talking about a ListViewAlpine
C
9

Finally This was the root cause:

Quoting from https://mcmap.net/q/793942/-wpf-datagrid-cancontentscroll-property-causing-odd-behavior:

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.

Consolatory answered 25/4, 2011 at 8:35 Comment(1)
next time you're posting a question post all relevant information right from the start. You could have for example stated that you are talking about a ScrollViewer inside an ItemsControl which is a totally different scenario than the usual ScrollViewer that just lets you scroll through some content.Alpine
N
1

They can differ from the point of (specified) Height being evaluated to any given time during the (ongoing) rendering process.

From MSDN:

There is a difference between the properties of Height and Width and ActualHeight and ActualWidth. For example, the ActualHeight property is a calculated value based on other height inputs and the layout system. The value is set by the layout system itself, based on an actual rendering pass, and may therefore lag slightly behind the set value of properties, such as Height, that are the basis of the input change.

Because ActualHeight is a calculated value, you should be aware that there could be multiple or incremental reported changes to it as a result of various operations by the layout system. The layout system may be calculating required measure space for child elements, constraints by the parent element, and so on.

Napoli answered 20/4, 2011 at 16:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.