WinRT中ViewModel层的虚拟化

时间:2014-09-11 10:14:55

标签: c# xaml mvvm winrt-xaml ui-virtualization

WPF和WinRT(C#+ XAML)都支持使用支持它的面板进行UI虚拟化,例如VirtualizingStackPanel等。使用MVVM时使用ItemsControl某种类型的ListBoxGridViewObservableCollection等...)来绑定到视图模型上的可枚举属性(通常的ObservableCollection)。 items控件仅为可见的项创建UI。它被称为UI虚拟化,因为只有UI被虚拟化。仅创建未呈现的项目的视图,并且将其推迟到用户实际滚动到项目的时刻。列表中的视图模型对象都是预先创建的。因此,如果我要列出100,000个人,{{1}}必须包含100,000个视图模型,无论用户何时将其滚动到视图中。

在我们的应用程序中,我们希望实现它,以便视图模型层是此虚拟化的一部分。我们希望items控件显示一个滚动条,该滚动条适合可能加载的项目总数(因此可观察集合应该使项目控件认为它已经包含100,000个项目,因此滚动条视图端口位于正确的大小),但我们希望在新项目即将进入视图时通知可观察集合,以便它可以从服务器加载实际对象。我们希望能够在加载的项目中显示某种进度指示器,然后在将其加载到可观察集合中后立即将其替换为项目的实际数据模板。

尽可能地,我们希望维护MVVM指南,但性能和响应能力是首要任务。如果可能的话,我们也更喜欢可重复使用的解决方案。

解决这个问题的最佳方法是什么?

3 个答案:

答案 0 :(得分:3)

实际上,WinRT ItemsControls已经能够使用两种方式处理数据虚拟化: 1)在实现IList或IObservableVector的自定义类中实现ISumportIncrementalLoading,或者从ObservableCollection继承。这种方法非常简单,但它只支持线性滚动(您无法跳过数据从第一个元素立即滚动到第100个元素),并且每次加载新的项目页面时滚动条会自行调整大小

2)自己实现IObservableVector,第一次访问项时,只返回null并开始加载过程。加载后,您可以引发VectorChanged事件,指示该项目不再为null。这实现起来要复杂得多(很难依赖现有的ObservableVector实现),但它支持非线性滚动,甚至可以添加逻辑来卸载控件未访问的项目很长一段时间(因此节省了内存并仅按需重新加载)。

答案 1 :(得分:3)

我最终根据Simon Ferquels的指导方针制定了POC。我在这里添加代码以供将来参考。

        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);
    }

一些注意事项:

  1. 虽然向量是T的可枚举,但它实现的接口是IObservableVector。原因是由于某些原因,WinRt项控件不会监听任何IObservableVector<T>,仅适用于IObservableVector<object>。伤心但真实......
  2. 虚拟化vercor将项目供应商作为参数并使用 它为了查询虚拟列表中的项目数, 以及物品本身。它还允许物品供应商 删除后再次访问它们时刷新项目 来自缓存
    1. 此类是针对特定场景编写的,其中内存不是问题,但时间是。它确实保留了已经创建的缓存项目列表,但它会延迟创建,直到第一次访问它们为止。

答案 2 :(得分:0)

如果您正在处理远程和/或异步后端,并且可能有不同的分页安排,那么这里有什么值得我回答这个问题: CodeProject article on Rx and IObservableVector