This question already has answers here:
WPF DataGrid : CanContentScroll property causing odd behavior
(2 answers)
Closed 9 years ago.
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
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
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.
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.
Related
I have a virtualized TreeView with these properties:
<TreeView
ScrollViewer.CanContentScroll="True"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingStackPanel.ScrollUnit="Pixel"
VirtualizingStackPanel.CacheLength="20,20"
VirtualizingStackPanel.CacheLengthUnit="Item"
/>
...
</TreeView>
The TreeViewItem's are roughly the same height.
The problem is that when scrolling down the handle of the scrollbar changes size and the scrolling is "jumpy" which leads to a quite poor user experience.
How might one obtain a more smooth UX?
I realize that this is an old post and you probably have a solution now, but it has a simple answer, so for anyone who may come across this in the future, here it is:
The behaviour you describe is normal. Had you search in MSDN for ScrollViewer.CanContentScroll which is the very first property that you used, you would have found this:
Content in a ScrollViewer can be scrolled in terms of physical units or logical units. Physical units are device independent pixels. Logical units are used for scrolling items within an ItemsControl. The default behavior of the ScrollViewer is to use physical units to scroll its content. However, in cases where the CanContentScroll is set to true, the content could use logical units to scroll. For example, ListBox, ListView, and other controls that inherit from ItemsControl use logical units to scroll. If CanContentScroll is true, the values of the ExtentHeight, ScrollableHeight, ViewportHeight, and VerticalOffset properties are number of items, instead of physical units.
If you require physical scrolling instead of logical scrolling, wrap the host Panel element in a ScrollViewer and set its CanContentScroll property to false. Physical scrolling is the default scroll behavior for most Panel elements.
So, this basically means that you can either scroll in terms of pixels, or whole items from the collection. Therefore, to have smooth scrolling, you'll want to scroll using pixels. To do that, you simply need to set the ScrollViewer.CanContentScroll property to False. So it turns out that it is an easy fix after all.
Unfortunately, setting ScrollViewer.CanContentScroll = False will disable virtualization so doesn't really help with this problem.
I have found that setting a hard coded height value to the content of your TreeViewItem makes the scrollbar behave a lot better, but the scroll bar still resizes a bit.
I have a control that has a list that varies in length greatly. This control appears in various places meaning that i cannot calculate its position and desired height easily.
Moreover all I want is for the scrollviewer to simply size itself according to its parent. currently it insists on sizing itself according to the content.
currently when i have a list that exceeds the height of the screen the whole control extends off the bottom and the scrollviewer shows no bar (because it has stretched to the heigth of the contents and so thinks it is not required).
I've not included code as the object graph is fairly deep.
What i am looking for is a set of conditions that would cause the scrollviewer to resize itself according to its content rather than its parent.
I have it working in a similar situation involving grids and datagrids, the unique part of this control is that there is a list containing controls.
Any ideas? I would prefer solutions that don't require use of code behind - but im really not in a position to be choosey.
Here are common reasons that come to mind that would allow a scroll viewer to size to its contents rather than to its "parent":-
It's placed on a Canvas or a StackPanel
It's assigned to a Grid row/column with it's Horizontal or Vertical alignment not set to Stretch and its content size is less than the size of the row or column.
Its ultimately upto the containing panel how it chooses to size a child element so its not really possible to dictate this completely from code inside the child.
I have a XamDataGrid in one of my user controls, inside of a stackpanel. I want the grid to maintain the same height regardless of how many rows are present in the grid. To do that, I set the grid's Height property to an explicit value.
Is that how things are done in WPF? Every time I do explicit sizing I feel like I am doing WinForms and not using WPF properly. Is setting the Height directly the only/correct solution?
There's nothing wrong with setting an explicit Height in situations where you want an element to always stay the same height. Where it's less appropriate is in situations where sizing is better handled by the parent layout Panel or the element's child content which can use the available space dynamically.
WPF uses a relative measurement system which at first glance is not intuitive. I have never found an example when I was forced to use explicit sizes ( once when I paint something on Canvas). I use styles in 90% cases where I define Padding, Margin, Aligment etc. Sometimes I use MinHeight and MinWidth for simple things.
About that Grid you can put it in the ScrollViewer or ViewBox to have dynamic sizing, yet If it won't be trouble set the explicit Height.
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:
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.
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?
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.
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.
I have read that placing a canvas inside a scrollviewer won't work because the canvas does not report its size. I have been experimenting with different containers (borders, grids, canvases and scrollviewers) and could really do with a simple explanation of how scrollviewers behave within nested containers. e.g. If I have a container hierarchy of UserControl>Grid1>Canvas1>ScrollViewer>Grid2>Canvas2 should scrollbars appear around Grid2 when Canvas2 becomes wider than Canvas1? (or indeed wider than UserControl) If not, how should I organise my containers so I can add loads of uielements to Canvas2 and have scrollbars appear as necessary. (My usercontrol width and height are set to 100%)
Canvas will work with a ScrollViewer if you explicitly give it a size. The problem comes from the fact that if you don't supply a Height and Width for any control it will try and determine it's available area based off it's parent container. A ScrollViewer however has infinite available area.
Take a Grid for example. If I define a Grid that has 2 Star Width columns and 2 Star Height rows. How does the Grid know how wide each of those columns should be? The star says they should be half of the available area, but inside a ScrollViewer the available area is infinity.
What controls are you adding to Canvas2? If you are adding them with fixed positions than the ScrollViewer will expand to house all elements. If you aren't giving them fixed positions than all the controls will stack on top of each other, because that is the arrange behavior of a Canvas