Child elements of scrollviewer preventing scrolling with mouse wheel?
Asked Answered
M

5

46

I'm having a problem getting mouse wheel scrolling to work in the following XAML, which I have simplified for clarity:

<ScrollViewer
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
CanContentScroll="False"
>
    <Grid
    MouseDown="Editor_MouseDown"
    MouseUp="Editor_MouseUp"
    MouseMove="Editor_MouseMove"
    Focusable="False"
    >
        <Grid.Resources>
            <DataTemplate
            DataType="{x:Type local:DataFieldModel}"
            >
                <Grid
                Margin="0,2,2,2"
                >
                    <TextBox
                    Cursor="IBeam"
                    MouseDown="TextBox_MouseDown"
                    MouseUp="TextBox_MouseUp"
                    MouseMove="TextBox_MouseMove"
                    />
                </Grid>
            </DataTemplate>
        </Grid.Resources>
        <ListBox
        x:Name="DataFieldListBox"
        ItemsSource="{Binding GetDataFields}"
        SelectionMode="Extended"
        Background="Transparent"
        Focusable="False"
        >
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemContainerStyle>
                <Style
                TargetType="ListBoxItem"
                >
                    <Setter
                    Property="Canvas.Left"
                    Value="{Binding dfX}"
                    />
                    <Setter
                    Property="Canvas.Top"
                    Value="{Binding dfY}"
                    />
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
    </Grid>
</ScrollViewer>

Visually, the result is an area of some known size where DataFields read from a collection can be represented with TextBoxes which have arbitrary position, size, et cetera. In cases where the ListBox's styled "area" is too large to display all at once, horizontal and vertical scrolling is possible, but only with the scroll bars.

For better ergonomics and sanity, mouse wheel scrolling should be possible, and normally ScrollViewer would handle it automatically, but the ListBox appears to be handing those events such that the parent ScrollViewer never sees them. So far I have only been able to get wheel scrolling working be setting IsHitTestVisible=False for either the ListBox or the parent Grid, but of course none of the child element's mouse events work after that.

What can I do to ensure the ScrollViewer sees mouse wheel events while preserving others for child elements?

Edit: I just learned that ListBox has a built-in ScrollViewer which is probably stealing wheel events from the parent ScrollViewer and that specifying a control template can disable it. I'll update this question if that resolves the problem.

Mcrae answered 15/1, 2013 at 22:59 Comment(0)
M
14

Specifying a ControlTemplate for the Listbox which doesn't include a ScrollViewer solves the problem. See this answer and these two MSDN pages for more information:

ControlTemplate

ListBox Styles and Templates

Mcrae answered 16/1, 2013 at 17:5 Comment(1)
This also works for TreeViewinstead of the ListBox. Thanks!Possie
E
66

You can also create a behavior and attach it to the parent control (in which the scroll events should bubble through).

// Used on sub-controls of an expander to bubble the mouse wheel scroll event up 
public sealed class BubbleScrollEvent : Behavior<UIElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;
        base.OnDetaching();
    }

    void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        e.Handled = true;
        var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
        e2.RoutedEvent = UIElement.MouseWheelEvent;
        AssociatedObject.RaiseEvent(e2);
    }
}

<SomePanel>
            <i:Interaction.Behaviors>
                <viewsCommon:BubbleScrollEvent />
            </i:Interaction.Behaviors>
</SomePanel>
Emersonemery answered 19/4, 2013 at 17:21 Comment(9)
It works !!! Thank you very much (do not forget to add the namespace xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity")Greige
Just a note to anyone else trying to use this solution. You will need to install the Expression Blend SDK in order to get access to System.Windows.Interactivity. The NuGet command Install-Package Expression.Blend.Sdk will install it for you.Advertising
@Emersonemery This is the better answer by far. Works easily with themes and other styles/templates!Turnedon
@BradleyUffner FYI, worked for me on VS2013 Ultimate without the Expression Blend SDK. Interestingly, ReSharper added the namespace xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" though, so not sure if it's just included now.Aegaeon
+1 It works, but I don't understand why it works :( In all my WPF experience setting e.Handled = true is enough, but in this occasion I have to raise the event again!? Anyway, thanks for a workaround!Magpie
A note for the future readers. I was trying to implement this in a ListBox where my ListBoxItems were stealing wheel event. This solution didn't work for me initially until I realized that I needed to attach this behavior to the items, not the parent ListBox. Maybe it will help someone down the road.Noway
Seriously, this is the only answer that worked properly for me. Works perfectly with our MVVM solution.Madaih
I got design-time error XamlObjectWriterException: Collection property 'System.Windows.Controls.StackPanel'.'Behaviors' is null. within parent UserControlAtlanta
Is it working with panning events using a touchscreen ?Tad
M
14

Specifying a ControlTemplate for the Listbox which doesn't include a ScrollViewer solves the problem. See this answer and these two MSDN pages for more information:

ControlTemplate

ListBox Styles and Templates

Mcrae answered 16/1, 2013 at 17:5 Comment(1)
This also works for TreeViewinstead of the ListBox. Thanks!Possie
T
5

Another way of implementing this, is by creating you own ScrollViewer like this:

public class MyScrollViewer : ScrollViewer
{
    protected override void OnMouseWheel(MouseWheelEventArgs e)
    {
        var parentElement = Parent as UIElement;
        if (parentElement != null)
        {
            if ((e.Delta > 0 && VerticalOffset == 0) ||
                (e.Delta < 0 && VerticalOffset == ScrollableHeight))
            {
                e.Handled = true;

                var routedArgs = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
                routedArgs.RoutedEvent = UIElement.MouseWheelEvent;
                parentElement.RaiseEvent(routedArgs);
            }
        }

        base.OnMouseWheel(e);
    }
}
Tyika answered 8/6, 2015 at 11:23 Comment(0)
D
2

I know it's a little late but I have another solution that worked for me. I switched out my stackpanel/listbox for an itemscontrol/grid. Not sure why the scroll events work properly but they do in my case.

<ScrollViewer VerticalScrollBarVisibility="Auto" PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
                <StackPanel Orientation="Vertical">
                    <ListBox ItemsSource="{Binding DrillingConfigs}" Margin="0,5,0,0">
                        <ListBox.ItemTemplate>
                            <DataTemplate>

became

<ScrollViewer VerticalScrollBarVisibility="Auto" PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ItemsControl ItemsSource="{Binding DrillingConfigs}" Margin="0,5,0,0" Grid.Row="0">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
Distribution answered 21/5, 2015 at 11:1 Comment(1)
This is because <ItemsControl> is more "dumb" control (actually, it's the ancestor for ListBox) and it's have no support for scrolling at all. So you got a solution that potentially is slower than previous version (ListBox-based).Sisto
K
0

isHitTestVisible=False in the child works great for me

Edit This isnt a good way to do it

Kunlun answered 28/9, 2022 at 9:49 Comment(2)
This effectively disables scrolling (and any other interaction) in the nested scroll viewer though.Dallapiccola
Yes I realised this a bit late! I will edit my response. Thank youKunlun

© 2022 - 2024 — McMap. All rights reserved.