无论何时在wpf中滚动任何一个ScrollViewers,都会同步滚动它们

我已经完成了这个主题:

将两个VerticalScrollBars绑定到另一个

它几乎帮助实现了目标,但仍然缺少一些东西。 这是左右或上下移动滚动条给出了我的两个滚动查看器中滚动的预期行为,但是当我们尝试在滚动查看器中滚动使用/单击这些滚动条末端的箭头按钮时,只滚动一个滚动查看器预期的行为。

那么我们需要添加/编辑以解决这个问题呢?

一种方法是使用ScrollChanged事件更新其他ScrollViewer

       private void ScrollChanged(object sender, ScrollChangedEventArgs e) { if (sender == sv1) { sv2.ScrollToVerticalOffset(e.VerticalOffset); sv2.ScrollToHorizontalOffset(e.HorizontalOffset); } else { sv1.ScrollToVerticalOffset(e.VerticalOffset); sv1.ScrollToHorizontalOffset(e.HorizontalOffset); } } 

问题出在WPF上,但是如果任何人开发UWP都会遇到这种情况,我不得不采取略微不同的方法。
在UWP中,当您设置另一个滚动查看器的滚动偏移量(使用ScrollViewer.ChangeView )时,它还会在另一个滚动查看器上触发ViewChanged事件,基本上会创建一个循环,导致它非常恶劣,并且无法正常工作。

如果滚动的对象不等于处理事件的最后一个对象,我通过在处理事件时使用一点超时来解决这个问题。

XAML:

  ...   ...  

代码背后:

 public sealed partial class MainPage { private const int ScrollLoopbackTimeout = 500; private object _lastScrollingElement; private int _lastScrollChange = Environment.TickCount; public SongMixerUserControl() { InitializeComponent(); } private void SynchronizedScrollerOnViewChanged(object sender, ScrollViewerViewChangedEventArgs e) { if (_lastScrollingElement != sender && Environment.TickCount - _lastScrollChange < ScrollLoopbackTimeout) return; _lastScrollingElement = sender; _lastScrollChange = Environment.TickCount; ScrollViewer sourceScrollViewer; ScrollViewer targetScrollViewer; if (sender == ScrollViewer1) { sourceScrollViewer = ScrollViewer1; targetScrollViewer = ScrollViewer2; } else { sourceScrollViewer = ScrollViewer2; targetScrollViewer = ScrollViewer1; } targetScrollViewer.ChangeView(null, sourceScrollViewer.VerticalOffset, null); } } 

请注意,超时为500毫秒。 这可能看起来有点长,但是当UWP应用程序在滚动时(在鼠标上使用滚轮时)有动画(或者,实际上是缓和),它会导致事件在几百毫秒内触发几次。 这个超时似乎完美无缺。

