仮想モードにしたListViewでも、SelectedIndices
プロパティ(ListView.SelectedIndexCollection
クラス)で非常に時間がかかるケースがあります。
(この記事では面倒なのでどれくらい時間がかかるかは述べません)
遅くならないケース
-
SelectedIndices.Count
内部ではLVM_GETSELECTEDCOUNT
を使って実装されているので高速に取得できます。 -
SelectedIndices[0]
1件目であれば、列挙を打ち切るため遅くはなりません。
全選択/全選択解除を早くする
以下のように、ループでSelectedIndices.Add
/Remove
でしていくと非常に時間がかかってしまいます。
NG
for (var i = 0; i < listView1.Items.Count; i++)
{
listView1.SelectedIndices.Add(i);
}
ListBoxの全項目を高速に選択すると同様に、P/Invokeで全選択するようにします。
こんなのを定義して、
const int LVM_FIRST = 0x1000;
const int LVM_SETITEMSTATE = LVM_FIRST + 43;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct LVITEM
{
public int mask;
public int iItem;
public int iSubItem;
public int state;
public int stateMask;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszText;
public int cchTextMax;
public int iImage;
public IntPtr lParam;
public int iIndent;
public int iGroupId;
public int cColumns;
public IntPtr puColumns;
};
private const int LVIS_SELECTED = 2;
public static void SelectAll(ListView control, bool select)
{
var lvItem = new LVITEM
{
stateMask = LVIS_SELECTED,
state = select ? LVIS_SELECTED : 0
};
SendMessage(new HandleRef(control, control.Handle), LVM_SETITEMSTATE, new UIntPtr(unchecked((uint)-1)), ref lvItem);
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SendMessage(HandleRef hWnd, int msg, UIntPtr wParam, ref LVITEM lvi);
こう使います。
// 全選択
SelectAll(listview1, true);
// 選択解除
SelectAll(listview1, false);
選択行の取得を早くする
SelectedIndices
に対して列挙すると、内部では1件目から全部舐めてリストを作ることになるので、非常に遅くなります。
(foreach
で回さなくても、CopyTo
メソッドなども含む)
大きなインデックスでインデックスアクセスしても同様にその位置まで1件進めるのに時間がかかります。
NG
foreach (int i in listView1.SelectedIndices)
{
// iを使用した処理
}
そのため、VirtualItemsSelectionRangeChanged
とItemSelectionChanged
イベントで選択行を保持しておき、選択行が必要なときはそれを参照するようにします。
private bool[] _selected = new bool[/*listView1.VirtualListSize分のサイズ*/];
private void listView1_VirtualItemsSelectionRangeChanged(object sender, ListViewVirtualItemsSelectionRangeChangedEventArgs e)
{
for (var i = e.StartIndex; i <= e.EndIndex; i++)
{
_selected[i] = e.IsSelected;
}
}
private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
_selected[e.ItemIndex] = e.IsSelected;
}
他の方法として、
- C#を諦めてC++/CLIで同等の処理を実装する
- 別途ネイティブDLLを作成してそれをP/Invokeで呼び出す
などもあります。