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

Lazy Grid WTL implementation

0.00/5 (No votes)
28 Jan 2002 1  
Simplest grid implementation in WTL

Sample Image - LazyGrid1.gif

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.

 // Overrideables

#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;        // current row, column  

    COLORREF clrCell, clrCellBk;      // selected cell text, background  

    COLORREF clrRow, clrRowBk;    // selected row text, background

    COLORREF clrGrid, clrGridBk, clrBk; // unselected cell text, background

 
    BEGIN_MSG_MAP(CGrid)
        // Other handlers for customizing colors, holding cell edit, ...

            CHAIN_MSG_MAP ( CCustomDraw<CGrid>)
    END_MSG_MAP()
    
    DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
    {
        return CDRF_NOTIFYITEMDRAW;
    } 
    
    DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW pCD)
    {
        NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pCD );
        // asking subitem repainting for selected||focused row

        if ( pCD->uItemState == 0x201 || pCD->uItemState == 0x211 ) 
             return CDRF_NOTIFYSUBITEMDRAW;
        pLVCD->clrTextBk = clrGridBk;
        pLVCD->clrText = clrGrid;
        return CDRF_DODEFAULT;
    }
DWORD OnSubItemPrePaint (int /*idCtrl*/, LPNMLVCUSTOMDRAW pLVCD)
    {
        if ( pLVCD->nmcd.uItemState == 0x201 ||
                     pLVCD->nmcd.uItemState == 0x211 )
        {
            curRow = pLVCD->nmcd.dwItemSpec;
            
            // ATTENTION here we simply unselect row to 

            // avoid standard selection 

            // painting with ugly colors and leaves our nice colors 

            // for selected row (and cell also)

            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 /*uMsg*/, 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 /*uMsg*/, 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 /*uMsg*/, 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 /*wParam*/, LPNMHDR lParam, BOOL& bHandled)
    {
        RECT rc;
        GetSubItemRect(curRow, curCol, LVIR_BOUNDS, &rc);
        if ( Cols > 0   && curCol == 0  ) // correction for first column

        {
            GetSubItemRect(curRow, 1, LVIR_BOUNDS, &rc);
            rc.right = rc.left;
            rc.left = 0;
        }
        rc.left++; rc.bottom--; // some rect correction


        GetItemText(curRow, curCol, m_edit, 256);
        m_edit.MoveWindow (&rc, TRUE);
        m_edit.SetFont (GetFont());
        if ( curCol ) m_edit.SetMargins (5,5); // some margins correction


        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 /*wParam*/, 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 /*uMsg*/, 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).

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