About Custom Draw
See http://www.codeproject.com/listctrl/lvcustomdraw.asp by Michael Dunn. All information can be found from http://www.codeproject.com/ articles and MSDN.
Introduction
We need at least version 4.71 of the common controls installed (IE 4.0 and higher). I'll show the most simple grid implementation based on custom draw mechanism. With version 4.71 someone have possibility to overwrite subitem for list-view control. So I added this functionality to CCustomDraw
(AtlCtrls.h).
First change is in CCustomDraw::OnCustomDraw
where I made one more conditional branch for subitem prepainting
LRESULT OnCustomDraw(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
...
#if(_WIN32_IE >= 0x0400)
LPNMLVCUSTOMDRAW lpNMLVCustomDraw =
reinterpret_cast<LPNMLVCUSTOMDRAW> (lpNMCustomDraw);
if ( lpNMLVCustomDraw->nmcd.dwDrawStage ==
(CDDS_SUBITEM | CDDS_ITEMPREPAINT) )
{
return pT->OnSubItemPrePaint(idCtrl, lpNMLVCustomDraw);
}
#endif
switch(lpNMCustomDraw->dwDrawStage)
{
...
return dwRet;
}
Then one more overrideable was added.
#if (_WIN32_IE >= 0x0400)
DWORD OnSubItemPrePaint (int idCtrl, LPNMLVCUSTOMDRAW lpNMLVCustomDraw)
{
return CDRF_DODEFAULT;
}
#endif
Let's now write simplest grid implementation. It handles both item and subitem prepainting.
class CGrid : public CWindowImpl<CGrid, CListViewCtrl>, CCustomDraw<CGrid>
{
public:
long curRow, curCol;
COLORREF clrCell, clrCellBk;
COLORREF clrRow, clrRowBk;
COLORREF clrGrid, clrGridBk, clrBk;
BEGIN_MSG_MAP(CGrid)
CHAIN_MSG_MAP ( CCustomDraw<CGrid>)
END_MSG_MAP()
DWORD OnPrePaint(int , LPNMCUSTOMDRAW )
{
return CDRF_NOTIFYITEMDRAW;
}
DWORD OnItemPrePaint(int , LPNMCUSTOMDRAW pCD)
{
NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pCD );
if ( pCD->uItemState == 0x201 || pCD->uItemState == 0x211 )
return CDRF_NOTIFYSUBITEMDRAW;
pLVCD->clrTextBk = clrGridBk;
pLVCD->clrText = clrGrid;
return CDRF_DODEFAULT;
}
DWORD OnSubItemPrePaint (int , LPNMLVCUSTOMDRAW pLVCD)
{
if ( pLVCD->nmcd.uItemState == 0x201 ||
pLVCD->nmcd.uItemState == 0x211 )
{
curRow = pLVCD->nmcd.dwItemSpec;
pLVCD->nmcd.uItemState = 0x200;
if ( pLVCD->iSubItem == curCol )
{
pLVCD->clrText = clrCell;
pLVCD->clrTextBk = clrCellBk;
}
else
{
pLVCD->clrText = clrRow;
pLVCD->clrTextBk = clrRowBk;
}
}
return CDRF_DODEFAULT;
}
...
};
From now we have pretty looking grid with selected row and cell highlighting and without any additional painting, filling...
Let's add navigation
BEGIN_MSG_MAP(CGrid)
...
MESSAGE_HANDLER ( WM_LBUTTONDOWN, OnMouse )
MESSAGE_HANDLER ( WM_KEYDOWN, OnKey )
...
END_MSG_MAP()
LRESULT OnMouse(UINT , WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
switch(wParam)
{
case MK_LBUTTON:
{
LVHITTESTINFO info;
info.pt.x = LOWORD(lParam);
info.pt.y = HIWORD(lParam);
info.iItem = 0;
info.iSubItem = 0;
int nRes = SubItemHitTest (&info);
if ( info.iItem == curRow )
{
curCol = info.iSubItem;
SendNotifyMessage(CDRF_NOTIFYSUBITEMDRAW);
}
else
{
curCol = info.iSubItem;
curRow = info.iItem;
SendNotifyMessage(CDRF_NOTIFYITEMDRAW);
}
break;
}
bHandled = FALSE;
return 0;
}
LRESULT OnKey(UINT , WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
switch (wParam)
{
case VK_RIGHT:
if ( curCol >= Cols-1) curCol = 0;
else curCol++;
SendNotifyMessage(CDRF_NOTIFYSUBITEMDRAW);
break;
case VK_LEFT:
if ( curCol <= 0) curCol = Cols-1;
else curCol--;
SendNotifyMessage(CDRF_NOTIFYSUBITEMDRAW);
break;
}
bHandled = FALSE;
return 0;
}
Now our grid reacts at mouse and navigating buttons clicks. Now someone might like editing selected cells. Built-in edit control cannot be shown at any time we want, so let's write our own edit - like this.
class CGridEdit : public CWindowImpl<CGridEdit, CEdit>
{
CHAR m_text[256];
public:
operator LPTSTR() { return m_text; }
operator LPCTSTR() const { return m_text; }
BEGIN_MSG_MAP(CGridEdit)
MESSAGE_HANDLER ( WM_KEYDOWN, OnKey )
END_MSG_MAP()
LRESULT OnKey(UINT , WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
switch (wParam)
{
case VK_RETURN:
{ LV_DISPINFO info;
GetWindowText(m_text, 256);
info.item.pszText = m_text;
info.item.iItem = 0;
info.item.cchTextMax = 256;
info.hdr.code = LVN_ENDLABELEDIT;
info.hdr.hwndFrom = m_hWnd;
::SendMessage ( GetParent(), WM_NOTIFY, NULL, (LPARAM)&info );
break; }
case VK_ESCAPE:
{ LV_DISPINFO info;
info.item.pszText = 0;
info.item.iItem = -1;
info.item.cchTextMax = 0;
info.hdr.code = LVN_ENDLABELEDIT;
info.hdr.hwndFrom = m_hWnd;
::SendMessage ( GetParent(), WM_NOTIFY, NULL, (LPARAM)&info );
break; }
case VK_LEFT:
{
WORD start = HIWORD(GetSel());
start--;
SetSel(start, start);
break; }
case VK_RIGHT:
{
WORD start = HIWORD(GetSel());
start++;
SetSel(start, start);
break; }
case VK_END: SetSel(LineLength(),LineLength()); break;
case VK_HOME: SetSel(0,0); break;
}
bHandled = TRUE;
return 1;
}
};
Our edit sends message to parent grid about finishing editing after Enter or Escape pressed. Also at grid we need to handle message WM_CTLCOLOREDIT
to set colors to edit text, background.
Finally we get editing mechanism
class CGrid : public CWindowImpl<CGrid, CListViewCtrl>, CCustomDraw<CGrid>
{
public:
bool editing;
CGridEdit m_edit;
...
BEGIN_MSG_MAP(CGrid)
...
NOTIFY_CODE_HANDLER ( LVN_ENDLABELEDIT , OnEndEdit)
NOTIFY_CODE_HANDLER ( LVN_BEGINLABELEDIT , OnBeginEdit)
MESSAGE_HANDLER ( WM_CTLCOLOREDIT, OnColor )
...
END_MSG_MAP()
LRESULT OnBeginEdit(int , LPNMHDR lParam, BOOL& bHandled)
{
RECT rc;
GetSubItemRect(curRow, curCol, LVIR_BOUNDS, &rc);
if ( Cols > 0 && curCol == 0 )
{
GetSubItemRect(curRow, 1, LVIR_BOUNDS, &rc);
rc.right = rc.left;
rc.left = 0;
}
rc.left++; rc.bottom--;
GetItemText(curRow, curCol, m_edit, 256);
m_edit.MoveWindow (&rc, TRUE);
m_edit.SetFont (GetFont());
if ( curCol ) m_edit.SetMargins (5,5);
else m_edit.SetMargins (3,5);
m_edit.SetWindowText (m_edit);
m_edit.ShowWindow(SW_SHOW);
m_edit.SetFocus();
m_edit.SetSel (0,0);
m_edit.UpdateWindow ();
editing = true;
bHandled = TRUE;
return 1;
}
LRESULT OnEndEdit(int , LPNMHDR lParam, BOOL& bHandled)
{
LV_DISPINFO * plvdi = (LV_DISPINFO *)lParam;
m_edit.ShowWindow(SW_HIDE);
if((plvdi->item.iItem != -1 ) &&
(plvdi->item.pszText != NULL))
SetItemText ( curRow, curCol, plvdi->item.pszText );
editing = false;
bHandled = FALSE;
return 0;
}
LRESULT OnColor(UINT , WPARAM wParam, LPARAM lParam,
BOOL& bHandled)
{
HBRUSH brush = CreateSolidBrush (clrGridBk);
::SetBkColor ((HDC)wParam, clrGridBk);
::SetTextColor ((HDC)wParam, clrGrid);
bHandled = TRUE;
return (DWORD)brush;
}
...
};
For now we have very simple, not optimized, but functional grid implementation without any hard coding. With the help of other authors on custom draw theme someone can customize grid in any way.
Conclusion
Without any exotic features lazy grid for me is easy to use and debug control. Warning - I haven't spent a lot of time for testing and debugging it. Best regards from Sokolov Maxim (lazy programmer).