Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SubItem Selection in List Control

0.00/5 (No votes)
27 Dec 2004 1  
SubItem selection in List Control.

Introduction

Muhammad Azam in his article 'SubItem Selection in List Control' showed how you can select subitem in CListCtrl by setting style LVS_OWNERDRAWFIXED and implementing DrawItem function. I'd like to show that a similar thing can be done using custom draw which is considered to be simpler.

The code

In CXListCtrl class derived from CListCtrl, I implemented NM_CUSTOMDRAW message because I wanted selected subitems to be drawn, NM_CLICK message to select subitem by mouse, and LVN_KEYDOWN to select subitem by keyboard.

  • int m_sel_row; to keep track of selected row
  • int m_sel_col; to keep track of selected column
  • BOOL m_bCustomDraw; e.g., to switch off custom draw
  • int m_nNumberOfRows; number of rows in ListCtrl
  • int m_nNumberOfCols; number of columns in ListCtrl

XListCtrl.h

//{{AFX_MSG(CXListCtrl)

  afx_msg void OnClick(NMHDR* pNMHDR, LRESULT* pResult);
  afx_msg void OnKeydown(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg void custom_draw_funtion(NMHDR *pNMHDR, LRESULT *pResult);

XListCtrl.cpp

BEGIN_MESSAGE_MAP(CXListCtrl, CListCtrl)
    //{{AFX_MSG_MAP(CXListCtrl)

    ON_NOTIFY_REFLECT(NM_CLICK, OnClick)
    ON_NOTIFY_REFLECT(LVN_KEYDOWN, OnKeydown)
    //}}AFX_MSG_MAP

    ON_NOTIFY_REFLECT(NM_CUSTOMDRAW,custom_draw_funtion)
END_MESSAGE_MAP()
void CXListCtrl::custom_draw_funtion(NMHDR *pNMHDR, LRESULT *pResult)
{
    NMLVCUSTOMDRAW* nmcd=(NMLVCUSTOMDRAW*)pNMHDR;
    *pResult=CDRF_DODEFAULT;

    int row; 
    int col;
    switch(nmcd->nmcd.dwDrawStage)
    {
        case CDDS_PREPAINT:
            if(m_bCustomDraw) 
                *pResult=CDRF_NOTIFYITEMDRAW; 
            // else CDRF_DODEFAULT which tell windows to paint itself

            return;

        case CDDS_ITEMPREPAINT:
            *pResult=CDRF_NOTIFYSUBITEMDRAW;
            return;

        case CDDS_SUBITEM|CDDS_ITEMPREPAINT:
        {
            *pResult=0;
            row=nmcd->nmcd.dwItemSpec;
            col=nmcd->iSubItem;

            CString str=GetItemText(row,col);

            CRect rect;
            CDC*pDC=CDC::FromHandle(nmcd->nmcd.hdc);

            if(col>0)
                GetSubItemRect(row,col,LVIR_BOUNDS,rect);
            else
                GetItemRect(row,&rect,LVIR_LABEL);

            UINT uCode=DT_LEFT;

            if(row==m_sel_row && col==m_sel_col)
            {
                COLORREF kolor=0xaa00aa;

                if(GetFocus()==this)
                    kolor=0x0000ff;

                CBrushbrush(kolor);
                pDC->FillRect(&rect,&brush);
            }
            rect.OffsetRect(5,0);
            pDC->DrawText(str,&rect,uCode);

            *pResult=CDRF_SKIPDEFAULT;

            break;
        }
    }
}
void CXListCtrl::invalidate_grid(int row, int col)
{   //I add this function to reduce flickering

    CRect r;

    if(col==0)
        GetItemRect(row,&r,LVIR_LABEL);
    else
        GetSubItemRect(row,col,LVIR_BOUNDS,r);

    InvalidateRect(&r);
}
void CXListCtrl::OnClick(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NMITEMACTIVATE* nm=(NMITEMACTIVATE*)pNMHDR;

    invalidate_grid(m_sel_row,m_sel_col);//to clear old selection

    m_sel_row=nm->iItem;
    m_sel_col=nm->iSubItem;
    invalidate_grid(m_sel_row,m_sel_col);

    *pResult = 0;
}
void CXListCtrl::OnKeydown(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NMLVKEYDOWN* nmkd = (NMLVKEYDOWN*)pNMHDR;

    switch(nmkd->wVKey)
    {
        case VK_LEFT:
            m_sel_col--;
            if(m_sel_col<0)
                m_sel_col=0;
            invalidate_grid(m_sel_row,m_sel_col+1);
            break;
        case VK_RIGHT:
            m_sel_col++;
            if(m_sel_col>m_nNumberOfCols-1)
                m_sel_col=m_nNumberOfCols-1;
            invalidate_grid(m_sel_row,m_sel_col-1);
            break;

        case VK_UP:
            m_sel_row--;
            if(m_sel_row<0)
                m_sel_row=0;
            invalidate_grid(m_sel_row+1,m_sel_col);
            break;

        case VK_DOWN: 
            m_sel_row++;
            if(m_sel_row>m_nNumberOfRows-1)
                m_sel_row=m_nNumberOfRows-1;
            invalidate_grid(m_sel_row-1,m_sel_col);
            break;
        case VK_PRIOR:
            invalidate_grid(m_sel_row,m_sel_col);
            m_sel_row=0;
            break;
        case VK_NEXT:
            invalidate_grid(m_sel_row,m_sel_col);
            m_sel_row=m_nNumberOfRows-1;
            break;
        case VK_HOME:
            invalidate_grid(m_sel_row,m_sel_col);
            m_sel_col=0;

            if(GetKeyState(VK_CONTROL)<0)
                m_sel_row=0;

            SetItemState(m_sel_row,LVIS_FOCUSED,LVIS_FOCUSED);
            *pResult = CDRF_SKIPDEFAULT;
            invalidate_grid(m_sel_row,m_sel_col);
            return;
            break;
        case VK_END:
            invalidate_grid(m_sel_row,m_sel_col);
            m_sel_col=m_nNumberOfCols-1;
            if(GetKeyState(VK_CONTROL)<0)
                m_sel_row=m_nNumberOfRows-1;

            SetItemState(m_sel_row,LVIS_FOCUSED,LVIS_FOCUSED);

            *pResult=CDRF_SKIPDEFAULT;
            invalidate_grid(m_sel_row,m_sel_col);
            return;
    }
    *pResult = 0;
}

Points of Interest

In OnKeydown function:

  • SetItemState(m_sel_row,LVIS_FOCUSED,LVIS_FOCUSED);
  • *pResult=CDRF_SKIPDEFAULT;

change default responding CListCtrl on messages VK_HOME and VK_END.

Demo project

Demo project is a dialog based application with CListCtrl control and CXListCtrl class derived from CListCtrl.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here