How to enable horizontal scrolling with mouse?
Asked Answered
L

6

17

I cannot determine how to scroll horizontally using the mouse wheel. Vertical scrolling works well automatically, but I need to scroll my content horizontally. My code looks like this:

<ListBox x:Name="receiptList"
         Margin="5,0"
         Grid.Row="1"
         ItemTemplate="{StaticResource receiptListItemDataTemplate}"
         ItemsSource="{Binding OpenReceipts}"
         ScrollViewer.VerticalScrollBarVisibility="Disabled">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"
                        ScrollViewer.HorizontalScrollBarVisibility="Visible"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ListBox>

My item template looks like this:

<DataTemplate x:Key="receiptListItemDataTemplate">
    <RadioButton GroupName="Numbers"
                 Command="{Binding Path=DataContext.SelectReceiptCommand,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type POS:PointOfSaleControl}}}"
                 CommandParameter="{Binding }"
                 Margin="2,0"
                 IsChecked="{Binding IsSelected}">
        <RadioButton.Template>
            <ControlTemplate TargetType="{x:Type RadioButton}" >
                <Grid x:Name="receiptGrid" >
                    <Grid>
                        <Border BorderThickness="2" 
                                BorderBrush="Green" 
                                Height="20" 
                                Width="20">
                            <Grid x:Name="radioButtonGrid" 
                                  Background="DarkOrange">
                                <TextBlock x:Name="receiptLabel"
                                           HorizontalAlignment="Center"
                                           VerticalAlignment="Center"
                                           Text="{Binding Path=NumberInQueue, Mode=OneWay}"
                                           FontWeight="Bold"
                                           FontSize="12"
                                           Foreground="White">
                                </TextBlock>
                            </Grid>
                        </Border>
                    </Grid>
                </Grid>

                <ControlTemplate.Triggers>
                    <Trigger Property="IsChecked" Value="True">
                        <Setter Property="Margin" 
                                TargetName="receiptGrid" 
                                Value="2,2,-1,-1"/>
                        <Setter Property="Background"
                                TargetName="radioButtonGrid" 
                                Value="Maroon"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </RadioButton.Template>
    </RadioButton>          
</DataTemplate>

Is there another method or control that I need to add to get that functionality?

Lough answered 16/9, 2010 at 14:3 Comment(1)
You should make it clear (by reformulating) in your question you need a solution for the mouse wheel to work. Some like me don't get it easily.Elbow
B
28

Here's a complete behaviour. Add the below class to your code, then in your XAML set the attached property to true on any UIElement that contains a ScrollViewer as a visual child.

<MyVisual ScrollViewerHelper.ShiftWheelScrollsHorizontally="True" />

The class:

public static class ScrollViewerHelper
{
    public static readonly DependencyProperty ShiftWheelScrollsHorizontallyProperty
        = DependencyProperty.RegisterAttached("ShiftWheelScrollsHorizontally",
            typeof(bool),
            typeof(ScrollViewerHelper),
            new PropertyMetadata(false, UseHorizontalScrollingChangedCallback));

    private static void UseHorizontalScrollingChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var element = d as UIElement;

        if (element == null)
            throw new Exception("Attached property must be used with UIElement.");

        if ((bool)e.NewValue)
            element.PreviewMouseWheel += OnPreviewMouseWheel;
        else
            element.PreviewMouseWheel -= OnPreviewMouseWheel;
    }

    private static void OnPreviewMouseWheel(object sender, MouseWheelEventArgs args)
    {
        var scrollViewer = ((UIElement)sender).FindDescendant<ScrollViewer>();

        if (scrollViewer == null)
            return;

        if (Keyboard.Modifiers != ModifierKeys.Shift)
            return;

        if (args.Delta < 0)
            scrollViewer.LineRight();
        else
            scrollViewer.LineLeft();

        args.Handled = true;
    }

    public static void SetShiftWheelScrollsHorizontally(UIElement element, bool value) => element.SetValue(ShiftWheelScrollsHorizontallyProperty, value);
    public static bool GetShiftWheelScrollsHorizontally(UIElement element) => (bool)element.GetValue(ShiftWheelScrollsHorizontallyProperty);

    [CanBeNull]
    private static T FindDescendant<T>([CanBeNull] this DependencyObject d) where T : DependencyObject
    {
        if (d == null)
            return null;

        var childCount = VisualTreeHelper.GetChildrenCount(d);

        for (var i = 0; i < childCount; i++)
        {
            var child = VisualTreeHelper.GetChild(d, i);

            var result = child as T ?? FindDescendant<T>(child);

            if (result != null)
                return result;
        }

        return null;
    }
}

This answer fixes a few bugs in Johannes' answer, such as not filtering by Shift key, scrolling both horizontally and vertically at the same time (motion was diagonal) and the inability to disable the behaviour by setting the property to false.

