Reputation: 933
I've got a ScrollViewer round some controls and with
<ScrollViewer VerticalScrollBarVisibility="Auto">
...some controls here...
</ScrollViewer>
by default it doesn't reserve the space needed to show the scroll bar. This means that when it's shown/hidden my controls are squished and expanded again.
How can I reserve space for the scroll bar? Is there some kind of property or style I can set which means when it's not needed it's set to hidden rather than collapsed? Or do my child controls need a 'padding' or something on their right hand side to leave the space? Should I put the controls in a grid and make a scroll viewer sized column for the scroll bar to be displayed in (somehow)?
I would like it still to be invisible when not required I would just like the best way of making it reserve the space for when it is.
Thanks all.
Upvotes: 7
Views: 3475
Reputation: 842
I've tried the answer of pushpraj but found out, that there is still a resize happening on the content because SystemParameters.VerticalScrollBarWidth
gives you just the Width of the pure Scrollbar although it can have Margins to it.
So I came up with a Behaviour which sets directly the Visibility of the ScrollBar. First just create a class called ScrollViewerHiddenScrollbarsBehaviour
and implement it like this:
/// <summary>
/// Set the ScrollBars of a ScrollViewer to Hidden (instead of Collasped) when they are not needed.
/// </summary>
/// <seealso cref="MTM.UI.Behaviors.FrameworkElementBehaviorBase{System.Windows.Controls.ScrollViewer}" />
public class ScrollViewerHiddenScrollbarsBehaviour : FrameworkElementBehaviorBase<ScrollViewer>
{
private ScrollBar _verticalScrollBar;
private ScrollBar _horizontalScrollBar;
/// <summary>
/// Setup the Behaviour
/// </summary>
protected override void Setup()
{
AssociatedObject.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// Getting the ScrollBars IF they are set to ScrollBarVisibility.Auto
_verticalScrollBar = AssociatedObject.VerticalScrollBarVisibility != ScrollBarVisibility.Auto ? null :
AssociatedObject.Template.FindName("PART_VerticalScrollBar", AssociatedObject) as ScrollBar;
_horizontalScrollBar = AssociatedObject.HorizontalScrollBarVisibility != ScrollBarVisibility.Auto ? null :
AssociatedObject.Template.FindName("PART_HorizontalScrollBar", AssociatedObject) as ScrollBar;
if (_verticalScrollBar != null) {
// When changing the Visibility of the ScrollBar directly it won't get updated anymore when the ScrollViewer
// needs it. So let's do this manually.
DependencyPropertyDescriptor
.FromProperty(ScrollViewer.ComputedVerticalScrollBarVisibilityProperty, typeof(ScrollViewer))
.AddValueChanged(AssociatedObject, VerticalHandler);
VerticalHandler(null, null);
}
if (_horizontalScrollBar != null) {
// When changing the Visibility of the ScrollBar directly it won't get updated anymore when the ScrollViewer
// needs it. So let's do this manually.
DependencyPropertyDescriptor
.FromProperty(ScrollViewer.ComputedHorizontalScrollBarVisibilityProperty, typeof(ScrollViewer))
.AddValueChanged(AssociatedObject, HorizontalHandler);
HorizontalHandler(null, null);
}
}
/// <summary>
/// Sets the Visibility of the vertical ScrollBar as needed by the ScrollViewer, BUT Hidden instead of collapsed
/// </summary>
private void VerticalHandler(object sender, EventArgs e)
{
if (_verticalScrollBar == null)
return;
_verticalScrollBar.Visibility = AssociatedObject.ComputedVerticalScrollBarVisibility == Visibility.Visible
? Visibility.Visible
: Visibility.Hidden;
}
/// <summary>
/// Sets the Visibility of the horizontal ScrollBar as needed by the ScrollViewer, BUT Hidden instead of collapsed
/// </summary>
private void HorizontalHandler(object sender, EventArgs e)
{
if (_horizontalScrollBar == null)
return;
_horizontalScrollBar.Visibility = AssociatedObject.ComputedHorizontalScrollBarVisibility == Visibility.Visible
? Visibility.Visible
: Visibility.Hidden;
}
/// <summary>
/// Cleanup the Behaviour
/// </summary>
protected override void Cleanup()
{
AssociatedObject.Loaded -= OnLoaded;
DependencyPropertyDescriptor
.FromProperty(ScrollViewer.ComputedVerticalScrollBarVisibilityProperty, typeof(ScrollViewer))
.RemoveValueChanged(AssociatedObject, VerticalHandler);
DependencyPropertyDescriptor
.FromProperty(ScrollViewer.ComputedHorizontalScrollBarVisibilityProperty, typeof(ScrollViewer))
.RemoveValueChanged(AssociatedObject, HorizontalHandler);
}
}
Usage is as simple as this:
<ScrollViewer VerticalScrollBarVisibility="auto">
<i:Interaction.Behaviors>
<behaviors:ScrollViewerHiddenScrollbarsBehaviour />
</i:Interaction.Behaviors>
<Border Background="LightGoldenrodYellow"
Height="300" />
</Border>
</ScrollViewer>
Hope this helps
Upvotes: 0
Reputation: 13669
here is a sample
this will reserve the scroll bar space when it is not visible by using a border as placeholder
<ScrollViewer VerticalScrollBarVisibility="auto" x:Name="scroll">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Border Background="LightGoldenrodYellow"
Height="300" />
<Border Grid.Column="1"
Width="{x:Static SystemParameters.VerticalScrollBarWidth}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ComputedVerticalScrollBarVisibility, ElementName=scroll}"
Value="Visible">
<Setter Property="Visibility"
Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</Grid>
</ScrollViewer>
the first border is the content and the second border is the placeholder for reserving the space for scrollbar. you can choose to replace with element of your choice
Define as a reusable template
<Grid>
<Grid.Resources>
<ControlTemplate x:Key="ReservedSpaceScroller" TargetType="ContentControl">
<ScrollViewer VerticalScrollBarVisibility="auto"
x:Name="scroll">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<ContentPresenter />
<Border Width="{x:Static SystemParameters.VerticalScrollBarWidth}"
x:Name="placeholder" Grid.Column="1" />
</Grid>
</ScrollViewer>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ComputedVerticalScrollBarVisibility, ElementName=scroll}"
Value="Visible">
<Setter TargetName="placeholder"
Property="Visibility"
Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Grid.Resources>
<ContentControl Template="{StaticResource ReservedSpaceScroller}">
<Border Background="LightGoldenrodYellow"
Height="300" />
</ContentControl>
</Grid>
result
Upvotes: 11
Reputation: 69959
If you want the ScrollViewer
to always 'reserve space' for its ScrollBar
, then you can do this:
<ScrollViewer VerticalScrollBarVisibility="Visible">
...some controls here...
</ScrollViewer>
This will show the ScrollBar
even when it is not required, but it will not make your controls squished and expanded when it is required.
Upvotes: 0