ListViewの高速化
.Net
のListView
に大量のデータを表示しようとすると時間が掛かってしまいます。
そこでいくつかある高速化の手段の1つとして、仮想化モード
を紹介します。
仮想モードのすごいところ
仮想モードではすべてをデータを描画するのではなく、見えているものだけを描画
します。
なので、非常に軽量です。
どのくらい遅いのか(動画)
軽い対策のものを載せていきます。
- 1行ずつAdd()
- BeginUpdate()とEndUpdate()を導入
- AddRange()を導入
1行ずつAdd()
これは対策なしです。
ただひたすら1行ずつAdd()
していく
遅すぎるので、表示する行数は10000にしています。(他は100000です。)
約5.73秒でした。
foreach (ListViewItem listViewItem in this.listViewItemList)
{
this.listView1.Items.Add(listViewItem);
}
BeginUpdate()
とEndUpdate()
を導入
描画を止めておく関数BeginUpdate()
とEndUpdate()
を導入する
件数は100000件です。
約3.71秒でした。
this.listView1.BeginUpdate();
foreach (ListViewItem listViewItem in this.listViewItemList)
{
this.listView1.Items.Add(listViewItem);
}
this.listView1.EndUpdate();
AddRange()
に置き換え
データ追加をAddRange()
でまるごとに追加します。
件数は100000件です。
約2.57秒でした。
this.listView1.BeginUpdate();
this.listView1.Items.AddRange(this.listViewItemList.ToArray());
this.listView1.EndUpdate();
仮想モードだとどのくらい早いのか(動画)
できなくなること
仮想モードを使用することで、できなくなることがあります。
これは選択されている項目がわからなくなるということです。
仮想モードがオン
とlistView1.SelectedItems
がアクセスできなるなります。
しかし選択されたインデックスlistView1.SelectedIndices
は利用可能なので、そちらをうまく使いましょう。
仮想モードでもできること
データの更新
, 選択された項目のデータ取得
, Insert
, Remove
です。
以下のコードで実装されているので、躓いた際には参考にしてください。
仮想モードの実装コード
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;
namespace TestTask
{
public partial class Form1 : Form
{
List<ListViewItem> listViewItemList;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// ListViewに仮想モードでデータを更新するイベントを追加
this.listView1.RetrieveVirtualItem += new RetrieveVirtualItemEventHandler(listView1_RetrieveVirtualItem);
// 仮想モードをオンに
this.listView1.VirtualMode = true;
}
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
e.Item = this.listViewItemList[e.ItemIndex];
}
private void data1_Click(object sender, EventArgs e)
{
this.setDate("Hello");
this.start();
}
private void data2_Click(object sender, EventArgs e)
{
this.setDate("こんにちは");
this.start();
}
private void start()
{
// 1回仮想モードに表示されるデータ数を0にすることで、別のデータを入れたときに全て更新されるようになる
this.listView1.VirtualListSize = 0;
this.listView1.VirtualListSize = this.listViewItemList.Count;
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
ListView.SelectedIndexCollection selectedIndices = this.listView1.SelectedIndices;
foreach(int index in selectedIndices)
{
string text = this.listView1.Items[index].Text;
this.label1.Text = "選択したのは" + text + "です";
}
}
private void insert_Click(object sender, EventArgs e)
{
// 追加
ListViewItem listViewItem = new ListViewItem();
listViewItem.Text = "追加された項目";
this.listViewItemList.Insert(10, listViewItem);
// 仮想モードの表示する数が一つ増えたので更新
this.listView1.VirtualListSize = this.listViewItemList.Count;
this.label1.Text = "10番目にInsertしました";
}
private void remove_Click(object sender, EventArgs e)
{
// 選択された項目を削除
ListView.SelectedIndexCollection selectedIndices = this.listView1.SelectedIndices;
foreach (int index in selectedIndices)
{
ListViewItem deleteItem = this.listView1.Items[index];
this.listViewItemList.Remove(deleteItem);
this.label1.Text = index + "番目の[" + deleteItem.Text + "]が削除されました";
}
// 仮想モードの表示する数が一つ減ったので更新
this.listView1.VirtualListSize = this.listViewItemList.Count;
}
private void setDate(string greeting)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
List<ListViewItem> listViewItemList = new List<ListViewItem>();
for (int i = 0; i < 100000; i++)
{
ListViewItem listViewItem = new ListViewItem();
listViewItem.Text = greeting + i.ToString();
listViewItemList.Add(listViewItem);
}
this.listViewItemList = listViewItemList;
stopWatch.Stop();
string time = stopWatch.Elapsed.TotalSeconds.ToString();
this.label1.Text = "データ生成に" + time + "秒掛かりました。";
}
}
}