Search code examples
uwpuwp-scrollviewer

Detect when scrollbar is visible in UWP


With a ScrollViewer in UWP, I need to detect when the scrollbar or the panning indicator is visible so that I can add padding to the ScrollViewer so that the scrollbar when it is visible does not overlap content within the viewer.

This response on SO to Detect, if ScrollBar of ScrollViewer is visible or not looked promising, however it is written for WPF. It did direct me towards the ComputedVerticalScrollBarVisibility property that also exists in the UWP ScrollViewer however this always has the value of Visible, even when the scrollbar is hidden in the user interface.

XAML fragment:

<ScrollViewer
    Name="ForegroundElement"
    VerticalScrollMode="Auto">
    <Grid>
        <Frame x:Name="shellFrame" />
    </Grid>
</ScrollViewer>

The documentation for ComputedVerticalScrollBarVisibility suggests that this is the most appropriate property to determine if the scrollbar is visible, the VerticalScrollBarVisibility property on the otherhand is used to indicate if you want the vertical scrollbar to be available at all.

ComputedVerticalScrollBarVisibility
A Visibility that indicates whether the vertical scroll bar is visible. The default is Visible.

that last bit is confusing, it looks as if this property always stays in the default state.

Initially I tried to register a change handler for the ComputedVerticalScrollBarVisibilityProperty in code behind, which as a concept generally works well to intercept changes to dependency properties on controls that do not expose a specific change event, but the following implementation never registered a change:

public ShellPage()
{
    InitializeComponent();
    // register for property change on the scrollbar, add padding to the page
    ForegroundElement.RegisterPropertyChangedCallback(
        ScrollViewer.ComputedVerticalScrollBarVisibilityProperty, 
        ForegroundElement_ScrollBarVisibilityChanged);
}


private void ForegroundElement_ScrollBarVisibilityChanged(DependencyObject sender, DependencyProperty dp)
{
    if (ForegroundElement.ComputedVerticalScrollBarVisibility == Visibility.Visible)
    {
        if (ForegroundElement.Padding.Right != 18)
            ForegroundElement.Padding = new Thickness(0, 0, 18, 0);
    }
    else
    {
        if (ForegroundElement.Padding.Right != 0)
            ForegroundElement.Padding = new Thickness(0);
    }
}

Changing the implementation to listen for the LayoutUpdated Event didn't help a lot because the value of ComputedVerticalScrollBarVisibility was always Visible, even when the scrollbar was not visible in the UI.

I double checked that the VerticalScrollMode was set to "Auto" as I am aware of this being a concern in other platforms, however changing this value or omitting it completely had no affect.


Background

Whilst the MS implementation of the scrollbar in modern Windows 10 store apps and the Edge Browser is well documented and generally tolerated by the pro-windows community because its seen as a feature of the OS, this default behaviour of hiding the scrollbar until the user hovers over it is causing our UWP apps to receive a lot of negative user feedback in the marketplace. The panning indicator is too subtle and our users are confused because they are not aware that a region can be scrolled at all.

For this post I have focussed specifically on a declared ScrollViewer, however the same issues apply to other standard controls that implement ScrollViewer internally in their control templates.


Solution

  • There is another property on a ScrollViewer that can be used to determine when the scrollbar will be visible, ScrollableHeight

    ScrollViewer.ScrollableHeight Property
    Gets a value that represents the vertical size of the area that can be scrolled; the difference between the height of the extent and the height of the viewport.

    So when the ScrollableHeight is zero, then the scrollbar will be Collapsed, for all other positive values the scrollbar will Visible.

    This implementation works as expected:

    public ShellPage()
    {
        InitializeComponent();
        // register for property change on the scrollbar, add padding to the page
        ForegroundElement.RegisterPropertyChangedCallback(
            ScrollViewer.ScrollableHeightProperty, 
            ForegroundElement_ScrollableHeightChanged);
    }
    
    private void ForegroundElement_ScrollableHeightChanged(DependencyObject sender, DependencyProperty dp)
    {
        if (ForegroundElement.ScrollableHeight > 0)
        {
            if (ForegroundElement.Padding.Right != 18)
                ForegroundElement.Padding = new Thickness(0, 0, 18, 0);
        }
        else
        {
            if (ForegroundElement.Padding.Right != 0)
                ForegroundElement.Padding = new Thickness(0);
        }
    }