I have learned that if the height of a grid row, where the ScrollViewer
resides, is set as Auto
, the vertical scroll bar will not take effect since the actual size of the ScrollViewer
can be larger than the height in sight. So in order to make the scroll bar work, I should set the height to either a fixed number or star height
However, I now have this requirement, that I have two different views reside in two grid rows, and I have a toggle button to switch between these two views: when one view is shown, the other one is hidden/disappeared. So I have defined two rows, both heights are set as Auto
. And I bind the visibility of the view in each row to a boolean property from my ViewModel (one is converted from True
to Visible
and the other from True
to Collapsed
. The idea is when one view's visibility is Collapsed
, the height of the grid row/view will be changed to 0 automatically.
The view show/hidden is working fine. However, in one view I have a ScrollViewer
, which as I mentioned doesn't work when the row height is set as Auto
. Can anybody tell me how I can fulfill such requirement while still having the ScrollViewer
working automatically`? I guess I can set the height in code-behind. But since I am using MVVM, it would require extra communication/notification. Is there a more straightforward way to do that?
In MVVM, the way that worked for me was to bind the height of the ScrollViewer
to the ActualHeight
of the parent control (which is always of type UIElement
).
ActualHeight
is a read-only property which is only set after the control has been drawn onto the screen. It may change if the window is resized.
<StackPanel>
<ScrollViewer Height="{Binding Path=ActualHeight,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}">
<TextBlock Text=Hello"/>
</ScrollViewer>
</StackPanel>
If the parent control has an infinite height, then we have a bigger problem. We have to keep setting the height of all parents, until we hit a control with a non-infinite height.
Snoop is absolutely invaluable for this:
If the "Height" for any XAML element is 0
or NaN
, you can set it to something using one of:
Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}"
VerticalAlignment="Stretch"
Height="Auto"
Hint: Use VerticalAlignment="Stretch"
if you are a child of a Grid
with a <RowDefinition Height="*">
, and the Binding RelativeSource...
elsewhere if that doesn't work.
If you're interested, here is all of my previous attempts to fix this issue:
Can also use this:
Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=StackPanel}}"
Useful info: see Auto Height in combination with MaxHeight.
If nothing seems to work, it's probably because the ActualHeight
of the parent is either 0 (so nothing is visible) or huge (so the scrollviewer never needs to appear). This is more of a problem if there are deeply nested grids, with a scrollviewer right at the bottom.
ActualHeight
of the parent StackPanel
. In properties, filter by the word "Actual"
, which brings back ActualHeight
and ActualWidth
.ActualHeight
is zero, give it a minimum height using MinHeight
, so we can at least see something.ActualHeight
is so huge that it goes off the edge of the screen (i.e. 16,000), give it a reasonable maximum height using MaxHeight
, so the scrollbars will appear.Once the scrollbars are appearing, then we can clean it up further:
Height
of the StackPanel
or Grid
to the ActualHeight
of the parent.Finally, put a ScrollViewer
inside this StackPanel
.
It turns out that this can sometimes fail:
Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=StackPanel}}"
The reason? It the binding fails, the height will be zero and nothing will be seen. The binding can fail if we are binding to an element which is not accessible. The binding will fail if we are going up
the visual tree, then down
to a leaf node (e.g. up to the parent grid, then down to the ActualHeight
of a row attached to that grid). This is why binding to the ActualWidth
of a RowDefinition
simply won't work.
I ended up getting this working by making sure that Height=Auto
for all of the parent elements from us to the first <Grid>
element in the UserControl.
Change Height from Auto
to *
, if you can.
Example:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="525">
<StackPanel Orientation="Horizontal" Background="LightGray">
<Grid Width="100">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="_scroll1">
<Border Height="300" Background="Red" />
</ScrollViewer>
<TextBlock Text="{Binding ElementName=_scroll1, Path=ActualHeight}" Grid.Row="1"/>
</Grid>
<Grid Width="100">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="_scroll2">
<Border Height="300" Background="Green" />
</ScrollViewer>
<TextBlock Text="{Binding ElementName=_scroll2, Path=ActualHeight}" Grid.Row="1"/>
</Grid>
</StackPanel>
</Window>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With