2

So I need to synchronise two ScrollViewers that are on different Windows. I have an Attached Property that enables me to scroll each ScrollViewer using buttons instead of the Scroll Bar and have tried to use that to do the synchronisation, adapting code I found here that was for UWP

Synchronized scrolling of two ScrollViewers whenever any one is scrolled in wpf

Like so.....

public class AutoScrollBehavior : Behavior<ScrollViewer>
{
    private double _height = 0.0d;
    private ScrollViewer _scrollViewer = null;

    protected override void OnAttached()
    {
        base.OnAttached();

        this._scrollViewer = base.AssociatedObject;
        this._scrollViewer.LayoutUpdated += new EventHandler(_scrollViewer_LayoutUpdated);

        var source = GetSource(this.AssociatedObject);
        this.UpdateTargetViewAccordingToSource(source);
    }

    private void _scrollViewer_LayoutUpdated(object sender, EventArgs e)
    {
        if (Math.Abs(this._scrollViewer.ExtentHeight - _height) > 1)
        {
            this._scrollViewer.ScrollToVerticalOffset(this._scrollViewer.ExtentHeight);
            this._height = this._scrollViewer.ExtentHeight;
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        if (this._scrollViewer != null)
        {
            this._scrollViewer.LayoutUpdated -= new EventHandler(_scrollViewer_LayoutUpdated);
        }
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////
    public static ScrollViewer GetSource(DependencyObject obj)
    {
        return (ScrollViewer)obj.GetValue(SourceProperty);
    }

    public static void SetSource(DependencyObject obj, ScrollViewer value)
    {
        obj.SetValue(SourceProperty, value);
    }

    public static readonly DependencyProperty SourceProperty =
                            DependencyProperty.RegisterAttached("Source", typeof(object), typeof(AutoScrollBehavior), new PropertyMetadata(null, SourceChangedCallBack));


    private static void SourceChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        AutoScrollBehavior synchronizeHorizontalOffsetBehavior = d as AutoScrollBehavior;
        if (synchronizeHorizontalOffsetBehavior != null)
        {
            var oldSourceScrollViewer = e.OldValue as ScrollViewer;
            var newSourceScrollViewer = e.NewValue as ScrollViewer;
            if (oldSourceScrollViewer != null)
            {
                oldSourceScrollViewer.SourceUpdated -= synchronizeHorizontalOffsetBehavior.SourceScrollViewer_ViewChanged;
            }
            if (newSourceScrollViewer != null)
            {
                newSourceScrollViewer.SourceUpdated += synchronizeHorizontalOffsetBehavior.SourceScrollViewer_ViewChanged;
                synchronizeHorizontalOffsetBehavior.UpdateTargetViewAccordingToSource(newSourceScrollViewer);
            }
        }
    }

    private void SourceScrollViewer_ViewChanged(object sender, DataTransferEventArgs e)
    {
        ScrollViewer sourceScrollViewer = sender as ScrollViewer;
        this.UpdateTargetViewAccordingToSource(sourceScrollViewer);
    }


    private void UpdateTargetViewAccordingToSource(ScrollViewer sourceScrollViewer)
    {
        if (sourceScrollViewer != null)
        {
            if (this.AssociatedObject != null)
            {
                this.AssociatedObject.ScrollToHorizontalOffset(sourceScrollViewer.HorizontalOffset);// .ChangeView(sourceScrollViewer.HorizontalOffset, null, null);
            }
        }
    }
}

Usage...

<ScrollViewer
      HorizontalScrollMode="Enabled"
      HorizontalScrollBarVisibility="Hidden">
          <interactivity:Interaction.Behaviors>
          <behaviors:SynchronizeHorizontalOffsetBehavior Source="{Binding ElementName=ScrollViewer}" />
       </interactivity:Interaction.Behaviors>                                       
</ScrollViewer>
<ScrollViewer x:Name="ScrollViewer" />

But that hasn't worked, maybe I have not converted the UPW code correctly... here is my Attached Property as is. Would like a solution that is similar to the UWP solution is the link above.

public class AutoScrollBehavior : Behavior<ScrollViewer>
{
    private double _height = 0.0d;
    private ScrollViewer _scrollViewer = null;

    protected override void OnAttached()
    {
        base.OnAttached();

        this._scrollViewer = base.AssociatedObject;
        this._scrollViewer.LayoutUpdated += new EventHandler(_scrollViewer_LayoutUpdated);
    }

    private void _scrollViewer_LayoutUpdated(object sender, EventArgs e)
    {
        if (Math.Abs(this._scrollViewer.ExtentHeight - _height) > 1)
        {
            this._scrollViewer.ScrollToVerticalOffset(this._scrollViewer.ExtentHeight);
            this._height = this._scrollViewer.ExtentHeight;
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        if (this._scrollViewer != null)
        {
            this._scrollViewer.LayoutUpdated -= new EventHandler(_scrollViewer_LayoutUpdated);
        }
    }
}

Any MVVM ideas would be useful...

Monty
  • 1,534
  • 2
  • 10
  • 12
  • Not working isn't sufficient description. – Andy Apr 07 '18 at 14:00
  • 1
    In the link, there is sv2.ScrollToVerticalOffset(e.VerticalOffset); That number from verticaloffset ought to be passed from one view to the other. You can do that various ways. Binding a datacontext or messenger are obvious candidates. Either way you want an attached property on your views somewhere that the behaviour can set and react to. You might even be able to do this with one attached property and a change handler for that. – Andy Apr 07 '18 at 14:03

1 Answers1

1

Subscribe to the ScrollViewer.ScrollChanged event and match the offset of the scroll bars. In the below example, the Window1 ScrollViewer will scroll the Window2 ScrollViewer.

Window 1 XAML:

<ScrollViewer Name="W1ScrollViewer" ScrollChanged="W1ScrollViewer_ScrollChanged/> 

Window 2 XAML:

<ScrollViewer Name="W2ScrollViewer"/>

Window 1 Code:

 //Create an object to store Window2 in
 private Window2 window2; 

 //Pass Window2 into the Window1 constructor.
 public Window1(Window2 w)
 {
    window2 = w;
    InitializeComponent();
 }

 //Scroll Window2's ScrollViewer as on Window1's ScrollViewer ScrollChanged
 private void W1ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
 {
      window2.W2ScrollViewer.ScrollToVerticalOffset(e.VerticalOffset);
      window2.W2ScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
 }

This is just a quick example. You may need to find a different way to pass windows between views, but this should get you going in the right direction.

Tronald
  • 1,520
  • 1
  • 13
  • 31