Как выбрать все в виртуальном ListView WinForms? - PullRequest
4 голосов
/ 28 января 2012

Каков правильный / управляемый путь к SelectAll в списке .NET в виртуальном режиме ?

Когда в ListView включен VirtualMode, понятие выбора ListViewItem исчезает. Единственное, что вы выбираете - это индексы . Они доступны через свойство SelectedIndices.

Обходной путь # 1

Первый способ - добавить итеративно добавить каждый индекс в коллекцию SelectedIncides:

this.BeginUpdate();
try
{
    for (int i = 0; i < this.VirtualListSize; i++)
        this.SelectedIndices.Add(i);
}
finally     
{
    this.EndUpdate();
}

Помимо того, что он плохо спроектирован (занятый цикл насчитывает до ста тысяч), он плохо работает ( он генерирует событие OnSelectedIndexChanged каждую итерацию ). Учитывая, что просмотр списка находится в виртуальном режиме, вполне разумно ожидать, что в списке будет немало элементов.

Обходной путь # 2

Элемент управления Windows ListView полностью способен выбирать все элементы одновременно. Отправка списка * сообщением LVM_SETITEMSTATE, сообщив об этом так, чтобы «выбрать» все элементы .:

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

Это работает достаточно хорошо. Это происходит мгновенно, и самое большее только два события происходят:

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);
    }
}

Но это зависит от взаимодействия P / Invoke. Это также основывается на том факте, что .NET ListView является оболочкой для элемента управления Windows ListView. Это не всегда так.

Так что я надеюсь, что правильный, управляемый способ SelectAll - это .NET WinForms ListView.

Бонусная болтовня

Нет необходимости прибегать к P / Invoke для отмены выбора всех элементов в списке:

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

управляемый эквивалент такой же быстрый:

listView.SelectedIndices.Clear();

Чтение бонусов

...