Search code examples
c#wpflistboxscrollbarscrollviewer

When i insert item in ListBox, my screen also move


Same with the subject, When i add some item in ListBox, also Listbox's Scroll Bar is moved automatically.

Because verticalOffset is same, but ExtraHeight is enlarged.

I Just want Don't move scrollBar when i insert some item...

I use both of ObservableCollection's Insert(0, Object) method and Add() method

The Symptom appears when VerticalOffSet is not 0 nor max Height. You can see that like below

Before Insert ItemAfter Insert Item

And i already moved it manually(with Code) But when i move it original position, I should watch the move animation.

Did you have an idea?

Plz know me about that.


Solution

  • In WPF the ItemsControl maintains the scroll offset relative to the beginning of the list, forcing items in the viewport to move down when items are added to the ItemsSource. UWP allows to customize this behavior e.g. to behave the way you need it.

    I recommend to extend ListBox or alternatively create an attached behavior.

    The following example extends ListBox and handles the internal ScrollViewer to adjust the scroll offset to keep the first visible item in the viewport when items are added to the ItemsSource:

    class KeepItemsInViewListBox : ListBox
    {
      private ScrollViewer ScrollViewer { get; set; }
    
      #region Overrides of FrameworkElement
    
      /// <inheritdoc />
      public override void OnApplyTemplate()
      {
        base.OnApplyTemplate();
        if (TryFindVisualChildElement(this, out ScrollViewer scrollViewer))
        {
          this.ScrollViewer = scrollViewer;
        }
      }
    
      #endregion
    
      #region Overrides of ListView
    
      /// <inheritdoc />
      protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
      {
        base.OnItemsChanged(e);
    
        if (this.ScrollViewer == null)
        {
          return;
        }
    
        double verticalOffset;
        switch (e.Action)
        {
          case NotifyCollectionChangedAction.Add when e.NewItems != null:
            // Check if insert or add
            verticalOffset = e.NewStartingIndex < this.ScrollViewer.VerticalOffset 
              ?  this.ScrollViewer.VerticalOffset + e.NewItems.Count 
              : this.ScrollViewer.VerticalOffset;
            break;
          case NotifyCollectionChangedAction.Remove when e.OldItems != null:
            verticalOffset = this.ScrollViewer.VerticalOffset - e.OldItems.Count;
            break;
          default:
            verticalOffset = this.ScrollViewer.VerticalOffset;
            break;
        }
    
        this.ScrollViewer?.ScrollToVerticalOffset(verticalOffset);
      }
    
    
      #endregion
    
      public bool TryFindVisualChildElement<TChild>(DependencyObject parent, out TChild childElement)
        where TChild : FrameworkElement
      {
        childElement = null;
        if (parent == null)
        {
          return false;
        }
    
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
        {
          DependencyObject child = VisualTreeHelper.GetChild(parent, i);
          if (child is TChild resultElement)
          {
            childElement = resultElement;
            return true;
          }
    
          if (TryFindVisualChildElement(child, out childElement))
          {
            return true;
          }
        }
    
        return false;
      }
    }
    

    The class can be enhanced by making the behavior optional e.g., by introducing an enum.