8

WPF で使用する仮想化ラップ パネルのオプションはそれほど多くありません。なんらかの理由で、MS は標準ライブラリに同梱しないことにしました。

次のコードプレックス プロジェクトの最初の作業項目に対するクラウド ソースの回答 (および説明) を大胆に提供できる人がいる場合は、大いに感謝します。

http://virtualwrappanel.codeplex.com/workitem/1

ありがとう!


問題の概要:

最近、このプロジェクトの仮想化ラップパネルを使用しようとしましたが、バグが発生しました。

再現する手順:

  1. リストボックスを作成します。
  2. listboxpanel テンプレートで仮想化ラップパネルを itemhost として設定します。
  3. リストボックスの itemsource を監視可能なコレクションにバインドします。
  4. バッキング監視可能なコレクションからアイテムを削除します。

Debug.Assert は MeasureOverride で (Debug.Assert(child == _children[childIndex], "Wrong child was generated");) 失敗し、実行を継続すると Cleanup メソッドで null 例外が発生します [添付のスクリーンショットを参照]。

これを修正できるかどうか教えてください。

ありがとう、

あお


コード:

http://virtualwrappanel.codeplex.com/SourceControl/list/changesets#

代替テキスト http://virtualwrappanel.codeplex.com/Project/Download/AttachmentDownload.ashx?ProjectName=virtualwrappanel&WorkItemId=1&FileAttachmentId=138959

4

3 に答える 3

8

問題の説明

何がうまくいかないかの説明と、それを修正する方法の指示を求めました。これまでのところ、誰も問題を説明していません。そうします。

VirtualizingWrapPanel を使用した ListBox には、アイテムを追跡する 5 つの個別のデータ構造があり、それぞれが異なる方法で追跡されます。

  1. ItemsSource: 元のコレクション (この場合は ObservableCollection)
  2. CollectionView: ソート/フィルター/グループ化されたアイテムの個別のリストを保持します (これらの機能のいずれかが使用されている場合のみ)
  3. ItemContainerGenerator: アイテムとコンテナ間のマッピングを追跡します
  4. InternalChildren: 現在表示されているコンテナを追跡します
  5. WrapPanelAbstraction: どのコンテナーがどの行に表示されるかを追跡します

アイテムが ItemsSource から削除された場合、この削除はすべてのデータ構造に反映される必要があります。仕組みは次のとおりです。

  1. ItemsSource で Remove() を呼び出します
  2. ItemsSource はアイテムを削除し、CollectionView によって処理されるその CollectionChanged を起動します
  3. CollectionView はアイテムを削除し (並べ替え/フィルタリング/グループ化が使用されている場合)、ItemContainerGenerator によって処理されるその CollectionChanged を起動します。
  4. ItemContainerGenerator はそのマッピングを更新し、VirtualizingPanel によって処理されるその ItemsChanged を起動します
  5. VirtualizingPanel は、VirtualizingWrapPanel によって実装される仮想 OnItemsChanged メソッドを呼び出します。
  6. VirtualizingWrapPanel はその WrapPanelAbstraction を破棄するためビルドされますが、InternalChildren は更新されません。

このため、InternalChildren コレクションは他の 4 つのコレクションと同期されず、発生したエラーにつながります。

問題の解決策

この問題を解決するには、次のコードを VirtualizingWrapPanel の OnItemsChanged メソッド内の任意の場所に追加します。

switch(args.Action)
{ 
    case NotifyCollectionChangedAction.Remove: 
    case NotifyCollectionChangedAction.Replace: 
        RemoveInternalChildRange(args.Position.Index, args.ItemUICount); 
        break; 
    case NotifyCollectionChangedAction.Move: 
        RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount); 
        break; 
} 

これにより、InternalChildren コレクションが他のデータ構造と同期されます。

ここで AddInternalChild/InsertInternalChild が呼び出されない理由

上記のコードで InsertInternalChild または AddInternalChild の呼び出しがない理由、特に、Replace と Move を処理するために OnItemsChanged 中に新しい項目を追加する必要がない理由を不思議に思うかもしれません。

これを理解する鍵は、ItemContainerGenerator の動作にあります。

ItemContainerGenerator が remove イベントを受け取ると、すぐにすべてを処理します。

  1. ItemContainerGenerator は、独自のデータ構造からアイテムをすぐに削除します
  2. ItemContainerGenerator は ItemChanged イベントを発生させます。パネルはすぐにコンテナを削除することが期待されています。
  3. ItemContainerGenerator は、その DataContext を削除することによってコンテナーを「準備解除」します

一方、ItemContainerGenerator は、アイテムが追加されると、通常はすべてが延期されることを学習します。

  1. ItemContainerGenerator は、そのデータ構造にアイテムの「スロット」をすぐに追加しますが、コンテナは作成しません
  2. ItemContainerGenerator は ItemChanged イベントを発生させます。パネルは InvalidateMeasure() を呼び出します [これは基本クラスによって行われます - 行う必要はありません]
  3. 後で MeasureOverride が呼び出されると、Generator.StartAt/MoveNext を使用してアイテム コンテナーが生成されます。新しく生成されたコンテナは、その時点で InternalChildren に追加されます。

したがって、InternalChildren コレクションからのすべての削除 (Move または Replace の一部であるものを含む) は OnItemsChanged 内で行う必要がありますが、追加は次の MeasureOverride まで延期することができます (また、延期する必要があります)。

于 2010-08-18T21:09:07.017 に答える
4

OnItemsChanged メソッドは、args パラメータを適切に処理する必要があります。詳細については、この質問を参照してください。その質問からコードをコピーすると、次のように OnItemsChanged を更新する必要があります。

protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args) {
    base.OnItemsChanged(sender, args);
    _abstractPanel = null;
    ResetScrollInfo();

    // ...ADD THIS...
    switch (args.Action) {
        case NotifyCollectionChangedAction.Remove:
        case NotifyCollectionChangedAction.Replace:
            RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
            break;
        case NotifyCollectionChangedAction.Move:
            RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount);
            break;
    }
}
于 2010-08-18T19:35:26.570 に答える