Visually Located

XAML and GIS

Persist ListView scroll position without setting NavigationCacheMode

In a previous post I wrote about why your ListView resets to the top of the list when navigating backwards. In that post I looked at using the NavigationCacheMode of the page to persist the scroll position of your ListView. I also briefly mentioned using the ScrollIntoView method of the ListView. In this post we’ll look at a little known helper class that allows you to keep your scroll position without using all the memory that NavigationCacheMode can use.

ListViewPersistenceHelper

The ListViewPersistenceHelper class allows you to easily restore the scroll position with only two methods, GetRelativeScrollPosition and SetRelativeScrollPositionAsync. These methods use the item that is at the top of the list as indicators for where the scroll position should be.

The GetRelativeScrollPosition should be called when navigating away from the page. A good place is the OnNavigatedFrom method.

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    _positionKey = ListViewPersistenceHelper.GetRelativeScrollPosition(myListView, ItemToKeyHandler);
    base.OnNavigatedFrom(e);
}
 
private string ItemToKeyHandler(object item)
{
    MyDataItem dataItem = item as MyDataItem;
    if (dataItem == null) return null;
 
    return dataItem.Id;
}

The SetRelativeScrollPositionAsync method tells the ListView where to scroll to. A good place to call this method is in the OnNavigatedTo method.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    if (_positionKey == null) return;
 
    ListViewPersistenceHelper.SetRelativeScrollPositionAsync(myListView, _positionKey, KeyToItemHandler);
}
 
private IAsyncOperation<object> KeyToItemHandler(string key)
{
    Func<System.Threading.CancellationToken, System.Threading.Tasks.Task<object>> taskProvider = token =>
    {
        var viewModel = DataContext as MyViewModel;
        if (viewModel == null) return null;
        foreach (var item in viewModel.Items)
        {
            if (item.Id == key) return Task.FromResult(item as object);
        }
        return Task.FromResult((object)null);
    };
 
    return AsyncInfo.Run(taskProvider);
}

In this simple example I’m storing the _positionKey key in a static field. This is ok for a simple case, but you may want to store this somewhere else.

Precautions:

I did notice that if you are animating the items of your ListView and using the ListViewPersistenceHelper, that there are some unintended side effects. The first few items of your ListView will still show the animation when navigating backward while the other items remain still. You can see this in the image below.

Animating side-effect

A simple work around for this is to reset the ItemContainerTransitions of the ListView in the OnNavigatedTo method if the _positionKey is not null.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    if (_positionKey == null) return;  
 
    myListView.ItemContainerTransitions = new TransitionCollection();
    ListViewPersistenceHelper.SetRelativeScrollPositionAsync(itemListView, _positionKey, KeyToItemHandler);
}

Hopefully this will help your apps use less memory while still maintaining a great experience for your users!

blog comments powered by Disqus