1. ホーム
  2. c#

[解決済み] ListViewへのアイテムの追加を高速化する方法は?

2023-05-28 11:37:24

質問

WinFormsのListViewに数千(例:53,709)の項目を追加しています。

試み 1 : 13,870 ms

foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);
}

これは非常に悪い動作です。明らかな最初の修正は BeginUpdate/EndUpdate .

試み2 : 3,106 ms

listView.BeginUpdate();
foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);
}
listView.EndUpdate();

これは改善されましたが、まだ一桁以上遅いです。ListViewItemsの作成とListViewItemsの追加を分けて、実際の原因を探ってみましょう。

試み 3 : 2,631 ms

var items = new List<ListViewItem>();
foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   items.Add(item);
}

stopwatch.Start();

listView.BeginUpdate();
    foreach (ListViewItem item in items)
        listView.Items.Add(item));
listView.EndUpdate();

stopwatch.Stop()

本当にネックになるのは項目の追加です。試しに変換してみましょう。 AddRange ではなく foreach

試行4回目。 2,182 ms

listView.BeginUpdate();
listView.Items.AddRange(items.ToArray());
listView.EndUpdate();

少しはマシになった。ボトルネックになっていないことを確認しましょう。 ToArray()

試み5。 2,132 ms

ListViewItem[] arr = items.ToArray();

stopwatch.Start();

listView.BeginUpdate();
listView.Items.AddRange(arr);
listView.EndUpdate();

stopwatch.Stop();

制限はリストビューにアイテムを追加することのようです。多分、他のオーバーロードの AddRange を追加しているのでしょう。 ListView.ListViewItemCollection を追加しています。

試行錯誤6。 2,141 ms

listView.BeginUpdate();
ListView.ListViewItemCollection lvic = new ListView.ListViewItemCollection(listView);
lvic.AddRange(arr);
listView.EndUpdate();

まあそれはいいとして。

さあ、ストレッチの時間です。

  • ステップ1 - に設定されているカラムがないことを確認します。 に設定されていないことを確認してください。 :

    チェック

  • ステップ2 - リストビューが、アイテムを追加するたびにソートしようとしないことを確認します。

    チェック

  • ステップ3 - stackoverflowに問い合わせる。

    チェック

注意してください。 明らかにこのListViewは仮想モードではありません。仮想リストビューにアイテムを追加することはできないので、( VirtualListSize ). 幸いなことに、私の質問は仮想モードでのリストビューに関するものではありません。

リストビューに項目を追加するのが非常に遅いことを説明するかもしれない、私が見逃している何かがありますか?


ボーナス・チャター

WindowsのListViewクラスがもっとうまくいくことは知っています。 394 ms :

ListView1.Items.BeginUpdate;
for i := 1 to 53709 do
   ListView1.Items.Add();
ListView1.Items.EndUpdate;

であり、同等のC#のコードと比較した場合 1,349 ms :

listView.BeginUpdate();
for (int i = 1; i <= 53709; i++)
   listView.Items.Add(new ListViewItem());
listView.EndUpdate();

は一桁以上速いです。

WinForms ListView wrapperのどのプロパティが足りないのでしょうか?

どのように解決するのですか?

リストビューのソースコードを見てみたところ、あなたが見ているような 4 倍程度のパフォーマンス低下を引き起こす可能性のあるいくつかの事柄に気づきました。

を ListView.cs に追加しました。 ListViewItemsCollection.AddRange が呼び出されます。 ListViewNativeItemCollection.AddRange であり、ここから私の監査が始まりました。

ListViewNativeItemCollection.AddRange (行番号: 18120) は値のコレクション全体を 2 回通過します。1 回はチェックされた項目をすべて収集するため、もう 1 回は InsertItems が呼ばれた後にそれらを「復元」するためです (これらは両方とも owner.IsHandleCreated であること、オーナーは ListView ) を呼び出すと BeginUpdate .

ListView.InsertItems (from line: 12952), 最初の呼び出しは、リスト全体の別のトラバースがあり、その後ArrayList.AddRangeが呼び出され(おそらくそこで別のパス)、その後別のパスがあります。次のようになります。

ListView.InsertItems (行番号: 12952 から)、2回目の呼び出し(via EndUpdate を経由して)もう一回通すと、それらは HashTable に追加され、さらに Debug.Assert(!listItemsTable.ContainsKey(ItemId)) はデバッグモードではさらに遅くなります。ハンドルが作成されていない場合は、項目を ArrayList , listItemsArray しかし if (IsHandleCreated) を呼び出すと

ListView.InsertItemsNative (行: 3848 から) リストを通過する最後のパスで、実際にネイティブのリストビューに追加されます。 Debug.Assert(this.Items.Contains(li) はさらにデバッグモードでのパフォーマンスを低下させます。

そのため、ネイティブのリストビューに実際にアイテムを挿入する前に、.net コントロールでアイテムのリスト全体を通して多くの余分なパスがあります。 パスの一部は、作成されるハンドルに対するチェックによって保護されているため、ハンドルが作成される前にアイテムを追加できれば、時間を節約できる可能性があります。 その OnHandleCreated メソッドは listItemsArray を受け取り InsertItemsNative を直接呼び出します。

を読むことができます。 ListView のコードを 参照元 を自分で書いて見てください。何か見落としがあるかもしれません。

MSDN マガジンの 2006 年 3 月号で という記事がありました。 Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps .

この記事には、ListViewのパフォーマンスを向上させるためのヒントなどが書かれていました。 ハンドルが作成される前にアイテムを追加することはより高速ですが、コントロールがレンダリングされるときに代償を払うことになることを示しているようです。 おそらく、コメントで言及されているレンダリングの最適化を適用し、ハンドルが作成される前にアイテムを追加すると、両方の長所が得られるでしょう。

編集: この仮説をさまざまな方法でテストしてみましたが、ハンドルを作成する前にアイテムを追加するのは超高速ですが、ハンドルを作成するときは指数関数的に遅くなります。ハンドルを作成し、余分なパスを通さずにInsertItemsNativeを呼び出すようなトリックを試してみましたが、残念ながら失敗しました。 唯一可能性があると思われるのは、c++プロジェクトでWin32 ListViewを作成し、それにアイテムを詰め込み、ハンドルを作成するときにListViewによって送信されるCreateWindowメッセージを捕捉するフックを使用して、新しいウィンドウの代わりにWin32 ListViewへの参照を返すことです。)