UWP ScrollViewer - 区分用户滚动和编程滚动

UWP ScrollViewer - Differentiate between user scrolling and programmatic scrolling

我有一个应用程序,当用户到达 ScrollViewer 中的某个地方时,我需要执行某个操作。此操作有时包括以编程方式将 ScrollViewer 滚动到不同的位置。

为了监听用户的滚动动作,我正在监听ScrollViewerViewChanged事件。问题是,当我从 ViewChanged 事件处理程序中以编程方式滚动时,同一个事件处理程序最终会再次被调用,导致发生不希望的额外滚动。

我尝试创建一个自定义方法来在调用 ScrollViewer.ChangeView() 之前删除事件处理程序,但这似乎没有效果。

谁能想出解决这个问题的方法,或者区分用户滚动操作和我的程序滚动操作的方法吗?

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

遗憾的是,无法确定是什么触发了 ViewChanged 事件。不过这个问题是可以解决的。

问题在于 ChangeView() 是异步的,因此在调用 ChangeView 后立即重新添加事件处理程序为时过早。 ChangeView 将引发一堆 ViewChanged 事件,最后一个事件 e.IsIntermediate == false;只有发生这种情况时,您才应该重新挂钩事件处理程序。处理此问题的最佳方法可能是使用一个临时事件处理程序等待 e.IsIntermediate == false 然后重新挂钩原始处理程序。

为了防止用户在执行 ChangeView 期间与 ScrollViewer 交互,可以暂时禁用滚动和缩放模式。

最后,如果用户在满足条件时正在操纵 ScrollViewer,则在调用 ScrollTo().

之前,该操纵需要 canceled

编辑: 在我的实现中,出现了一个问题,由于这些处理程序被调用的次数,事件处理程序被添加了不止一次。为了解决这个问题,我采用了 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);
}