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"に設定されている列がないことを確認します。
チェック
ステップ2-リストビューがアイテムを追加するたびにアイテムを並べ替えようとしていないことを確認します。
チェック
ステップ-stackoverflowに問い合わせます:
チェック
注:明らかにこの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ラッパーのどのプロパティがありませんか?
リストビューのソースコードを見てみると、パフォーマンスが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))
が遅くなりますさらにデバッグモードで。ハンドルが作成されない場合、ArrayList
、listItemsArray
に項目を追加しますが、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の第一人者がそのクレイジーなアイデアについて話す必要があります:)
私はこのコードを使用しました:
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
これは、列で構成されるリストボックスに項目を追加するために構築できた簡単なコードです。最初の列はアイテムで、2番目の列は価格です。以下のコードは、最初の列にアイテムシナモンを、2番目の列に0.50を印刷します。
// How to add ItemName and Item Price
listItems.Items.Add("Cinnamon").SubItems.Add("0.50");
インスタンス化は必要ありません。
私は同じ問題を抱えています。それからsorter
であることがわかりました。ソーターをヌルにする
this.listViewAbnormalList.ListViewItemSorter = null;
次に、ソーターをクリックすると、ListView_ColumnClick
メソッド、それを作る
lv.ListViewItemSorter = new ListViewColumnSorter()
最後に、ソート後、sorter
を再びnullにします
((System.Windows.Forms.ListView)sender).Sort();
lv.ListViewItemSorter = null;