3

I've got a custom horizontal ListView with custom ScrollViewer inside it's template (created with Blend). I want it to scroll horizontally when using mouse scrolling wheel. How can I do that?

jacek11
  • 97
  • 3
  • 13

4 Answers4

5

This should be done with a Behavior for greater reusability. Also, the logic from ZSH is redundant and could be simplified. Here's my code:

/// <summary>
/// Allows an <see cref="ItemsControl"/> to scroll horizontally by listening to the
/// <see cref="PreviewMouseWheel"/> event of its internal <see cref="ScrollViewer"/>.
/// </summary>
public class HorizontalScrollBehavior : Behavior<ItemsControl>
{
    /// <summary>
    /// A reference to the internal ScrollViewer.
    /// </summary>
    private ScrollViewer ScrollViewer { get; set; }

    /// <summary>
    /// By default, scrolling down on the wheel translates to right, and up to left.
    /// Set this to true to invert that translation.
    /// </summary>
    public bool IsInverted { get; set; }

    /// <summary>
    /// The ScrollViewer is not available in the visual tree until the control is loaded.
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.Loaded += OnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        AssociatedObject.Loaded -= OnLoaded;

        ScrollViewer = VisualTreeHelpers.FindVisualChild<ScrollViewer>(AssociatedObject);

        if (ScrollViewer != null)
        {
            ScrollViewer.PreviewMouseWheel += OnPreviewMouseWheel;
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        if (ScrollViewer != null)
        {
            ScrollViewer.PreviewMouseWheel -= OnPreviewMouseWheel;
        }
    }

    private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        var newOffset = IsInverted ?
            ScrollViewer.HorizontalOffset + e.Delta :
            ScrollViewer.HorizontalOffset - e.Delta;

        ScrollViewer.ScrollToHorizontalOffset(newOffset);
    }
}

You'll need to add the following references: System.Windows, System.Windows.Controls, System.Windows.Input, and you may need to get the Blend SDK NuGet package, and find and reference the System.Windows.Interactivity DLL in the Assemblies Extensions section.

Use this for VisualTreeHelpers:

public class VisualTreeHelpers
{
    /// <summary>
    /// Return the first visual child of element by type.
    /// </summary>
    /// <typeparam name="T">The type of the Child</typeparam>
    /// <param name="obj">The parent Element</param>
    public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(obj, i);
            if (child != null && child is T)
                return (T)child;
            else
            {
                T childOfChild = FindVisualChild<T>(child);
                if (childOfChild != null)
                    return childOfChild;
            }
        }
        return null;
     }
}

Reference: https://codereview.stackexchange.com/questions/44760/is-there-a-better-way-to-get-a-child

Note that it is NOT the same as VisualTreeHelper in Windows.System.Media.

Here's how to use it in XAML:

<ListBox>
    <i:Interaction.Behaviors>
        <behaviors:HorizontalScrollBehavior />
    </i:Interaction.Behaviors>
</ListBox>

Where the i namespace is declared as xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" and

behaviors is declared as

xmlns:behaviors="clr-namespace:MyNamespace"

where MyNamespace is the namespace that contains the HorizontalScrollBehavior class.

Community
  • 1
  • 1
Charlie
  • 15,069
  • 3
  • 64
  • 70
  • +1 because it saved me a lot of time searching for a properly solution. BTW, here is the link for the `VisualTreeHelpers` implementation: http://codereview.stackexchange.com/questions/44760/is-there-a-better-way-to-get-a-child – Guilherme Oliveira Jun 06 '14 at 21:00
  • Discovered that `i` could also be referenced like this: `xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"` – vapcguy Oct 25 '16 at 17:25
4

if you implement IScrollInfo you can override the MouseWheelUp to do MouseWheelLeft and down\right the in same way

edit (much more simple):

add to your ScrollViewer PreviewMouseWheel

private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (e.Delta < 0) // wheel down
            {
                if (myScrollViewer.HorizontalOffset + e.Delta > 0)
                {
                    myScrollViewer.ScrollToHorizontalOffset(myScrollViewer.HorizontalOffset + e.Delta);  
                }
                else
                {
                    myScrollViewer.ScrollToLeftEnd();
                }
            }
            else //wheel up
            {
                if (myScrollViewer.ExtentWidth > myScrollViewer.HorizontalOffset + e.Delta)
                {
                    myScrollViewer.ScrollToHorizontalOffset(myScrollViewer.HorizontalOffset + e.Delta);  
                }
                else
                {
                    myScrollViewer.ScrollToRightEnd();
                }
            }

        }

xaml:

<ScrollViewer x:Name="myScrollViewer" HorizontalScrollBarVisibility="Visible" Mouse.PreviewMouseWheel="ScrollViewer_PreviewMouseWheel"> 
ZSH
  • 905
  • 5
  • 15
  • You mean like inside the control? By 'custom' I meant that I have a custom template. Does your solution require implementing new ListView class that inherits from WPF ListView? – jacek11 Aug 22 '12 at 10:23
  • my solution meant you implement the scrollinfo on a custom panel and use the panel – ZSH Aug 22 '12 at 10:30
  • Still don't know where to put it. My code looks like this: – jacek11 Aug 22 '12 at 11:40
  • and the rest in the code behind – ZSH Aug 22 '12 at 13:33
  • i cannot access code behind from the styles which are in resource dictionary – jacek11 Aug 23 '12 at 12:37
  • can u make a custom ScrollViewer(not in xaml) and use it? – ZSH Aug 23 '12 at 13:30
  • I've created a code behind for my resources file. It works. Thanks! – jacek11 Aug 24 '12 at 14:01
  • Why not simplify the logic to: ScrollViewer.ScrollToHorizontalOffset(ScrollViewer.HorizontalOffset + e.Delta)? – Charlie May 29 '14 at 21:30
0

I was kinda looking for the most simple way to make any ScrollViewer scroll left-right instead of up-down. So here is the simplest combination of the other answers.

<ScrollViewer HorizontalScrollBarVisibility="Visible"
              PreviewMouseWheel="ScrollViewer_PreviewMouseWheel"> 

and:

private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
    ScrollViewer scrollViewer = (ScrollViewer)sender;
    if (e.Delta < 0)
    {
        scrollViewer.LineRight();
    }
    else
    {
        scrollViewer.LineLeft();
    }
    e.Handled = true;
}
kub1x
  • 3,272
  • 37
  • 38
-1

Xaml Code:

<ScrollViewer HorizontalScrollBarVisibility="Visible" 
              VerticalScrollBarVisibility="Visible" 
              PreviewMouseWheel="ScrollViewer_PreviewMouseWheel"> 
</ScrollViewer>

C# Code

private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
    var scrollViewer = (ScrollViewer)sender;
    if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
    {
        scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - e.Delta);
        e.Handled = true;
    }
}
Matthias Seifert
  • 2,033
  • 3
  • 29
  • 41
shanthosh
  • 25
  • 4