0

I am working on a Windows 10 Universal app and see some flickering issues when I use a ListView in my app. My ListView is using x:Bind to bind to an ObservableCollection in my View Model.

When user performs some actions, or a background update occurs, I do some processing that requires the ObservableCollection to be refreshed.

    private ObservableCollection<Item> UIItems = new ObservableCollection<Item>();
    private bool IsUpdating = false;

    private void UpdateUIProperties(List<Item> newItems)
    {
        DispatcherHelper.CheckBeginInvokeOnUI(() =>
        {
            IsUpdating = true;
            UIItems.Clear();
            foreach (var item in newItems)
            {
                if (item.IsVisible)
                {
                    UIItems.Add(item);
                }
            }
            IsUpdating = false;
        });
    }

After this code gets executed, the ListView flickers and then the Scrollviewer goes all the way to the top. Is there any way to prevent this and have the ListView's ScrollViewer stay at its original offset?

Michael Sabin
  • 1,679
  • 1
  • 20
  • 33

2 Answers2

1

A solution that seem to work for me is to bind the Itemsource to an Observable collection and then have another collection that contains the items that you want to add. Have the Item in the collection implement the interface below. When you want to update the collection use the MergeCollection method to make sure the items in the collection are preserved, but they have the new config.

    public interface IConfigureFrom<T>
    {
        void ConfigureFrom(T other);
    }

    public static void MergeCollection<T>(ICollection<T> source, ICollection<T> dest)  where T : IConfigureFrom<T>, new()
    {
        // First remove entries at the bottom of the dest list that are no longer there
        if (dest.Count > source.Count)
        {
            for (int i = dest.Count - 1; i >= source.Count; i--)
            {
                var coll = dest as Collection<T>;
                if (coll != null)
                {
                    coll.RemoveAt(i);
                }
                else
                {
                    dest.Remove(dest.Last());
                }
            }
        }

        // reconfigure existing entries with the new configureation
        var sourecList = source.ToList();
        var destList = dest.ToList();

        for (int i = dest.Count - 1; i >= 0; i--)
        {
            var target = destList[i];
            var config = sourecList[i];
            target.ConfigureFrom(config);
        }


        // add new entries at the end and configure them from the source list
        for (int i = dest.Count; i < source.Count; i++)
        {
            T newItem = new T();
            newItem.ConfigureFrom(sourecList[i]);
            dest.Add(newItem);
        }

    }
Michael Sabin
  • 1,679
  • 1
  • 20
  • 33
0

When changing all items in your ListView, it is usually better to just swap the whole ItemsSource.

Just set:

UIItems = new List<...>(your data); 

And have it fire OnNotifyPropertyChanged of course.

Kai Brummund
  • 3,538
  • 3
  • 23
  • 33
  • The problem with doing that is it will cause the ListView to scroll to the top after the UIItems collection is updated. With the above solution, the scroll offset will stay the same. – Michael Sabin Jun 29 '15 at 13:10
  • Set the ItemsUpdatingScrollMode to KeepScrollOffset: See here: http://stackoverflow.com/questions/27924000/prevent-the-listview-from-scrolling-to-its-top-position-when-itemsource-changed/27933274#27933274 – Kai Brummund Jun 29 '15 at 17:21
  • But to be honest: When removing all items in the collection anyway, should the view really stay? If you are just changing *some* items, why not update just these instead of refilling the collection? – Kai Brummund Jun 29 '15 at 17:23