Virtualization at the ViewModel level in WinRT

WPF and WinRT (C # + XAML) support UI virtualization support using panels that support it like VirtualizingStackPanel

others. When using MVVM, this is done with a ItemsControl

specific type ( ListBox

,GridView

and so on), which is associated with an enumerated property in the view model (usually ObservableCollection). The item control creates a user interface only for items that become visible. It is called user interface virtualization because only the user interface is virtualized. Only the presentation of items that are not presented is not generated and deferred until the moment the user actually scrolls the item. View model objects in the list are created in advance. So if I had a list of 100,000 people, I ObservableCollection

would have to include 100,000 view models that are made regardless of when the user views them in the view.

In our application, we would like to implement it so that the viewmodel layer is part of this virtualization. We want the item control to represent a scrollbar that matches the total number of items that can be loaded (so the observable collection should make the control think it already contains 100,000 items, so the viewport of the scrollbar is at the correct size) but we want the observable collection to be notified anytime a new item is about to show up so that it can load the actual object from the server. We want to show some kind of progress indicator inside the loaded items, and then replace it with the actual data template for the item as soon as it's loaded into the observable collection.

As much as possible, we would like to support MVVM guidelines, but performance and responsiveness are a priority. We also prefer to use a reusable solution if at all possible.

What would be the best way to handle this?

+3


source to share


3 answers


I ended up doing the POC as recommended by Simon Ferkel. I am adding code here for future reference.

        public class VirtualizaingVector<T> : ObservableObject, IObservableVector<object>
        {

        public event VectorChangedEventHandler<object> VectorChanged;

        private Dictionary<int, T> _items;

        private int _count;
        private bool _countCalculated;

        private IItemSupplier<T> _itemSuplier;

        public VirtualizaingVector(IItemSupplier<T> itemSupplier)
        {
            _itemSuplier = itemSupplier;
            _items = new Dictionary<int, T>();
        }

        #region Notifications

        private void _notifyVectorChanged(VectorChangedEventArgs args)
        {
            if (VectorChanged != null)
            {
                VectorChanged(this, args);
            }
        }

        private void _notifyReset()
        {
            var args = new VectorChangedEventArgs(CollectionChange.Reset, 0);
            _notifyVectorChanged(args);
        }

        private void _notifyReplace(int index)
        {
            var args = new VectorChangedEventArgs(CollectionChange.ItemChanged, (uint)index);
            _notifyVectorChanged(args);
        }

        #endregion

        #region Private

        private void _calculateCount()
        {
            _itemSuplier.GetCount().ContinueWith(task =>
            {
                lock (this)
                {
                    _count = task.Result;
                    _countCalculated = true;
                }

                NotifyPropertyChanged(() => this.Count);
                _notifyReset();
            }, TaskScheduler.FromCurrentSynchronizationContext());
        }

        private void _startRefreshItemAsync(T item)
        {
            var t = new Task(() =>
            {
                _itemSuplier.RefreshItem(item);
            });

            t.Start(TaskScheduler.FromCurrentSynchronizationContext());
        }

        private void _startCreateItemAsync(int index)
        {
            var t = new Task<T>(() =>
            {
                return _itemSuplier.CreateItem(index);
            });

            t.ContinueWith(task =>
            {
                lock (this)
                {
                    _items[index] = task.Result;
                }
                _notifyReplace(index);
            }, TaskScheduler.FromCurrentSynchronizationContext());

            t.Start(TaskScheduler.FromCurrentSynchronizationContext());
        }


        #endregion

        public object this[int index]
        {
            get
            {
                T item = default(T);
                bool hasItem;

                lock (this)
                {
                    hasItem = _items.ContainsKey(index);
                    if (hasItem) item = _items[index];
                }

                if (hasItem)
                {
                    _startRefreshItemAsync(item);
                }
                else
                {
                    _startCreateItemAsync(index);
                }

                return item;
            }
            set
            {
            }
        }

        public int Count
        {
            get
            {
                var res = 0;
                lock (this)
                {
                    if (_countCalculated)
                    {
                        return res = _count;
                    }
                    else
                    {
                        _calculateCount();
                    }
                }

                return res;
            }
        }

    #region Implemenetation of other IObservableVector<object> interface - not relevant
    ...
    #endregion
}
    public interface IItemSupplier<T>
    {
        Task<int> GetCount();

        T CreateItem(int index);

        void RefreshItem(T item);
    }

      



A few notes:

  • While a vector is enumerable from T, the interface it implements is IObservableVector. The reason is that for some reason WinRt controls are not listening for any IObservableVector<T>

    , only for IObservableVector<object>

    . Sad but true ...
  • The virtualizing vercor takes the supplier of the item as a parameter and uses it to request the number of items in the virtual list, and for the items themselves. It also allows the vendor of products to update items when they are available again after they have been removed from the cache
    1. This class is written for a specific scenario where memory is not an issue, but time is. It keeps a list of cached items that have already been created, but delays their creation until they are available the first time.
+3


source


In fact, WinRT ItemsControls are already capable of handling data virtualization in two ways: 1) Inject ISumportIncrementalLoading into a custom class that implements IList or IObservableVector, or by inheriting from ObservableCollection. This method is very simple, but it only supports linear scrolling (you cannot skip the data to instantly scroll from the first to 1,000,000th item) and the scrollbar resizes every time a new page of items is loaded.



2) Implements IObservableVector itself, and on the first access to the element, they simply return null and start the loading process. Once loaded, you can raise a VectorChanged event to indicate that the element is no longer zero. It's quite difficult to implement (it's hard to rely on the existing ObservableVector implementation for that), but it does support non-linear scrolling, and you can even add logic to unload items when they haven't been available to the controller for a long time (thus saving memory and reloading them on demand only).

+3


source


For what my answer to this question is worth, if you are dealing with remote and / or asynchronous back ends, with potentially different paging devices: CodeProject article for Rx and IObservableVector

0


source







All Articles