3

WPF 和 WinRT (C# + XAML) 都支持 UI 虚拟化,使用支持它的面板VirtualizingStackPanel和其他面板。使用 MVVM 时,它是使用某种绑定到视图模型上的可枚举属性(通常是 ObservableCollection)ItemsControl的某种(ListBox、等...)来完成的。GridViewitems 控件仅为可见的项目创建 UI。之所以称为 UI 虚拟化,是因为只有 UI 是虚拟化的。只有未呈现的项目的视图不会被创建,并且会延迟到用户实际滚动到项目的那一刻。列表中的视图模型对象都是预先创建的。因此,如果我有一个包含 100,000 人的列表,那么ObservableCollection无论用户何时将它们滚动到视图中,都必须包含 100,000 个视图模型。

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

我们希望尽可能地保持 MVVM 指南,但性能和响应能力是优先考虑的因素。如果可能的话,我们也更喜欢可重复使用的解决方案。

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

4

3 回答 3

3

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

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

于 2014-09-12T08:19:12.117 回答
3

我最终根据 Simon Ferques 的指导方针做了一个 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. 此类是为特定场景编写的,其中内存不是问题,但时间是问题。它确实保留了一个已经创建的缓存项目列表,但它会延迟它们的创建,直到它们第一次被访问。
于 2014-09-17T16:07:06.797 回答
0

如果您正在处理远程和/或异步后端,可能会有不同的分页安排,那么这是我对这个问题的回答: 关于 Rx 和 IObservableVector 的 CodeProject 文章

于 2015-06-11T07:28:04.617 回答