4

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

Community
  • 1
  • 1
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • Another person who is afraid of P/Invoke? I just don't get it. The support for this is so extensive in the .NET Framework at least in part because they *knew* it wasn't a complete wrapper around the Win32 API and was never intended to be. When you start doing things that are outside of the typical 80% cases (and virtual mode list views definitely qualify), you'll need to drop to a lower level. You already know how to do it at the Win32 level, the WinForms control wraps the Win32 control, bingo bango problem solved. – Cody Gray - on strike Jan 27 '12 at 21:07
  • 1
    @CodyGray Which is fine, except when the ListView is not backed by a Win32 ListView (e.g. http://msdn.microsoft.com/en-us/library/hf2k718k.aspx). i also want to be a *good* developer, and not be the ***cause*** of compatibility problems. – Ian Boyd Jan 27 '12 at 22:07
  • Are you targeting the compact framework? If not, then you know that it's going to be backed by a Win32 ListView. That much is a contract, guaranteed in the documentation as I read it. I very much sympathize with the desire to be a good developer and pay your taxes, but I don't think this is an instance of that. (I don't think this deserves a downvote though; it's still a reasonable question, even if I think you're vastly overthinking the problem.) – Cody Gray - on strike Jan 30 '12 at 05:57
  • @CodyGray i would certainly agree that the WinForms `ListView` on Win32 is a lost cause. So many people P/Invoke to the undocumented internal control behind it that if Microsoft were to upgrade it to the WPF listview that a lot of existing code would break. But i certainly don't want to contribute to the world of poor developers who cause compatibility problems. The counter-argument is, "Well if Microsoft would have just provided the functionality we needed there would be no need to P/Invoke", which is quite true for Clearing, or setting the column sort glyph. – Ian Boyd Jan 30 '12 at 18:06
  • (Time passes.) How's the compatibility with WinForms on Linux? http://www.mono-project.com/docs/gui/winforms/ – fadden Jun 28 '18 at 21:52
  • 1
    To put some numbers on things: individually selecting 554,253 lines takes about 24 seconds on my Win10 x64 system. Using P/Invoke to select all takes 4 milliseconds. The latter also causes a single VirtualItemsSelectionRangeChanged event, instead of one ItemSelectionChanged event per line. (I'm tracking the selection state separately because it takes 2.5 seconds to query the individual selection states with a foreach loop.) – fadden Jul 10 '18 at 00:37

0 Answers0