Search code examples
.netwpfxaml

How do I prevent a ScrollViewer in WPF from eating the scroll event if the Scrollbar is auto hidden


I have a ScrollViewer that contains everything in this window and some ItemControls that dynamically grow and shrink depending om how many items they contain. I have wrapped them in ScrollViewers and capped their height to 400 so that the ScrollViewers become scrollable once they become taller than 400 pixels. The ScrollViewers also contain some other stuff like a textbox and button to create a new item to add to the ItemsControl that I only want to bring into view once the users has scrolled all the way to the bottom.

<ScrollViewer>
    <StackPanel Margin="20">
        <ScrollViewer MaxHeight="400" VerticalScrollBarVisibility="Auto">
            <ItemsControl>
                ...                                
            </ItemsControl>  
            <StackPanel Orientation="Horizontal">
                ...
            </StackPanel>
        </ScrollViewer>
        <ScrollViewer MaxHeight="400" VerticalScrollBarVisibility="Auto">
            <ItemsControl>
                ...                                
            </ItemsControl> 
            <StackPanel Orientation="Horizontal">
                ...
            </StackPanel>       
        </ScrollViewer>                    
    </StackPanel>
</ScrollViewer>

Now for my problem: when the ItemControls are smaller then 400 pixels, the scrollbar auto hides as it should but it still eats the mousewheel events. What I would like is that it just scrolls the outer ScrollViewer instead.

I know I can change the properties of the ScrollViewer when it auto hides like this but there doesn't seem to be a property to stop it from trying to scroll anyway even though there is nothing to scroll.

<Style TargetType="{x:Type ScrollViewer}">
        <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
        <Style.Triggers>
            <Trigger Property="ComputedVerticalScrollBarVisibility" Value="Collapsed">
                What do I put here?
            </Trigger>
        </Style.Triggers>
    </Style>

The only things that seem to work are IsEnabled="False" but that also disables everything inside the ScrollViewer which I do not want and IsHittestVisible="False" which has the same problem of not letting me interact with anything inside the ItemsControl which I need.

I have also tried some stuff with the PreviewMouseEvent but nothing I tried seems to work.


Solution

  • I could not find any clean solution with only the XAML but you can reroute the event in the code behind.

    Set the VerticalScrollBarVisibility of the children on Auto

                <Style TargetType="{x:Type ScrollViewer}" x:Key="ScrollviewerChildren">
                    <Setter Property="MaxHeight" Value="250"/>
                    <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
                </Style>
    

    Then add the same event to both of the "Children". (You can do that in the XAML or the code behind)

            sc1.PreviewMouseWheel += Sc_PreviewMouseWheel;
            sc2.PreviewMouseWheel += Sc_PreviewMouseWheel;
    

    Finally, if the ComputedVerticalScrollBarVisibility is "Collapsed", call the ScrollToVerticalOffset of the main scrollviewer with the Delta value (Substract it from the actual VerticalOffset)

        private void Sc_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            var s = sender as ScrollViewer;
            if (s.ComputedVerticalScrollBarVisibility == Visibility.Collapsed)
            {
            //Scroll to the actual position - the delta scrolled
                ParentScrollViewer.ScrollToVerticalOffset(ParentScroll.VerticalOffset-e.Delta);
            }
        }
    

    However, I'm not that happy because calling the ScrollToVerticalOffset method like that produces another scrolling behaviour than the usual stuff: The scrolling distance is not the same. You can try to adapt/improve this according to your needs. But I'm sure there's a cleaner solution, I just haven't found it for the moment

    About the ScrollToVerticalOffset & VerticalOffset:
    https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.scrollviewer.verticaloffset?view=netframework-4.8
    https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.scrollviewer.scrolltoverticaloffset?view=netframework-4.8