如何防止 ScrollViewer 在更新时触发 ViewChanged 事件?

Posted

技术标签:

【中文标题】如何防止 ScrollViewer 在更新时触发 ViewChanged 事件?【英文标题】:How to prevent ScrollViewer firing ViewChanged event while it is updating? 【发布时间】:2019-01-15 03:32:59 【问题描述】:

我有两个 ScrollViewer,当任何 ScrollViewer 发生更改时,我需要同步这些 ScrollViewer 的位置,但现在假设当任何人 scrollviewer2 发生更改时,然后在调用 ScrollViewer1 的 ChangeView 事件时,它会触发其 ViewChangedEvent 正在重置 ScrollViewer2 位置.

 private void Scroll(ScrollViewer changedScrollViewer)
    
        var group = ScrollViewers[changedScrollViewer];
        VerticalScrollOffsets[group] = changedScrollViewer.VerticalOffset;
        HorizontalScrollOffsets[group] = changedScrollViewer.HorizontalOffset;
        foreach (var scrollViewer in ScrollViewers.Where(s => s.Value == group && s.Key != changedScrollViewer))
        
            scrollViewer.Key.ViewChanged -= ScrollViewer_ViewChanged;
            if (scrollViewer.Key.VerticalOffset != changedScrollViewer.VerticalOffset)
            
                scrollViewer.Key.ChangeView(null, changedScrollViewer.VerticalOffset, null, true);
            

            if (scrollViewer.Key.HorizontalOffset != changedScrollViewer.HorizontalOffset)
            
                scrollViewer.Key.ChangeView(changedScrollViewer.HorizontalOffset, null, null, true);
            
           //Commenting this line works. But I need to set ViewChange event back.
            scrollViewer.Key.ViewChanged += ScrollViewer_ViewChanged;
        
    

【问题讨论】:

因此,滚动查看器会在以下情况下更改视图:1) 用户滚动 2) 您调用 ChangeView,您希望在 (1) 发生时同步它们,而不是 (2) 。您可以使用标志来确保 . 谢谢,一些代码参考会有所帮助。 【参考方案1】:

@Nico 的解决方案更可取。如果您仍然需要带有标志的东西,它看起来像这样:

bool is_programmatic_call = false;
private void ScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)


    if (is_programmatic_call)
    
        is_programmatic_call = false;
        return;
    
    if(sender == ScrollViewer1)
    
        ScrollViewer2.ViewChanged -= ScrollViewer_ViewChanged;
        is_programmatic_call = true;
        ScrollViewer2.ChangeView(ScrollViewer1.HorizontalOffset, ScrollViewer1.VerticalOffset, null, true);
        ScrollViewer2.ViewChanged += ScrollViewer_ViewChanged;
    
    else
    
        ScrollViewer1.ViewChanged -= ScrollViewer_ViewChanged;
        is_programmatic_call = true;
        ScrollViewer1.ChangeView(ScrollViewer2.HorizontalOffset, ScrollViewer2.VerticalOffset, null, true);
        ScrollViewer1.ViewChanged += ScrollViewer_ViewChanged;
    

ScrollViewerViewChanged 事件都由这个ScrollViewer_ViewChanged 处理

【讨论】:

如果Horizo​​ntal或Vertical Offset为0而不是使用额外的变量,如何返回? 不,这会让你的代码更难看,这里我没有“声明”一个额外的变量,它们是另一个 ScrollViewer 的属性 我的意思是你添加了额外的变量is_programmatic_call 来检查程序调用,使用Horizo​​ntalOffset 或VerticalOffset 是否为0 不是更好吗? 您很可能误解了HorizontalOffsetVerticalOffset 的含义。那些值为 0 的值与 ScrollViewer 是否已更改其视图无关 - 以编程方式或由用户更改。【参考方案2】:

对于同步两个ScrollViewers,更好的方法是创建一个新的Dependency Property,并将其绑定到相同的值。当Dependency Property 值更改时,它将通知ScrollViewer 自动滚动。此解决方案将阻止 Circular Reference 在 ViewChanged 事件中发生。

