Listview inside of scrollviewer prevents scrollviewer scroll

22,336

Solution 1

That happens because the ListView's (ListBox's, actually) content template wraps its items with a ScrollViewer by itself.

The simplest way is to disable it by dropping your own Template for the inside ListView, one that doesn't create a ScrollViewer:

    <ListView>
      <ListView.Template>
        <ControlTemplate>
          <ItemsPresenter></ItemsPresenter>
        </ControlTemplate>
      </ListView.Template>
      ...
    </ListView>

BTW the same happens if you have a ListView inside a ListView (this was my case).

Solution 2

IMO, the best way to handle this scenario is to create a custom control :

     class MyScrollViewer : ScrollViewer
     {
         protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
         {
            base.OnPreviewMouseWheel(e);
            if (!e.Handled)
            {
                e.Handled = true;
                this.RaiseEvent(new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
                {
                    RoutedEvent = UIElement.MouseWheelEvent,
                    Source = this
                });
            }
        }
    }

Solution 3

Did you try disabling the ListView's ScrollBars?

<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled"
          ScrollViewer.VerticalScrollBarVisibility="Disabled" />

Solution 4

Inspired by some helpful answers, I have an implementation that scrolls ancestor ScrollViewers when inner ones (including from ListView, ListBox, DataGrid) scroll past their top/bottom.

I apply an attached property to all ScrollViewers in App.xaml:

<Style TargetType="ScrollViewer" BasedOn="{StaticResource {x:Type ScrollViewer}}">
    <Setter Property="local:ScrollViewerHelper.FixMouseWheel" Value="True" />
</Style>

The attached property detects scrolling past top/bottom, and when that happens raises a mouse wheel event on the ScrollViewer's parent. Event routing gets it to the outer ScrollViewer:

public static class ScrollViewerHelper
{
    // Attached property boilerplate
    public static bool GetFixMouseWheel(ScrollViewer scrollViewer) => (bool)scrollViewer?.GetValue(FixMouseWheelProperty);
    public static void SetFixMouseWheel(ScrollViewer scrollViewer, bool value) => scrollViewer?.SetValue(FixMouseWheelProperty, value);
    public static readonly DependencyProperty FixMouseWheelProperty =
        DependencyProperty.RegisterAttached("FixMouseWheel", typeof(bool), typeof(ScrollViewerHelper),
            new PropertyMetadata(OnFixMouseWheelChanged));
    // End attached property boilerplate

    static void OnFixMouseWheelChanged(DependencyObject d,
                                       DependencyPropertyChangedEventArgs e)
    {
        var scrollViewer = d as ScrollViewer;
        if (scrollViewer == null) return;

        scrollViewer.PreviewMouseWheel += (s2, e2) =>
        {
            var parent = scrollViewer.Parent as UIElement;
            bool hitTopOrBottom = HitTopOrBottom(e2.Delta, scrollViewer);
            if (parent is null || !hitTopOrBottom) return;

            var argsCopy = Copy(e2);
            parent.RaiseEvent(argsCopy);
        };
    }

    static bool HitTopOrBottom(double delta, ScrollViewer scrollViewer)
    {
        var contentVerticalOffset = scrollViewer.ContentVerticalOffset;

        var atTop = contentVerticalOffset == 0;
        var movedUp = delta > 0;
        var hitTop = atTop && movedUp;

        var atBottom =
            contentVerticalOffset == scrollViewer.ScrollableHeight;
        var movedDown = delta < 0;
        var hitBottom = atBottom && movedDown;

        return hitTop || hitBottom;
    }

    static MouseWheelEventArgs Copy(MouseWheelEventArgs e)
        => new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
        {
            RoutedEvent = UIElement.MouseWheelEvent,
            Source = e.Source,
        };
}
Share:
22,336
ConditionRacer
Author by

ConditionRacer

Updated on April 08, 2020

Comments

  • ConditionRacer
    ConditionRacer about 4 years

    I have a scrollviewer with a couple listboxes in it. The problem is if a user uses the middle mouse roller to scroll the scrollviewer while their mouse is over a listview. The listview scrolls its internal scrollviewer to the bottom and then continues to capture the mouse, preventing the containing scrollviewer from scrolling.

    Any ideas on how to handle this?

  • ConditionRacer
    ConditionRacer over 12 years
    I can't do that because the listviews may have more items than are visible.
  • Rachel
    Rachel over 12 years
    @Justin984 Aren't they in another ScrollViewer though?
  • ConditionRacer
    ConditionRacer over 12 years
    Bah, I was going to draw a little ascii pic, but it's too much of a pain. The listboxes each have multiple entries. For example, list box 1 may have 10 entries, which require scrolling the listbox in order to view. But there are multiple listboxes which, together, are too large for the screen, so i have an outer scrollviewer to scroll the listboxes into view. Does that make sense?
  • Rachel
    Rachel over 12 years
    @Justin984 Perhaps you can do something where if the ListView is already scrolled to the bottom, it stops responding to scroll down events? Or instead passes them to the outer ScrollViewer?
  • Peter
    Peter almost 4 years
    This solution worked nicely for me and it is easy to implement. I tried other suggested solutions, but only this worked for me (ListViewer inside a ScrollViewer)
  • Al Banna Techno logy
    Al Banna Techno logy about 3 years
    Your answer is the best, because other answers, will remove the header, and to solve this you will need to implement the ScrollViewer from the ground see ref1 help
  • iam.Carrot
    iam.Carrot almost 3 years
    I was looking for exactly this. Been a long time windows 10 dev and this issue caught me by surprise in WPF. Thanks +1 :)
  • Kos
    Kos almost 3 years
    Haha thanks! I haven't touched any C# since 2013, happy to see that this answer is still relevant!