4

ViewModel インスタンスのツリーにバインドされた TreeView があります。問題は、モデル データが低速のリポジトリから取得されているため、データの仮想化が必要なことです。ノードの下のサブ ViewModel のリストは、親ツリー ビュー ノードが展開されている場合にのみロードされ、折りたたまれている場合はアンロードされる必要があります。

MVVM の原則に準拠しながら、これをどのように実装できますか? サブノードをロードまたはアンロードする必要があることを ViewModel に通知するにはどうすればよいですか? それは、ツリービューの存在について何も知らずにノードが展開または折りたたまれたときですか?

MVVMではデータの仮想化がうまくいかないような気がします。データ仮想化では、ViewModel は通常、UI の現在の状態について多くのことを知る必要があり、UI の多くの側面を制御する必要もあります。別の例を見てみましょう:

データ仮想化を備えたリストビュー。ViewModel は、モデル内のアイテムの数に依存するため、ListView のスクロールサムの長さを制御する必要があります。また、ユーザーがスクロールするとき、ViewModel は、リポジトリからモデル データの適切な部分をロードできるように、スクロールした位置とリストビューの大きさ (現在収まる項目の数) を認識する必要があります。

4

2 に答える 2

6

これを解決する簡単な方法は、アイテムをフェッチ/作成するためのアルゴリズムとともに、そのアイテムへの弱い参照を維持する「仮想化コレクション」実装を使用することです。このコレクションのコードはかなり複雑で、必要なすべてのインターフェイスと、ロードされたデータの範囲を効率的に追跡するためのデータ構造がありますが、インデックスに基づいて仮想化されたクラスの部分的な API を次に示します。

public class VirtualizingCollection<T>
  : IList<T>, ICollection<T>, IEnumerable<T>,
    IList, ICollection, IEnumerable,
    INotifyPropertyChanged, INotifyCollectionChanged
{
  protected abstract void FetchItems(int requestedIndex, int gapStartIndex, int gapEndIndex);
  protected void RecordFetchedItems(int startIndex, int count, IEnumerable items) ...
  protected void RecordInsertOrDelete(int startIndex, int countPlusOrMinus) ...
  protected virtual void OnCollectionChanged(CollectionChangedEventArgs e) ...
  protected virtual void Cleanup();
}

ここでの内部データ構造は、データ範囲のバランスの取れたツリーであり、各データ範囲には開始インデックスと弱い参照の配列が含まれています。

このクラスは、実際にデータをロードするためのロジックを提供するためにサブクラス化されるように設計されています。仕組みは次のとおりです。

  • サブクラスのコンストラクターで、RecordInsertOrDeleteコレクションの初期サイズを設定するために呼び出されます
  • を使用して項目にアクセスするIList/ICollection/IEnumerableと、ツリーを使用してデータ項目が検索されます。ツリーで見つかったときに弱い参照があり、その弱い参照がまだライフ オブジェクトを指している場合は、そのオブジェクトが返されます。それ以外の場合は、読み込まれて返されます。
  • アイテムをロードする必要がある場合、インデックス範囲は、次/前の既にロードされたアイテムのデータ構造を前後に検索することによって計算されFetchItems、サブクラスがアイテムをロードできるように抽象が呼び出されます。
  • サブクラスのFetchItems実装では、項目がフェッチされRecordFetchedItems、新しい項目で範囲のツリーを更新するために呼び出されます。隣接するノードをマージしてツリーが大きくなりすぎないようにするには、ある程度の複雑さが必要です。
  • サブクラスが外部データの変更の通知を受け取ると、呼び出しRecordInsertOrDeleteてインデックス追跡を更新できます。これにより、開始インデックスが更新されます。挿入の場合、これは範囲を分割することもあり、削除の場合、1 つまたは複数の範囲を小さく再作成する必要がある場合があります。IListおよびIList<T>インターフェイスを介してアイテムが追加/削除されるときに、これと同じアルゴリズムが内部的に使用されます。
  • このCleanupメソッドはバックグラウンドで呼び出され、範囲のツリーと破棄WeakReferences可能な範囲全体、およびあまりにもまばらな範囲 (たとえばWeakReference、1000 スロットの範囲に 1 つだけ)をインクリメンタルに検索します。

FetchItemsヒューリスティックを使用して一度に複数のアイテムをロードできるように、アンロードされたアイテムの範囲が渡されることに注意してください。このような単純なヒューリスティックは、次の 100 個のアイテムをロードするか、現在のギャップの終わりまでのいずれか早い方をロードします。

を使用すると、WPF の組み込みの仮想化により、 、 などVirtualizingCollectionの適切なタイミングでデータがロードされます。の代わりに。ListBoxComboBoxVirtualizingStackPanelStackPanel

a のTreeView場合、もう 1 つの手順が必要です。HierarchicalDataTemplateセット内の a MultiBindingforItemsSourceは、実数にバインドされ、テンプレート化された親にItemsSourceもバインドされます。IsExpandedのコンバーターは、2 番目の値 (値) が true の場合はMultiBinding最初の値 ( )を返し、それ以外の場合は null を返します。これにより、ノードを折りたたむと、コレクションの内容へのすべての参照がすぐに削除され、それらをクリーンアップできるようになります。ItemsSourceIsExpandedTreeViewVirtualizingCollection

インデックスに基づいて仮想化を行う必要はないことに注意してください。ツリー シナリオでは、全か無かの場合があり、リスト シナリオでは、推定カウントを使用し、必要に応じて「開始キー」/「終了キー」メカニズムを使用して範囲を埋めることができます。これは、基になるデータが変更される可能性があり、仮想化されたビューが画面の上部にあるキーに基づいて現在の場所を追跡する必要がある場合に役立ちます。

于 2010-01-29T19:26:35.540 に答える
-2

これを試してください。

パフォーマンスを最適化するために、VirtualizingStackPanel.IsVirtualizing 添付プロパティを true に設定し、VirtualizingStackPanel.VirtualizationMode 添付プロパティを VirtualizationMode.Recycling に設定する TreeView。

    <TreeView VirtualizingStackPanel.IsVirtualizing = "True" VirtualizingStackPanel.VirtualizationMode = "Recycling" VirtualizingStackPanel.CleanUpVirtualizedItem="TreeView_CleanUpVirtualizedItem">
            <TreeView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </TreeView.ItemsPanel>
        </TreeView>

またはこれも

 <TreeView Height="200" 
        ItemsSource="{Binding Source={StaticResource dataItems}}"
        VirtualizingStackPanel.IsVirtualizing="True"
        VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemContainerStyle>
  
  <!--Expand each TreeViewItem in the first level and 
      set its foreground to Green.-->
  <Style TargetType="TreeViewItem">
    <Setter Property="IsExpanded" Value="True"/>
    <Setter Property="Foreground" Value="Green"/>
  </Style>
</TreeView.ItemContainerStyle>
于 2015-07-19T01:21:26.397 に答える