mauiobservablecollection

How to keep CollectionView scrolled to bottom always in .Net MAUI


I want to create a Log window using CollectionView. Whenever a new item is added, it should automatically scroll to the bottom. I attempted to implement this functionality using a behavior for easy code portability. Here's how I wrote the behavior:

namespace Behaviors;


public class ScrollToBottomBehavior
{
    public static readonly BindableProperty DataSourceProperty =
        BindableProperty.CreateAttached("DataSource",
                                        typeof(object),
                                        typeof(ScrollToBottomBehavior),
                                        null,
                                        BindingMode.TwoWay,
                                        propertyChanged: OnDataSourceChanged);

    public static int GetDataSource(BindableObject view) => (int)view.GetValue(DataSourceProperty);

    public static void SetDataSource(BindableObject view, int value) => view.SetValue(DataSourceProperty, value);

    private static void OnDataSourceChanged(BindableObject sender, object oldValue, object newValue)
    {
        if (sender is not CollectionView view) return;
        //ObservableCollection<object> collection = newValue as ObservableCollection<object>;
        if (newValue is not ObservableCollection<object> collection) return;
        //view?.ScrollTo(collection.LastOrDefault(), null, ScrollToPosition.End);
        view?.ScrollTo(collection.Count - 1, position: ScrollToPosition.End);
    }
}

However, I failed because the Add method of ObservableCollection does not trigger the PropertyChanged event.

I also tried manually setting the SelectedItem to the last item of the ObservableCollection every time an item was added, but this didn't make the CollectionView scroll to the bottom automatically.

I have a couple of ideas:

  1. Modify the ObservableCollection to notify on Add, or
  2. Modify the CollectionView to add a ScrollToBottom property, as shown below, but I still encountered failure.
internal class CollectionViewExtended : CollectionView
{
    public static readonly BindableProperty ScrollToButtomProperty =
        BindableProperty.Create(nameof(ScrollToButtom), typeof(bool), typeof(CollectionViewExtended), false);

    public bool ScrollToButtom
    {
        get => (bool)GetValue(ScrollToButtomProperty);
        set => SetValue(ScrollToButtomProperty, value);
    }

    protected override void OnChildAdded(Element child)
    {
        base.OnChildAdded(child);
        if (ScrollToButtom)
        {
            ScrollTo(child, position: ScrollToPosition.End, animate: true);
        }
    }

}

Any sugguestions will be thankful.


Solution

  • CollectionView defines a ItemsUpdatingScrollMode property, which is backed by a bindable property. This property gets or sets a ItemsUpdatingScrollMode enumeration value that represents the scrolling behavior of the CollectionView when new items are added to it. The ItemsUpdatingScrollMode enumeration defines the following members:

    The default value of the ItemsUpdatingScrollMode property is KeepItemsInView. Therefore, when new items are added to a CollectionView the first item in the list will remain displayed. To ensure that the last item in the list is displayed when new items are added, set the ItemsUpdatingScrollMode property to KeepLastItemInView:

    <CollectionView ItemsUpdatingScrollMode="KeepLastItemInView">
        ...
    </CollectionView>
    

    For more information, check document: Control scroll position when new items are added.

    Note:

    In fact, the exact position of the item after the scroll has completed can be specified with the position argument of the ScrollTo methods. This argument accepts a ScrollToPosition enumeration member.

    For more information, check document: Control scroll position.