Tom
Tom

Reputation: 657

Child elements of scrollviewer preventing scrolling with mouse wheel?

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.

Upvotes: 47

Views: 28237

Answers (6)

Mitrakov Artem
Mitrakov Artem

Reputation: 1513

I also had a "ScrollView-in-ScrollView" problem, and I ended up with the following solution (actually based on @JoeB response):

internalScrollView.PreviewMouseWheel += MarkdownScrollViewer_PreviewMouseWheel;

void MarkdownScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) {
    if (sender is MarkdownScrollViewer sv) {
        var parent = sv.Parent as UIElement;
        parent?.RaiseEvent(new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) { RoutedEvent = MouseWheelEvent });
    }
}

Upvotes: 0

Gabagoool
Gabagoool

Reputation: 1

isHitTestVisible=False in the child works great for me

Edit This isnt a good way to do it

Upvotes: 0

kahr
kahr

Reputation: 115

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

Upvotes: 5

sean.net
sean.net

Reputation: 759

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>

Upvotes: 2

JoeB
JoeB

Reputation: 2815

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>

Upvotes: 67

Tom
Tom

Reputation: 657

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

Upvotes: 14

Related Questions