0

I have an application where I need to take a certain action when the user gets to a certain place in a ScrollViewer. This action sometimes includes scrolling the ScrollViewer to a different location programmatically.

In order to moniter the user's scrolling action, I am listening for the ViewChanged event of the ScrollViewer. The issue is that when I scroll progrmatically from within the ViewChanged event handler, that same event handler ends up getting called again, causing undesired additional scrolling to happen.

I have tried creating a custom method to remove the event handler before calling ScrollViewer.ChangeView(), but this seems to have no effect.

Can anyone come up with a way around this issue, or a way to differentiate the user's scrolling action from my programmatic one?

private void MyScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
    if (conditionals) 
    {
        ScrollTo(location);
    }
}

private void ScrollTo(double offset)
{
    MyScrollViewer.ViewChanged -= MyScrollViewer_ViewChanged;
    MyScrollViewer.ChangeView(offset, null, null);
    MyScrollViewer.ViewChanged += MyScrollViewer_ViewChanged;
}
Zach Olivare
  • 3,805
  • 3
  • 32
  • 45

1 Answers1

1

It is, unfortunately, not possible to determine what triggered a ViewChanged event. It is however possible to solve this problem.

The issue is that ChangeView() is asynchronous, so re-adding the event handler immediately after calling ChangeView is too soon. ChangeView will raise a bunch of ViewChanged events with a final one where e.IsIntermediate == false; only once that happens should you re-hook the event handler. The best way to handle this might be to use a temporary event handler that waits for that e.IsIntermediate == false and then re-hooks the original handler.

To prevent the user from interacting with the ScrollViewer during the execution of ChangeView, the scroll and zoom modes can be temporarily disabled.

Finally, if the user is manipulating the ScrollViewer when the conditionals are met, that manipulation needs to be canceled before calling ScrollTo().

EDIT: In my implementation, an issue arose where because of the number of times these handlers were called, event handlers were added more than once. To combat this, I've taken the simple strategy from here.

private void MyScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) 
{
    if (!conditionals) return;

    if (e.IsIntermediate) 
    {
        var uiElement = MyScrollViewer.Content as UIElement;
        uiElement?.CancelDirectManipulations();
    }

    ScrollTo(location);
}

private void Temporary_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
    if (e.IsIntermediate) return;
    MyScrollViewer.ViewChanged -= Temporary_ViewChanged;
    MyScrollViewer.ViewChanged -= MyScrollViewer_ViewChanged;
    MyScrollViewer.ViewChanged += MyScrollViewer_ViewChanged;

    MyScrollViewer.HorizontalScrollMode = ScrollMode.Enabled;
    MyScrollViewer.VerticalScrollMode = ScrollMode.Enabled;
    MyScrollViewer.ZoomMode = ZoomMode.Enabled;
}

private void ScrollTo(double offset)
{
    MyScrollViewer.ViewChanged -= MyScrollViewer_ViewChanged;
    MyScrollViewer.ViewChanged -= Temporary_ViewChanged;
    MyScrollViewer.ViewChanged += Temporary_ViewChanged;

    MyScrollViewer.HorizontalScrollMode = ScrollMode.Disabled;
    MyScrollViewer.VerticalScrollMode = ScrollMode.Disabled;
    MyScrollViewer.ZoomMode = ZoomMode.Disabled;

    MyScrollViewer.ChangeView(offset, null, null);
}
Community
  • 1
  • 1
Zach Olivare
  • 3,805
  • 3
  • 32
  • 45