如果它有用,这是一种行为(对于UWP,但它足以得到这个想法); 使用行为有助于解耦MVVM设计中的视图和代码。

 using Microsoft.Xaml.Interactivity; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; public class SynchronizeHorizontalOffsetBehavior : Behavior { public static ScrollViewer GetSource(DependencyObject obj) { return (ScrollViewer)obj.GetValue(SourceProperty); } public static void SetSource(DependencyObject obj, ScrollViewer value) { obj.SetValue(SourceProperty, value); } // Using a DependencyProperty as the backing store for Source. This enables animation, styling, binding, etc... public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("Source", typeof(object), typeof(SynchronizeHorizontalOffsetBehavior), new PropertyMetadata(null, SourceChangedCallBack)); private static void SourceChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e) { SynchronizeHorizontalOffsetBehavior synchronizeHorizontalOffsetBehavior = d as SynchronizeHorizontalOffsetBehavior; if (synchronizeHorizontalOffsetBehavior != null) { var oldSourceScrollViewer = e.OldValue as ScrollViewer; var newSourceScrollViewer = e.NewValue as ScrollViewer; if (oldSourceScrollViewer != null) { oldSourceScrollViewer.ViewChanged -= synchronizeHorizontalOffsetBehavior.SourceScrollViewer_ViewChanged; } if (newSourceScrollViewer != null) { newSourceScrollViewer.ViewChanged += synchronizeHorizontalOffsetBehavior.SourceScrollViewer_ViewChanged; synchronizeHorizontalOffsetBehavior.UpdateTargetViewAccordingToSource(newSourceScrollViewer); } } } private void SourceScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) { ScrollViewer sourceScrollViewer = sender as ScrollViewer; this.UpdateTargetViewAccordingToSource(sourceScrollViewer); } private void UpdateTargetViewAccordingToSource(ScrollViewer sourceScrollViewer) { if (sourceScrollViewer != null) { if (this.AssociatedObject != null) { this.AssociatedObject.ChangeView(sourceScrollViewer.HorizontalOffset, null, null); } } } protected override void OnAttached() { base.OnAttached(); var source = GetSource(this.AssociatedObject); this.UpdateTargetViewAccordingToSource(source); } } 

以下是如何使用它:

       

在C#for UWP中跟进Rene Sackers代码列表时,我在VB.Net中为UWP解决了同样的问题,并且超时以避免由于一个Scroll Viewer Object触发事件而产生的惊人效果,因为它的视图被更改了代码而不是用户交互。 我设置了500毫秒的超时时间,适用于我的应用程序。

注意:svLvMain是一个scrollviewer(对我来说它是主窗口)svLVMainHeader是一个scrollviewer(对我而言,它是在主窗口上方的标题,是我想跟随主窗口跟踪的,反之亦然)。 缩放或滚动任一滚动查看器将使两个滚动查看器保持同步。

 Private Enum ScrollViewTrackingMasterSv Header = 1 ListView = 2 None = 0 End Enum Private ScrollViewTrackingMaster As ScrollViewTrackingMasterSv Private DispatchTimerForSvTracking As DispatcherTimer Private Sub DispatchTimerForSvTrackingSub(sender As Object, e As Object) ScrollViewTrackingMaster = ScrollViewTrackingMasterSv.None DispatchTimerForSvTracking.Stop() End Sub Private Sub svLvTracking(sender As Object, e As ScrollViewerViewChangedEventArgs, ByRef inMastScrollViewer As ScrollViewer) Dim tempHorOffset As Double Dim tempVerOffset As Double Dim tempZoomFactor As Single Dim tempSvMaster As New ScrollViewer Dim tempSvSlave As New ScrollViewer Select Case inMastScrollViewer.Name Case svLvMainHeader.Name Select Case ScrollViewTrackingMaster Case ScrollViewTrackingMasterSv.Header tempSvMaster = svLvMainHeader tempSvSlave = svLvMain tempHorOffset = tempSvMaster.HorizontalOffset tempVerOffset = tempSvMaster.VerticalOffset tempZoomFactor = tempSvMaster.ZoomFactor tempSvSlave.ChangeView(tempHorOffset, tempVerOffset, tempZoomFactor) If DispatchTimerForSvTracking.IsEnabled Then DispatchTimerForSvTracking.Stop() DispatchTimerForSvTracking.Start() End If Case ScrollViewTrackingMasterSv.ListView Case ScrollViewTrackingMasterSv.None tempSvMaster = svLvMainHeader tempSvSlave = svLvMain ScrollViewTrackingMaster = ScrollViewTrackingMasterSv.Header DispatchTimerForSvTracking = New DispatcherTimer() AddHandler DispatchTimerForSvTracking.Tick, AddressOf DispatchTimerForSvTrackingSub DispatchTimerForSvTracking.Interval = New TimeSpan(0, 0, 0, 0, 500) DispatchTimerForSvTracking.Start() tempHorOffset = tempSvMaster.HorizontalOffset tempVerOffset = tempSvMaster.VerticalOffset tempZoomFactor = tempSvMaster.ZoomFactor tempSvSlave.ChangeView(tempHorOffset, tempVerOffset, tempZoomFactor) End Select Case svLvMain.Name Select Case ScrollViewTrackingMaster Case ScrollViewTrackingMasterSv.Header Case ScrollViewTrackingMasterSv.ListView tempSvMaster = svLvMain tempSvSlave = svLvMainHeader tempHorOffset = tempSvMaster.HorizontalOffset tempVerOffset = tempSvMaster.VerticalOffset tempZoomFactor = tempSvMaster.ZoomFactor tempSvSlave.ChangeView(tempHorOffset, tempVerOffset, tempZoomFactor) If DispatchTimerForSvTracking.IsEnabled Then DispatchTimerForSvTracking.Stop() DispatchTimerForSvTracking.Start() End If Case ScrollViewTrackingMasterSv.None tempSvMaster = svLvMain tempSvSlave = svLvMainHeader ScrollViewTrackingMaster = ScrollViewTrackingMasterSv.ListView DispatchTimerForSvTracking = New DispatcherTimer() AddHandler DispatchTimerForSvTracking.Tick, AddressOf DispatchTimerForSvTrackingSub DispatchTimerForSvTracking.Interval = New TimeSpan(0, 0, 0, 0, 500) DispatchTimerForSvTracking.Start() tempHorOffset = tempSvMaster.HorizontalOffset tempVerOffset = tempSvMaster.VerticalOffset tempZoomFactor = tempSvMaster.ZoomFactor tempSvSlave.ChangeView(tempHorOffset, tempVerOffset, tempZoomFactor) End Select Case Else Exit Sub End Select End Sub Private Sub svLvMainHeader_ViewChanged(sender As Object, e As ScrollViewerViewChangedEventArgs) Handles svLvMainHeader.ViewChanged Call svLvTracking(sender, e, svLvMainHeader) End Sub Private Sub svLvMain_ViewChanged(sender As Object, e As ScrollViewerViewChangedEventArgs) Handles svLvMain.ViewChanged Call svLvTracking(sender, e, svLvMain) End Sub