如何防止 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;
ScrollViewer
的ViewChanged
事件都由这个ScrollViewer_ViewChanged
处理
【讨论】:
如果Horizontal或Vertical Offset为0而不是使用额外的变量,如何返回? 不,这会让你的代码更难看,这里我没有“声明”一个额外的变量,它们是另一个 ScrollViewer 的属性 我的意思是你添加了额外的变量is_programmatic_call
来检查程序调用,使用HorizontalOffset 或VerticalOffset 是否为0 不是更好吗?
您很可能误解了HorizontalOffset
和VerticalOffset
的含义。那些值为 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。当您同时拥有两个卷轴并想要相应地更新时,事情会变得更加棘手。 是的,对于horizontalScroll,您还可以添加用于绑定PointX
值的Dependency Property
。
对。现在我只是检查这个changedScrollViewer.VerticalOffset == 0) return;
,它适用于我的给定场景,但会将其标记为答案。以上是关于如何防止 ScrollViewer 在更新时触发 ViewChanged 事件?的主要内容,如果未能解决你的问题,请参考以下文章
防止 updateUIView 在 @State var 更改时更新视图