GoldieLocks
GoldieLocks

Reputation: 933

Reserve Space for ScrollViewer

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

Answers (3)

Soko
Soko

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

pushpraj
pushpraj

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

result

Upvotes: 11

Sheridan
Sheridan

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

Related Questions