What is the correct/managed way to SelectAll
in a .NET listview that is in virtual mode?
When a ListView's VirtualMode
is enabled, the notion of selecting a ListViewItem
goes away. The only thing you select are indexes. These are accessible through the SelectedIndices
property.
Workaround #1
The first hack is to add iteratively add every index to the SelectedIncides
collection:
this.BeginUpdate();
try
{
for (int i = 0; i < this.VirtualListSize; i++)
this.SelectedIndices.Add(i);
}
finally
{
this.EndUpdate();
}
In addition to being poorly designed (a busy loop counting to a hundred thousand), it's poorly performing (it throws an OnSelectedIndexChanged
event every iteration). Given that the listview is in virtual mode, it is not unreasonable to expect that there will be quite a few items in the list.
Workaround #2
The Windows ListView
control is fully capable of selecting all items at once. Sending the listview a LVM_SETITEMSTATE
message, telling it so "select" all items.:
LVITEM lvi;
lvi.stateMask = 2; //only bit 2 (LVIS_SELECTED) is valid
lvi.state = 2; //setting bit two on (i.e. selected)
SendMessage(listview.Handle, LVM_SETITEMSTATE, -1, lvi); //-1 = apply to all items
This works well enough. It happens instantly, and at most only two events are raised:
class NativeMethods
{
private 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;
};
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageLVItem(HandleRef hWnd, int msg, int wParam, ref LVITEM lvi);
/// <summary>
/// Select all rows on the given listview
/// </summary>
/// <param name="listView">The listview whose items are to be selected</param>
public static void SelectAllItems(ListView listView)
{
NativeMethods.SetItemState(listView, -1, 2, 2);
}
/// <summary>
/// Set the item state on the given item
/// </summary>
/// <param name="list">The listview whose item's state is to be changed</param>
/// <param name="itemIndex">The index of the item to be changed</param>
/// <param name="mask">Which bits of the value are to be set?</param>
/// <param name="value">The value to be set</param>
public static void SetItemState(ListView listView, int itemIndex, int mask, int value)
{
LVITEM lvItem = new LVITEM();
lvItem.stateMask = mask;
lvItem.state = value;
SendMessageLVItem(new HandleRef(listView, listView.Handle), LVM_SETITEMSTATE, itemIndex, ref lvItem);
}
}
But it relies on P/Invoke interop. It also relies on the truth that the .NET ListView
is a wrapper around the Windows ListView
control. This is not always true.
So i'm hoping for the correct, managed, way to SelectAll
is a .NET WinForms ListView.
Bonus Chatter
There is no need to resort to P/Invoke in order to deselect all items in a listview:
LVITEM lvi;
lvi.stateMask = 2; //only bit 2 (LVIS_SELECTED) is valid
lvi.state = 1; //setting bit two off (i.e. unselected)
SendMessage(listview.Handle, LVM_SETITEMSTATE, -1, lvi); //-1 = apply to all items
the managed equivalent is just as fast:
listView.SelectedIndices.Clear();
Bonus Reading
- Ian Boyd wonders how to
SelectAll
items of a non-virtualListView
- Making "select all" faster definitely helps someone who wants to add Ctrl+A support