0
votes

Consider this:

<ScrollViewer>
  <!-- Several Controls /-->

  <MyControl MouseMove="myMouseMoveHandler" />

  <!-- Several Controls /-->
</ScrollViewer>

MyControl is a HSV color selection control with the color spectrum on a circle which can rotate, and the nuances of the selected hue on a triangle. It looks awesome, but sadly I cannot post a picture yet (rep). It really needs to be able to handle mouse movement in all directions on its surface.

Now when I move the mouse on MyControl (and it correctly handles the movement), the ScrollViewer still scrolls!

This happens even when it is the only control in the ScrollViewer, the movement starts and ends inside my control, and/or I set e.Handled = true in both the MouseLeftButtonDown / -Up events. Using CaptureMouse() in ..Down and ReleaseMouseCapture() in ..Up doesn't help either.

You will agree that I cannot change the ScrollViewer implementation (or can I?), and I cannot guarantee that my control is never hosted inside a ScrollViewer (eg. because I want to publish it).

It must be possible to prevent the ScrollViewer from getting the MouseMove. Proof: simply replace MyControl with a ListBox containing more items than fit into its Height, and you can swipe through the ListBox items without the ScrollViewer reacting.

But how? Is it also a ScrollViewer inside the ListBox and that's why it works there, or can it be done for my control too?

1

1 Answers

1
votes

Alright, I found a solution that works nicely.

My thoughts were so fixed to e.Handled (unavailable in MouseMove), IsHitTestVisible (hides all children from touch events as well) and sorts of stuff, I didn't see the obvious.

Here is the code in case somebody has the same question:

struct ScrollVisibilities
{
    public ScrollBarVisibility Horizontal;
    public ScrollBarVisibility Vertical;
}

Dictionary<ScrollViewer, ScrollVisibilities> scrollersStates = new Dictionary<ScrollViewer, ScrollVisibilities>();

bool scrollersDisabled;

void disableScrollViewers(bool disable)
{
    if (scrollersDisabled == disable)   // can't disable if disabled or enable if enabled
        return;
    scrollersDisabled = disable;

    if (disable)
    {
        DependencyObject dpo = Parent;
        while (dpo is FrameworkElement)
        {
            if (dpo is ScrollViewer)
            {
                ScrollViewer s = dpo as ScrollViewer;
                ScrollVisibilities v = new ScrollVisibilities()
                {
                    Horizontal = s.HorizontalScrollBarVisibility,
                    Vertical = s.VerticalScrollBarVisibility
                };
                scrollersStates.Add(s, v);
                s.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
                s.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
            }
            dpo = ((FrameworkElement)dpo).Parent;
        }
    }
    else // restore
    {
        foreach (ScrollViewer s in scrollersStates.Keys)
        {
            s.HorizontalScrollBarVisibility = scrollersStates[s].Horizontal;
            s.VerticalScrollBarVisibility = scrollersStates[s].Vertical;
        }
        scrollersStates.Clear();
    }
}

In MouseLeftButtonDown, I disableScrollViewers(true), and hook into Touch.FrameReported. In Touch_FrameReported, I disableScrollViewers(false) when all touch points have Action == Up. That way, I get the Up event even when it happens outsided MyControl.

There are limitations to this approach, as disabling the ScrollViewers will cause them to jump to their (and their childrens) unscrolled state. So I put MyControl on the top and set all the alignments accordingly.