Codename_DJ
Codename_DJ

Reputation: 563

only single selection in CListCtrl Checkbox in MFC

In a MFC dialog, I have used a CListCtrl with checkbox. I want to disable multi checkbox selection , so that user can only select a single checkbox at a time. What is the best way to achieve this.I have done this

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


        int nSelectedItemIndex = -1;
        nSelectedItemIndex = m_ListCtrl.GetNextItem(-1, LVNI_SELECTED);
        int nCount = m_ListCtrl.GetItemCount();    
        for(int nItem = 0; nItem < nCount; nItem++)
        {
            m_ListCtrl.SetCheck(nItem,false);
        }
        if(nSelectedItemIndex != -1)
            m_ListCtrl.SetCheck(nSelectedItemIndex,true);

        *pResult = 0;
    }

Somehow I think this method is not so proper and can be made better in other way. All suggesions are welcomed.

EDIT: UPDATE: after writing the code , everything is working but I am facing a new problem. calling SetCheck() function inside OnItemChanged message handler function, it is calling the same function again, creating a recursion.Thus selection change is somehow slow. How to avoid this.Please help. ????

Upvotes: 1

Views: 4645

Answers (3)

Ionel POP
Ionel POP

Reputation: 833

I found a shorter solution. The idea is to detect if the user checked a checkbox. If it is the case, unchecked all the other checboxes, otherwise do nothing. Contrary to the answer by @egur, this solution does not check the checkbox upon selection of the row.

static void DisableAllItemsExcept(CListCtrl& ctrl, int indexToKeepChecked)
{
    for (int nItem = 0; nItem < ctrl.GetItemCount(); nItem++)
        if (nItem != indexToKeepChecked)
            ctrl.SetCheck(nItem, FALSE);
}

//in begin message map
//ON_NOTIFY(LVN_ITEMCHANGED, IDC_SC_LIST, &CMyDlg::OnLvnItemchangedScList)

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

    //detect if we checked a new element
    if ( (pNMLV->uChanged & LVIF_STATE) && (pNMLV->uNewState & 0x2000) && (pNMLV->uOldState & 0x1000) )
        DisableAllItemsExcept(m_lcList, pNMLV->iItem);  

    *pResult = 0;
}

The detection of state change was based on the following article : Get the notification code from Listview Control checkboxes. The values 0x1000 and 0x2000 do not seems to be defined anywhere in the header file, so maybe I'm using an undocumented feature.

Upvotes: 0

Codename_DJ
Codename_DJ

Reputation: 563

Finally I have solved it . Now the checkbox and selection works at par. selecting checkbox selects the row and vice versa and one selection is possible. the code:

    void SomeClass::ResetAllCheckBox()
    {
        int nCount = m_ListCtrl.GetItemCount();
        for(int nItem = 0; nItem < nCount; nItem++)
        {
            m_ListCtrl.SetCheck(nItem,false);
        }

    }

    //Handler for ON_NOTIFY(NM_CLICK,...)
    void SomeClass::OnClickList(NMHDR *pNMHDR, LRESULT *pResult)
    {
        LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
        NMLISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
        LVHITTESTINFO hitinfo;
        int nPosCB=-1,nPos=-1;
        hitinfo.pt = pNMListView->ptAction;

        //Make the hit test...
        nPosCB = m_ListCtrl.HitTest(&hitinfo);

        if(hitinfo.flags != LVHT_ONITEMSTATEICON)
            return;
        ResetAllCheckBox();

        nPos = m_ListCtrl.GetNextItem(-1,LVNI_SELECTED);
        m_ListCtrl.SetItemState(nPos, ~LVIS_SELECTED, LVIS_SELECTED);
        m_ListCtrl.SetItemState(nPosCB, LVIS_SELECTED, LVIS_SELECTED);
        m_ListCtrl.SetSelectionMark(nPosCB);
        *pResult = 0;
    }


    //Handler for ON_NOTIFY(LVN_ITEMCHANGED,...)
    void SomeClass::OnItemchangedList(NMHDR *pNMHDR, LRESULT *pResult)
    {
        LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
        int nPos = -1;

        ResetAllCheckBox();

        nPos = m_ListCtrl.GetNextItem(-1,LVNI_SELECTED);
        if(nPos != -1)
            m_ListCtrl.SetCheck(nPos);

        int nCount = m_ListCtrl.GetItemCount();
        int nSelectedItemIndex = -1;
        for(int nItem = 0; nItem < nCount; nItem++)
        {
            if(m_ListCtrl.GetCheck(nItem)== 1)
                nSelectedItemIndex = nItem;
        }

        *pResult = 0;
    }

Upvotes: 3

egur
egur

Reputation: 7960

When creating the control make sure this style is used LVS_SINGLESEL.

It is passed in the CreateEx/CreateEx function. Also available from the resource editor (if control is added through it).

Upvotes: 2

Related Questions