Beggar answered 5/2, 2017 at 0:2 Comment(12)
Doesn't compile. The class and its property can't be found, this is three errors in the XAML file. The C# file has no own errors. VS2015Dunaj
@ygoe, you'll need to set up a xmlns that maps to the CLR namespace of the class.Beggar
I've put the class into my own namespace and already use other classes from there. That's not the problem.Dunaj
@Dunaj what is the error you get? This exact code works on my computer.Beggar
Three errors in the XAML file: 1. "Static properties are not supported in markup", 2. "The 'ShiftWheelScrollsHorizontallyProperty' property that can be attached was not found in the type 'ScrollViewerHelper'", 3. "The property "ScrollViewerHelper.ShiftWheelScrollsHorizontallyProperty" does not exist in the XML namespace "clr-namespace:Unclassified.UI"" (that's my namespace). When I fix your code and remove the "Property" suffix from the XAML attached property, I get the same errors.Dunaj
The property suffix is standard naming convention. Are you using WPF or UWP?Beggar
WPF. You usually specify the name from the string (without Property) in XAML, not from the code (with Property). This is the first time I see it different.Dunaj
Ok I see now what you mean. Updated the answer. Thanks.Beggar
Well, like I said, I already tried that and it has the same errors.Dunaj
The Property not found issue is caused by the wrong name of the Get and Set properties. They need to be SetShiftWheelScrollsHorizontally and GetShiftWheelScrollsHorizontally instead of SetUseHorizontalScrolling and GetUseHorizontalScrollingArchibald
Using LineRight() and LineLeft() can be a bit slow depending on your content, you can use the following instead: scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + -args.Delta);Phonemics
This really needs to be fixed. It's referencing ItemsControl in the code but the answer says "set the attached property to true on any UIElement that contains a ScrollViewer as a visual child". To fix this simply swap UIElement in place of ItemsControl.Winded
M
10

I wrote an Attached Property for this purpose to reuse it on every ItemsControl containing an ScrollViewer. FindChildByType is an Telerik extension but can also be found here.

 public static readonly DependencyProperty UseHorizontalScrollingProperty = DependencyProperty.RegisterAttached(
            "UseHorizontalScrolling", typeof(bool), typeof(ScrollViewerHelper), new PropertyMetadata(default(bool), UseHorizontalScrollingChangedCallback));

        private static void UseHorizontalScrollingChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            ItemsControl itemsControl = dependencyObject as ItemsControl;

            if (itemsControl == null) throw new ArgumentException("Element is not an ItemsControl");

            itemsControl.PreviewMouseWheel += delegate(object sender, MouseWheelEventArgs args)
            {
                ScrollViewer scrollViewer = itemsControl.FindChildByType<ScrollViewer>();

                if (scrollViewer == null) return;

                if (args.Delta < 0)
                {
                    scrollViewer.LineRight();
                }
                else
                {
                    scrollViewer.LineLeft();
                }
            };
        }


        public static void SetUseHorizontalScrolling(ItemsControl element, bool value)
        {
            element.SetValue(UseHorizontalScrollingProperty, value);
        }

        public static bool GetUseHorizontalScrolling(ItemsControl element)
        {
            return (bool)element.GetValue(UseHorizontalScrollingProperty);
        }
Mediative answered 1/10, 2014 at 14:41 Comment(1)
This should be the accepted answer, +1 for including code and links to places to get more code. This implementation worked on the first try and made it extremely simple to add the behavior to only the controls I wanted to add it to.Holleyholli
M
7

The easiest way is to add PreviewMouseWheel listener to the ScrollViewer, check for shift (or whatever you want to do to indicate horizontal scroll), and then call LineLeft or LineRight (or PageLeft / PageRight) depending on the value of the Delta value of the MouseWheelEventArgs

Moonstone answered 10/5, 2011 at 17:47 Comment(4)
I dont' understand this answer: How should I add PreviewMouseWheel when there is no ScrollViewer at all?Nutation
i believe the listbox's control template has a scrollviewer inside of it. hence the reason that the attached properties like ScrollViewer.VerticalScrollBarVisibility="Disabled" work.Moonstone
Yes, but these are attached properties. But how do I attach PreviewMouseWheel of the ScrollViewer without rewriting the control template (which I won't do because this will break the automatic operating system dependent theming)?Nutation
i presume you walk the visual tree to find it? (don't remember, this answer is 3 years old now :))Moonstone
H
7

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;
}

This is basically what John Gardner suggested just with the actual code. I'm also quoting my answer from similar question here.

Hierarchize answered 9/11, 2018 at 15:1 Comment(0)
K
6
(sender as ScrollViewer).ScrollToHorizontalOffset( (sender as ScrollViewer).ContentHorizontalOffset    + e.Delta);
Kithara answered 1/3, 2014 at 12:21 Comment(1)
I used this but had to subtract the delta value to get the expected behavior (i.e. content scrolls to the right when mouse-wheel down/towards user).Kaleidoscopic
R
0

Try this:

<ListBox x:Name="receiptList" 
                       Margin="5,0" 
                       Grid.Row="1" 
                       ItemTemplate="{StaticResource receiptListItemDataTemplate}" 
                       ItemsSource="{Binding OpenReceipts}" 
                       ScrollViewer.VerticalScrollBarVisibility="Disabled" 
                       ScrollViewer.HorizontalScrollBarVisibility="Visible"
                           > 
                <ItemsControl.ItemsPanel> 
                  <ItemsPanelTemplate> 
                    <StackPanel Orientation="Horizontal" /> 
                  </ItemsPanelTemplate> 
                </ItemsControl.ItemsPanel> 
</ListBox> 

UPDATE Ooops, missed the part about the mouse wheel! Sorry

To make the mouse wheel work, you will have to subscribe to the mouse wheel event and manuaaly move the scroll bar... This can nicelly be encapsulated by a behavior but I think thats the only way to make it work!

Rhapsodic answered 16/9, 2010 at 17:10 Comment(1)
This does not works. it works only with keyborad "->" and "<-". Moruse wheel scroll does not work.Lough

© 2022 - 2024 — McMap. All rights reserved.