title: WPF: Horizontal ScrollViewer on DataGrid snapping to right side on reinstantation of the bound ObservableCollection
body: <p>I am currently experiencing an issue in WPF where the horizontal ScrollViewer of a DataGrid snaps to the right side of the possible scroll space (showing the right most content of the DataGrid) when reinstantiating the bound ObservableCollection.</p>
  
  <p>Even if I trigger a behavior to manually set the HorizontalOffset to 0 when a bound event is invoked, and invoke the event immediately after rebinding the list, the 0 is ignored and the snap goes to the right side again. I assume this is something to do with the order of operations and command queue inside the ScrollViewer.</p>
  
  <p>This seems like something that should be default behaviour (I'm not sure why you'd ever want a scroll bar to snap to right side by default when the data is populated). Does anyone know of a workaround to this issue?</p>
  
  <p>As requested, code files from my replication project.</p>
  
  <p><strong>MainWindow.xaml</strong></p>
  
  <pre><code>&lt;Window x:Class="WpfScrollViewer.MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:local="clr-namespace:WpfScrollViewer"
      mc:Ignorable="d"
      Title="MainWindow" Height="450" Width="800"&gt;
  &lt;Window.DataContext&gt;
      &lt;local:MainViewModel/&gt;
  &lt;/Window.DataContext&gt;
  &lt;Grid&gt;
      &lt;Grid.RowDefinitions&gt;
          &lt;RowDefinition Height="Auto"/&gt;
          &lt;RowDefinition Height="Auto"/&gt;
      &lt;/Grid.RowDefinitions&gt;
      &lt;StackPanel Grid.Row="0" Orientation="Horizontal"&gt;
          &lt;Button Content="Rebind" Command="{Binding RebindCommand}"/&gt;
          &lt;Button Content="Clear and Set" Command="{Binding ClearCommand}"/&gt;
      &lt;/StackPanel&gt;
  
      &lt;DataGrid ItemsSource="{Binding People}" Grid.Row="1" HorizontalScrollBarVisibility="Visible" FontSize="30"&gt;
  
      &lt;/DataGrid&gt;
  &lt;/Grid&gt;
  </code></pre>
  
  <p></p>
  
  <p><strong>Person.cs</strong></p>
  
  <pre><code>namespace WpfScrollViewer
  {
      public class Person
      {
          public string FirstNames { get; set; }
  
          public string LastName { get; set; }
  
          public int Age { get; set; }
  
          public string Address { get; set; }
  
          public string PostCode { get; set; }
  
          public string PhoneNumber { get; set; }
      }
  }
  </code></pre>
  
  <p><strong>DelegateCommand.cs</strong></p>
  
  <pre><code>using System;
  using System.Windows.Input;
  
  namespace WpfScrollViewer
  {
      public class DelegateCommand : ICommand
      {
          private readonly Action _fn;
          private readonly Func&lt;bool&gt; _canExecute;
  
          public event EventHandler CanExecuteChanged;
  
          public DelegateCommand(Action fn, Func&lt;bool&gt; canExecute = null)
          {
              _fn = fn;
              _canExecute = canExecute;
          }
  
          public bool CanExecute(object parameter)
          {
              if (_canExecute == null)
              {
                  return true;
              }
  
              return _canExecute();
          }
  
          public void Execute(object parameter)
          {
              _fn();
          }
      }
  }
  </code></pre>
  
  <p><strong>MainViewModel.cs</strong></p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.ComponentModel;
  using System.Windows.Input;
  
  namespace WpfScrollViewer
  {
      public class MainViewModel : INotifyPropertyChanged
      {
          private readonly Random _random = new Random();
  
          private ObservableCollection&lt;Person&gt; _people;
  
          public ObservableCollection&lt;Person&gt; People
          {
              get =&gt; _people;
              set
              {
                  if (_people == value)
                  {
                      return;
                  }
  
                  _people = value;
                  PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(People)));
              }
          }
  
          public event PropertyChangedEventHandler PropertyChanged;
  
          public ICommand RebindCommand { get; }
  
          public ICommand ClearCommand { get; }
  
          public MainViewModel()
          {
              RebindCommand = new DelegateCommand(RebindPeople);
              ClearCommand = new DelegateCommand(ClearAndSetPeople);
          }
  
          private void RebindPeople()
          {
              People = new ObservableCollection&lt;Person&gt;(GetPeople());
          }
  
          private void ClearAndSetPeople()
          {
              var people = GetPeople();
              People.Clear();
              foreach (var person in people)
              {
                  People.Add(person);
              }
          }
  
          private List&lt;Person&gt; GetPeople()
          {
              var people = new List&lt;Person&gt;
              {
                  new Person
                  {
                      FirstNames = "John",
                      LastName = "Doe",
                      Address = "17 Random Street",
                      PostCode = "RN32 2JR",
                      Age = 31,
                      PhoneNumber = "07647123456"
                  },
                  new Person
                  {
                      FirstNames = "Jane",
                      LastName = "Doe",
                      Address = "17 Random Street",
                      PostCode = "RN32 2JR",
                      Age = 30
                  },
                  new Person
                  {
                      FirstNames = "Jack",
                      LastName = "Freens",
                      Address = "37 Badboi Lane",
                      Age = 30
                  },
                  new Person
                  {
                      FirstNames = "Richard",
                      LastName = "Brodget",
                      Address = "69 Meme Street",
                      Age = 31
                  },
                  new Person
                  {
                      FirstNames = "Sam",
                      LastName = "Orfitt",
                      Address = "16 Withernsea Road",
                      Age = 29
                  },
                  new Person
                  {
                      FirstNames = "Tom",
                      LastName = "Orfitt",
                      Address = "16 Withernsea",
                      Age = 27
                  }
              };
  
              var rmCount = _random.Next(1, 4);
              for (var i = 0; i &lt; rmCount; i++)
              {
                  people.RemoveAt(_random.Next(people.Count));
              }
  
              return people;
          }
      }
  }
  </code></pre>
  
  <p>Using the "Rebind" button shows the behaviour I've described above. Make sure you scroll slightly over to the right, and the horizontal scroll bar will snap to the right on rebind. If the bar is fully over to the left, the horizontal scroll bar will correctly snap to the left, as I'd like it to do in all situations.</p>
  
  <p>Cheers </p>