web-dev-qa-db-ja.com

ListViewへのアイテムの追加を高速化する方法は?

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();
_

これは良いですが、それでも一桁も遅すぎます。 ListViewItemの作成とListViewItemの追加を分けて、実際の原因を見つけましょう。

試行:_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();
_

制限はリストビューにアイテムを追加することのようです。おそらく、配列ではなく_ListView.ListViewItemCollection_を追加するAddRangeの他のオーバーロード

試行6: _2,141 ms_

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

まあそれはましです。

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

  • ステップ1- "auto-width"に設定されている列がないことを確認します。

    enter image description here

    チェック

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

    enter image description here

    チェック

  • ステップ-stackoverflowに問い合わせます:

    enter image description here

    チェック

注:明らかにこのListViewは仮想モードではありません。仮想リストビューにアイテムを「追加」/追加できないためです(VirtualListSizeを設定します)。幸いなことに、私の質問は仮想モードのリストビューに関するものではありません。

リストビューへのアイテムの追加が非常に遅いことを説明するものがありませんか?


ボーナスチャッター

私は_394 ms_でそれを行うコードを書くことができるので、Windows ListViewクラスがより良くできることを知っています:

_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ラッパーのどのプロパティがありませんか?

80
Ian Boyd

リストビューのソースコードを見てみると、パフォーマンスが4倍ほど遅くなる可能性があることに気づきました。

listView.csでは、_ListViewItemsCollection.AddRange_が_ListViewNativeItemCollection.AddRange_を呼び出します。これが監査を開始した場所です

_ListViewNativeItemCollection.AddRange_(行:18120)には、値のコレクション全体を通る2つのパスがあり、1つはInsertItemsが呼び出された後にそれらを「復元」するためにチェックされたすべてのアイテムを収集します_owner.IsHandleCreated_に対するチェック、所有者がListView)である場合、BeginUpdateを呼び出します。

_ListView.InsertItems_(行:12952)、最初の呼び出しはリスト全体の別の走査を行い、その後ArrayList.AddRangeが呼び出され(おそらく別のパス)、その後別のパスを呼び出します。につながる

_ListView.InsertItems_(行:12952から)、2番目の呼び出し(EndUpdate経由)がHashTableに追加される別のパスを通過し、Debug.Assert(!listItemsTable.ContainsKey(ItemId))が遅くなりますさらにデバッグモードで。ハンドルが作成されない場合、ArrayListlistItemsArrayに項目を追加しますが、if (IsHandleCreated)を追加し、次に呼び出します

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

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

参照ソースListViewコードを自分で読んで見てみると、何か見逃したかもしれません。

MSDN Magazine 2006年3月号 _Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps_という記事がありました。

この記事には、特にListViewのパフォーマンスを向上させるためのヒントが含まれていました。ハンドルが作成される前にアイテムを追加する方が速いことを示しているように見えますが、コントロールがレンダリングされるときに価格を支払うことになります。おそらく、コメントに記載されているレンダリングの最適化を適用し、ハンドルを作成する前にアイテムを追加することで、両方の長所を活用できます。

編集:この仮説をさまざまな方法でテストしました。ハンドルを作成する前に項目を追加するのは非常に高速ですが、ハンドルを作成すると指数関数的に遅くなります。私はそれをだましてハンドルを作成しようとしましたが、どうにかしてすべての余分なパスを経由せずにInsertItemsNativeを呼び出すようにしましたが、残念ながら阻止されました。私が考えられる唯一のことは、c ++プロジェクトでWin32 ListViewを作成し、アイテムを詰め込み、フックを使用してハンドルを作成するときにListViewによって送信されたCreateWindowメッセージをキャプチャし、win32への参照を返すことです新しいウィンドウの代わりにリストビュー..しかし、サイドが何に影響するかを知っている人は... Win32の第一人者がそのクレイジーなアイデアについて話す必要があります:)

21
Erikest

私はこのコードを使用しました:

ResultsListView.BeginUpdate();
ResultsListView.ListViewItemSorter = null;
ResultsListView.Items.Clear();

//here we add items to listview

//adding item sorter back
ResultsListView.ListViewItemSorter = lvwColumnSorter;


ResultsListView.Sort();
ResultsListView.EndUpdate();

また、列ごとにGenerateMemberをfalseに設定しました。

カスタムリストビューソーターへのリンク: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter

8
Slav2

ListView Box Add

これは、列で構成されるリストボックスに項目を追加するために構築できた簡単なコードです。最初の列はアイテムで、2番目の列は価格です。以下のコードは、最初の列にアイテムシナモンを、2番目の列に0.50を印刷します。

// How to add ItemName and Item Price
listItems.Items.Add("Cinnamon").SubItems.Add("0.50");

インスタンス化は必要ありません。

0
Demetre Phipps

私は同じ問題を抱えています。それからsorterであることがわかりました。ソーターをヌルにする

this.listViewAbnormalList.ListViewItemSorter = null;

次に、ソーターをクリックすると、ListView_ColumnClickメソッド、それを作る

 lv.ListViewItemSorter = new ListViewColumnSorter()

最後に、ソート後、sorterを再びnullにします

 ((System.Windows.Forms.ListView)sender).Sort();
 lv.ListViewItemSorter = null;
0
Batur