Aho Aho - 14 days ago 10
C++ Question

How to force a CListCtrl to always have one item selected?

I want a CListCtrl to always have a selected item, like a collection of radio buttons.

I have used the styles:

LVS_SHOWSELALWAYS|LVS_SINGLESEL


I have been looking for a style for this, but haven't been able to find this.

Answer

I don't think there's built-in support for this.

The part of having a selection from the start is easy: just select an item after populating the list:

// Populate the list
// ...

c_MyList.SetItemState(nItem, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED);

The other part, preventing the deselection of all items when the user clicks outside all the items, is trickier. You can easily detect if the list is deselecting an item, but not why the item is losing the selected state. I.e. you can't tell if it is deselecting it to select another one or to leave all the items unselected. The reason is the control first sends the "item X has been deselected" notification, then the "item Y has been selected" notification. If no item is selected, you get the first one, but not the second one.

A little idea I've thought of is to catch NM_CLICK notifications and prevent the control from deselecting the item. The problem is NM_CLICK is sent after all the select/deselect notifications.

So, this is the little hack I've come up with: when an item loses the selected status, store its item index. Then in the NM_CLICK notification, if the activated item is -1, select again the last unselected item:

void CMyDialog::OnLvnItemchangedListaEjesPane(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);

    if (pNMLV->uChanged & LVIF_STATE)
    {
        UINT oldSelectionState = (pNMLV->uOldState & LVIS_SELECTED);
        UINT newSelectionState = (pNMLV->uNewState & LVIS_SELECTED);

        if ( oldSelectionState == LVIS_SELECTED && newSelectionState == 0 )
        {   // Deselect item
            m_LastDeselectedItem = pNMLV->iItem;
        }
        // ...
    }

    *pResult = 0;
}

void CMyDialog::OnNMClickListaEjesPane(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);

    if (pNMItemActivate->iItem == -1)
    {
        c_ListaEjes.SetItemState(m_LastDeselectedItem, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED);
    }
    *pResult = 0;
}

It may not suit your needs fully, because it actually deselects the item then selects it again.

There are probably better solutions, involving creating your own CListCtrl subclass and intercepting the clicks, checking if they are going to force a null selection and preventing it, but you have to decide if the extra work is worth the trouble.