Reputation: 1595
I have two ScrollViewer and I need to sync position of those ScrollViewer when any of the ScrollViewer gets changes, but right now suppose when anyone scrollviewer2 is changed then on calling ChangeView event of ScrollViewer1 it's firing its ViewChangedEvent which is resetting ScrollViewer2 position back.
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;
}
}
Upvotes: 0
Views: 709
Reputation: 2581
@Nico's solution is much preferable. If you still need something with a flag, it 'll look like this:
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;
}
}
both the ScrollViewer
's ViewChanged
event is handled by this ScrollViewer_ViewChanged
Upvotes: 2
Reputation: 32775
For sync two ScrollViewers
, the better way is make a new Dependency Property
, and bind it with the same value. It will notify the ScrollViewer
to scroll automatically when the Dependency Property
value changed. This solution will stop Circular Reference
happening in the ViewChanged event.
I have implemented it for ListView
in this code sample. You could refer segment code. But for ScrollViewer
, you need to make xaml Behavior
, because ScrollViewer
is sealed class, it could not be inherited.
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);
}
}
}
Usage
<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>
And I have also added the above code to the sample that you could refer easily.
Upvotes: 1