カスタム並べ替えを使用して項目を再並べ替える WPF ListCollectionView の原因は何ですか?

StackOverflow https://stackoverflow.com/questions/601864

  •  03-07-2019
  •  | 
  •  

質問

次のコードを考えてみましょう (型名は例として一般化されています)。

// Bound to ListBox.ItemsSource
_items = new ObservableCollection<Item>();

// ...Items are added here ...

// Specify custom IComparer for this collection view
_itemsView = CollectionViewSource.GetDefaultView(_items)
((ListCollectionView)_itemsView).CustomSort = new ItemComparer();

設定したとき CustomSort, 、コレクションは期待どおりに並べ替えられています。

ただし、プロパティの変更に応じて、実行時にデータ自体を再並べ替える必要があります。 Item. 。の Item クラスの派生元 INotifyPropertyChanged データ テンプレートが画面上の値を更新すると、プロパティが正しく起動することがわかります。並べ替えロジックが呼び出されないだけです。

私も育ててみました INotifyPropertyChanged.PropertyChanged 空の文字列を渡して、一般的な通知によって並べ替えが開始されるかどうかを確認します。バナナはありません。

編集 Kent の提案に応えて、これを使用してアイテムを並べ替えても同じ結果が得られることを指摘しようと思いました。つまり、コレクションは 1 回並べ替えられますが、 ではない データの変更に応じて再ソートします。

_itemsView.SortDescriptions.Add(
    new SortDescription("PropertyName", ListSortDirection.Ascending));
役に立ちましたか?

解決

見つけました 博士によるこの記事。WPF 私の質問に対する答えから始まり、次に通話によるパフォーマンスへの影響について説明します。 Refresh. 。いくつかの重要な抜粋:

残念ながら、Refresh() メソッドではビューが完全に再生成されます。Refresh() がビュー内で発生すると、CollectionChanged 通知が生成され、アクションが「Reset」として提供されます。ListBox の ItemContainerGenerator はこの通知を受信し、アイテムの既存のビジュアルをすべて破棄して応答します。その後、新しいアイテム コンテナとビジュアルが完全に再生成されます。

次に、パフォーマンスを向上させるハッキングな回避策について説明します。Refresh を呼び出すのではなく、項目を削除、変更してから再度追加します。

私は、リスト ビューが変更された項目を追跡し、ビュー内でその項目だけを再配置することを認識できる可能性があると考えていました。

新しいアプローチが導入されました .NET 3.5 SP1 インターフェースに関わるもの IEditableObject メソッドを使用したテンプレートへのデータ バインディングを介したトランザクション編集を提供します。 BeginEdit(), CancelEdit(), 、 そして EndEdit(). 。読む 記事 詳細については。

編集 として user346528 が指摘する, IEditableObject 実際には 3.5SP1 では新しいものではありませんでした。実際にはフレームワークに入っているように見えます 1.0以降.

他のヒント

受け入れられた答えが私に指示するように、私はコードで単一のアイテムの再配置を強制することができます

IEditableCollectionView collectionView = DataGrid.Items;

collectionView.EditItem(changedItem);
collectionView.CommitEdit();

changedItem viewモデル ItemsSource コレクションのアイテム)である場合。

この方法では、 IEditableObject などのインターフェースを実装するためにアイテムを必要としません(これは非常に複雑で、場合によってはコントラクトを実装するのが困難です)。

古い投稿をバンプしますが、ListViewCollectionを継承してOnPropertyChangedをオーバーライドする新しいコレクションクラスを作成します(IBindingListの場合、ListChangedイベントにはListChangedEventArgsパラメーターにプロパティの変更が含まれます)。そして、コレクション内のアイテムが、プロパティが変更されたとき(あなたによって発生)にINotifyPropertyChangeを実装および使用するか、コレクションがプロパティの変更にバインドしないことを確認します。

このOnPropertyChangedメソッドでは、送信者がアイテムになります。リゾートを引き起こすプロパティが変更された場合にのみアイテムを削除し、それを再追加します(追加してもまだ実行されていない場合は、ソートされた位置に挿入します)。アイテムを削除/追加する代わりに、使用可能な場合は、アイテムを移動することをお勧めします。同様に、これもフィルタリングで行う必要があります(フィルター述語を確認します)。

IEditableObjectは必要ありません!複数のプロパティを編集したい場合は、編集を終了し(3つのプロパティを編集してからWinForm DataGridViewの別の行を選択するなど)、並べ替えを行うと、これが機能する適切な方法になります。ただし、多くの場合、BeginEdit / EndEditを手動で呼び出すことなく、各プロパティを変更した後にコレクションを再ソートすることをお勧めします。 IEditableObject(btw)は.NET 2.0フレームワークに存在し、.NET 3.5の新機能ではありません(博士の記事を読んだ場合)。

注:ブール値を設定する代わりに整数をインクリメント(true)/デクリメント(false)しない限り、BeginEdit()およびEndEdit()を使用して同じアイテムを複数回編集すると問題が発生する可能性があります!編集がいつ終了するかを正確に知るために、必ず整数をインクリメント/デクリメントしてください。

永続的にソートされたリストを保持することは時間がかかり、エラーが発生しやすく(ソートされた挿入を強制すると挿入コントラクトを破壊する可能性があります)、ComboBoxesなどの特定の場所でのみ使用する必要があります。グリッドでは、これは非常に悪い考えです。行を変更すると、ユーザーの現在の位置の下から行が再配置されるためです。

Public Class ListView
  Inherits ListCollectionView

  Protected Overrides Sub OnPropertyChanged(sender As Object, e As PropertyChangedEventArgs)

    ' Add resorting/filtering logic here.
  End Sub
End Class

必要なものに似たコレクションの最良の例はJesse Johnsons ObjectListViewですが、これは.NET 2.0固有(INotifyCollectionChanged / ObservableCollection / ListCollectionViewではなくIBindingList)であり、非常に制限的なライセンスを使用します。彼のブログは、彼がこれをどのように達成したかについて、まだ非常に価値があるかもしれません。

編集:

Senderが再ソートする必要があるアイテムになることを追加するのを忘れていました。e.PropertyNameは、SortDescriptions内にあるかどうかを判断するために使用する必要があるものです。そうでない場合、そのプロパティを変更しても、リゾートは必要ありません。 e.PropertyNameがNothingの場合、多くのプロパティが変更されている可能性があり、再ソートを行う必要がある更新のようになります。

フィルターが必要かどうかを判断するには、FilterPredicateを実行し、必要に応じて削除します。フィルタリングはソートよりもはるかに安価です。

ご協力ありがとうございます

TamusJRoyce

カスタムソートを使用していることを考えると、 ListCollectionView が更新をトリガーする基準を知る方法はありません。したがって、コレクションビューで Refresh()を自分で呼び出す必要があります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top