我已经在这个code sample 中为ListView 实现了它。您可以参考段代码。但是对于ScrollViewer,你需要使xaml Behavior,因为ScrollViewer是密封类,它不能被继承。

public class SyncBehavior : Behavior<ScrollViewer>


    protected override void OnAttached()
    
        base.OnAttached();
        AssociatedObject.Loaded += OnAssociatedObjectLoaded;
        AssociatedObject.LayoutUpdated += OnAssociatedObjectLayoutUpdated;
    

    protected override void OnDetaching()
    
        base.OnDetaching();
        AssociatedObject.Loaded -= OnAssociatedObjectLoaded;
        AssociatedObject.LayoutUpdated -= OnAssociatedObjectLayoutUpdated;
    

    private void OnAssociatedObjectLayoutUpdated(object sender, object o)
    
        SyncPointOffSetY();
    

    private void OnAssociatedObjectLoaded(object sender, RoutedEventArgs routedEventArgs)
    
        SyncPointOffSetY();
        AssociatedObject.Loaded -= OnAssociatedObjectLoaded;
    

    private void SyncPointOffSetY()
    
        if (AssociatedObject == null) return;

        AssociatedObject.ViewChanged += AssociatedObject_ViewChanged;
    

    private void AssociatedObject_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
    
        var MyScrollViewer = sender as ScrollViewer;
        this.SetValue(PointOffSetYProperty, MyScrollViewer.VerticalOffset);

    

    public double PointOffSetY
    
        get  return (double)GetValue(PointOffSetYProperty); 
        set  SetValue(PointOffSetYProperty, value); 
    

    // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty PointOffSetYProperty =
        DependencyProperty.Register("PointOffSetY", typeof(double), typeof(SyncBehavior), new PropertyMetadata(0.0, CallBack));

    private static void CallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
    
        var current = d as SyncBehavior;
        var temScrollViewer = current.AssociatedObject;
        if (e.NewValue != e.OldValue & (double)e.NewValue != 0)
        
            temScrollViewer.ScrollToVerticalOffset((double)e.NewValue);
        
    



用法

<ScrollViewer  >
    <Interactivity:Interaction.Behaviors>
        <local:SyncBehavior  PointOffSetY="Binding PointY,Mode=TwoWay"/>
    </Interactivity:Interaction.Behaviors>
    <StackPanel >
        <Rectangle Height="500" Fill="Red"/>
        <Rectangle Height="500" Fill="Black"/>
        <Rectangle Height="500" Fill="Yellow"/>
    </StackPanel>
</ScrollViewer>
<ScrollViewer Grid.Column="1" >
    <Interactivity:Interaction.Behaviors>
        <local:SyncBehavior  PointOffSetY="Binding PointY,Mode=TwoWay"/>
    </Interactivity:Interaction.Behaviors>
    <StackPanel >
        <Rectangle Height="500" Fill="Red"/>
        <Rectangle Height="500" Fill="Black"/>
        <Rectangle Height="500" Fill="Yellow"/>
    </StackPanel>

</ScrollViewer>

我还将上述代码添加到sample,您可以轻松参考。

【讨论】:

谢谢,但是在您的示例中,您只有 VerticalScroll。当您同时拥有两个卷轴并想要相应地更新时,事情会变得更加棘手。 是的,对于horizo​​ntalScroll,您还可以添加用于绑定PointX值的Dependency Property 对。现在我只是检查这个changedScrollViewer.VerticalOffset == 0) return;,它适用于我的给定场景,但会将其标记为答案。

以上是关于如何防止 ScrollViewer 在更新时触发 ViewChanged 事件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ListBox 中禁用 ScrollViewer?

防止 updateUIView 在 @State var 更改时更新视图

如何在ListBox中禁用ScrollViewer?

如何防止模型事件使用 phpunit 触发?

更新 ViewModel 时如何防止 Kendo UI Grid 多次重新绑定

引发错误,防止 MySQL 触发器中的表更新