3

WPF と WinRT (C# + XAML) はどちらも、 などをサポートするパネルを使用して UI 仮想化をサポートしVirtualizingStackPanelます。MVVM を使用する場合ビュー モデル (通常は ObservableCollection) の列挙可能なプロパティにバインドされたItemsControl何らかの種類 ( ListBox、など) を使用して行われます。GridViewアイテム コントロールは、表示されるアイテムに対してのみ UI を作成します。UI のみが仮想化されるため、UI 仮想化と呼ばれます。表示されていないアイテムのビューのみが作成されず、ユーザーが実際にアイテムにスクロールするまで延期されます。リスト内のビュー モデル オブジェクトはすべて事前に作成されています。したがって、提示する 100,000 人のリストがObservableCollectionある場合、ユーザーがいつスクロールして表示するかに関係なく、作成される 100,000 のビュー モデルを含める必要があります。

私たちのアプリケーションでは、ビュー モデル レイヤーがこの仮想化の一部になるように実装したいと考えています。アイテム コントロールに、潜在的にロードできるアイテムの総数に適合するスクロール バーを表示する必要があります (そのため、監視可能なコレクションにより、アイテム コントロールは既に 100,000 個のアイテムを含んでいると認識し、スクロール バー ビュー ポートがただし、サーバーから実際のオブジェクトをロードできるように、新しいアイテムが表示されようとするたびに監視可能なコレクションに通知する必要があります。読み込まれたアイテム内にある種の進行状況インジケーターを表示し、オブザーバブル コレクションに読み込まれるとすぐにアイテムの実際のデータ テンプレートに置き換えられるようにしたいと考えています。

MVVM のガイドラインを可能な限り維持したいと考えていますが、パフォーマンスと応答性が優先されます。また、可能であれば再利用可能なソリューションを優先します。

これに取り組む最善の方法は何ですか?

4

3 に答える 3

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 です。その理由は、何らかの理由で Wi​​nRt アイテム コントロールが をリッスンせずIObservableVector<T>、 のみをリッスンするためIObservableVector<object>です。悲しいけれど事実です...
  2. 仮想化バーコアはアイテム サプライヤーをパラメーターとして受け取り、それを使用して仮想リスト内のアイテム数とアイテム自体を照会します。また、アイテム サプライヤーは、キャッシュから削除された後に再度アクセスされたときにアイテムを更新できます。
    1. このクラスは、メモリが問題ではなく時間が問題である特定のシナリオ向けに記述されています。すでに作成されているキャッシュされたアイテムのリストを保持しますが、最初にアクセスされるまで作成を遅らせます。
于 2014-09-17T16:07:06.797 に答える
3

実際には、WinRT ItemsControls は既に次の 2 つの方法を使用してデータ仮想化を処理できます。1) IList または IObservableVector を実装するカスタム クラスに ISumportIncrementalLoading を実装するか、ObservableCollection から継承します。この方法は非常に簡単ですが、線形スクロールのみをサポートし (データをスキップして最初の要素から 1000000 番目の要素まで即座にスクロールすることはできません)、アイテムの新しいページが読み込まれるたびにスクロールバーのサイズが自動的に変更されます。

2) IObservableVector を自分で実装し、最初にアイテムにアクセスしたときに null を返し、読み込みプロセスを開始するだけです。読み込まれると、項目が null ではないことを示す VectorChanged イベントを発生させることができます。これを実装するのはかなり複雑ですが (そのために既存の ObservableVector 実装に依存するのは困難です)、非線形スクロールをサポートしており、コントロールによって長い間アクセスされていないアイテムをアンロードするロジックを追加することもできます。 (したがって、メモリを節約し、必要に応じてそれらを再ロードします)。

于 2014-09-12T08:19:12.117 に答える
0

価値があるのは、この質問に対する私の答えです。リモートおよび/または非同期のバックエンドを扱っている場合、ページングの配置が異なる可能性があります: Rx および IObservableVector に関する CodeProject の記事

于 2015-06-11T07:28:04.617 に答える