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 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)
ON_NOTIFY_REFLECT(NM_CLICK, OnClick)
ON_NOTIFY_REFLECT(LVN_KEYDOWN, OnKeydown)
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;
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)
{
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);
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
.