Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WTL
Article

Lazy Grid WTL implementation

Rate me:
Please Sign up or sign in to vote.
4.64/5 (5 votes)
28 Jan 20021 min read 149.5K   2.7K   31   12
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


Written By
Canada Canada
russian

Comments and Discussions

 
QuestionOnBeginEdit does not call Pin
uzerman1-Nov-12 6:01
uzerman1-Nov-12 6:01 
Questionabout all of these Customdraw functions Pin
pxdbxq12-Aug-12 18:14
pxdbxq12-Aug-12 18:14 
AnswerRe: about all of these Customdraw functions Pin
pxdbxq14-Aug-12 20:57
pxdbxq14-Aug-12 20:57 
Generalv c++ 7.0 = assertion failure Pin
ericb_summit2-Jul-04 9:44
ericb_summit2-Jul-04 9:44 
GeneralRe: v c++ 7.0 = assertion failure Pin
Tcherepanov Serguei14-Mar-06 23:59
Tcherepanov Serguei14-Mar-06 23:59 
GeneralProblem with compiling Pin
Hamlet_h17-Mar-04 4:43
Hamlet_h17-Mar-04 4:43 
The file atlres.h not found.
GeneralClistViewCtrl with different colors for rows for wince Pin
Bilal Ahmad1-Dec-03 17:56
Bilal Ahmad1-Dec-03 17:56 
Generalno compile Pin
Alex_Black18-Aug-03 0:32
Alex_Black18-Aug-03 0:32 
GeneralRe: no compile Pin
sixth_voice1-Oct-03 2:48
susssixth_voice1-Oct-03 2:48 
GeneralSelection is not updates when navigating to left of right Pin
Ramon Smits21-Jan-02 6:48
Ramon Smits21-Jan-02 6:48 
General2 problems Pin
limax15-Jan-02 9:57
limax15-Jan-02 9:57 
GeneralRe: 2 problems Pin
gok21-Jan-02 7:57
professionalgok21-Jan-02 7:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.