checkboxmfcside-effectsclistctrl

CListCtrl with checkboxes questions


The List Control is defined as Single Selection on the resources.

Question 1

I want to have a checkbox on the header of first column of my CListCtrl. On the OnInitDialog I have

    m_list.SetExtendedStyle(m_list.GetExtendedStyle() | LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT);

    CString s;
    s.LoadString(IDS_COLUMN1);

    #ifndef HDS_CHECKBOXES
    // Copied from Microsoft SDKs\Windows\v7.0A\Include\CommCtrl.h
    #define HDS_CHECKBOXES  0x0400
    #endif 

    CHeaderCtrl& header = *m_list.GetHeaderCtrl();
    header.ModifyStyle(0, HDS_CHECKBOXES);


    #ifndef HDF_CHECKBOX
    // Copied from Microsoft SDKs\Windows\v7.0A\Include\CommCtrl.h    
    #define HDF_CHECKBOX  0x0040
    #endif 

    LVCOLUMN lc = { 0 };
    lc.mask = LVCF_FMT |LVCF_WIDTH |LVCF_TEXT | LVCF_SUBITEM;
    lc.fmt |= HDF_CHECKBOX;
    lc.cx = 96;
    lc.pszText = (TCHAR*) (LPCTSTR)s;

    m_list.InsertColumn(0, &lc);

It only presents the header's checkbox if I add in the extended |LVS_EX_AUTOCHECKSELECT, which I definitely not want because I desire that checking action and selection action will be used for different purposes.

Question 2

I need to set a boolean and mark thing as modified when user checks or unchecks an item. But I don't want this action to occur when inserting items, for example when filling the list at form loading, but it is triggered without my intention, as InsertItem triggers "a uncheck action" on OnItemChanged.

It obliged me to condition every insert with a m_is_inserting member flag:

    m_is_inserting = true;
    m_list.InsertItem(i, m_array[i]->GetName());
    m_is_inserting = false;

and react accordingly on the LVN_ITEMCHANGED handler

void CMyDialog::OnItemChanged(NMHDR* pNMHDR, LRESULT* pResult)
{
    NMLISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

    if (pNMListView->uChanged & LVIF_STATE)
    {
        if (pNMListView->uNewState & LVIS_SELECTED)
            OnSelect();
        else
        {
            if (pNMListView->iItem != -1)
            {
                if ((pNMListView->uNewState & LVIS_STATEIMAGEMASK) == 0x1000)
                {
                    if (!m_is_inserting)
                    {
                        m_array[pNMListView->iItem]->m_active = false;
                        SetModified();
                    }
                }
                else if ((pNMListView->uNewState & LVIS_STATEIMAGEMASK) == 0x2000)
                {
                    if (!m_is_inserting)
                    {
                        m_array[pNMListView->iItem]->m_active = true;
                        SetModified();
                    }
                }
            }
        }
    }

    *pResult = 0;
}

Is there a better way to distinguish a real user check/uncheck action from a InsertItem side effect?

Question 3

Is there a better symbolic convention for getting the check/uncheck state? Magic numbers 0x1000 and 0x2000 are pretty meaningless!

Thanks in advance.


Solution

  • Insert the column headers first. Then change the HDF_CHECKBOX. For example:

    m_list.SetExtendedStyle(LVS_EX_CHECKBOXES| LVS_EX_FULLROWSELECT);
    
    CHeaderCtrl &header = *m_list.GetHeaderCtrl();
    header.ModifyStyle(0, HDS_CHECKBOXES);
    
    m_list.InsertColumn(0, L"Column0", 0, 120, 0);
    m_list.InsertColumn(1, L"Column1", 0, 80, 1);
    m_list.InsertColumn(2, L"Column2", 0, 80, 2);
    
    HDITEM hdi = { 0 };
    hdi.mask = HDI_FORMAT;
    header.GetItem(0, &hdi);
    hdi.fmt |= HDF_CHECKBOX;
    header.SetItem(0, &hdi);
    
    m_list.InsertItem(m_list.GetItemCount(), L"C0", 0);
    m_list.InsertItem(m_list.GetItemCount(), L"C1", 0);
    
    m_list.SetCheck(0, 1);
    m_list.SetCheck(1, 1);
    

    When handling the notification you can use the GetCheck method to see if item is checked or not. Example:

    if(pNMListView->uChanged & LVIF_STATE)
    {
        if(pNMListView->uNewState & LVIS_SELECTED)
        {
            ...
        }
        else if(pNMListView->uNewState & LVIS_STATEIMAGEMASK && pNMListView->iItem >= 0)
        {
            if(m_list.GetCheck(pNMListView->iItem))
                TRACE("%d checked\n", pNMListView->iItem);
        }
    }