Solved

CListCtrl - Making unused rows shaded gray

Posted on 2004-10-25
1,118 Views
Last Modified: 2013-11-20
Hi,

I've created a list control (CListCtrl-derived) that shows parameters for a selected command. Now the number of parameters can change, based on the selected command. Sometimes the list control looks silly, as there is one row there, and a whole heap of unused rows, which look like they are still normal rows (I am showing grid lines.)

What is the best way to make a CListCtrl-derived class only show the rows that exist, and the other area in the control shaded a certain colour (e.g., gray, like you see in other programs...) ? I'm sure this would be useful for many people.

Cheers,

Dave
0
Question by:208Fireball
    14 Comments
     
    LVL 43

    Expert Comment

    by:AndyAinscow
    You need the list control to be owner draw.  (You show gris lines - are you already doing owner draw?).  Now paint the bacground you require when you draw the item.

    An alternative is custom draw. see-
    http://www.codeproject.com/listctrl/
    0
     

    Author Comment

    by:208Fireball
    I'm just setting the extended style LVS_EX_GRIDLINES for grid lines.

    As for the owner-draw and custom-draw list controls, I will investigate that link you gave...

    Cheers,

    Dave
    0
     
    LVL 43

    Expert Comment

    by:AndyAinscow
    If you handled the owner draw or custom draw you could also draw grid lines yourself - ie. only on the part of the display that has rows.  (Thats why I asked)
    0
     

    Author Comment

    by:208Fireball
    It's funny that the one thing that I can't seem to find on that link or other ones off google is someone doing a decent example on doing grid lines. Everyone seems to be content having grids for the whole control, regardless of how many rows there are.

    Ideally, the solution should have:

    background of a certain colour (ok now, I paint the background by overriding the onerasebackground behaviour),
    grid lines around all cells where we have a row (item) that has data, and
    a line under the last row to separate it from the background (optional.)

    I can work out the bounding rectangle for each sub-item and draw a framerect around it, but this doesn't give the right outcome (the default gridlines aren't included in the bounding rectangle.)

    I guess from what I have seen, to answer the original question I need to know when the gridlines get drawn within the customdraw framework.
    I will look for the answer for this sub-question too...

    Cheers,

    Dave
    0
     
    LVL 43

    Expert Comment

    by:AndyAinscow
    If you do owner draw you have to draw each item.  The lpDrawItemStruct->rcItem contains the rectangle to be drawn - you can use this to draw lines AND as it is only called for an item to be drawn you will only draw lines around rows that have contents.  

    see
    http://www.ainscow.ch/ee/testgrid.jpg
    0
     

    Author Comment

    by:208Fireball
    Ok so that would probably do it for owner-draw. Is it possible to do it with custom draw?

    Here's what I have so far...

    void CPropertyListCtrl::OnCustomDrawList(NMHDR* pNMHDR, LRESULT* pResult)
    {
          CDC *pDC;
          CBrush brBackground, *pOldBrush;
          CPen penBorder, *pOldPen;

          NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*> ( pNMHDR );

        // Default value...
        *pResult = CDRF_DODEFAULT;

          // What custom draw stage are we up to?
          switch(pLVCD->nmcd.dwDrawStage)
          {
          case CDDS_PREPAINT:
                // request item draw notifications...
                *pResult = CDRF_NOTIFYITEMDRAW;
                break;

          case CDDS_ITEMPREPAINT:
                // requesr sub-item draw notifications...
                *pResult = CDRF_NOTIFYSUBITEMDRAW;
                break;

          case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
          {
                CRect rcTemp;
                int iCol = pLVCD->iSubItem;
                int iRow = pLVCD->nmcd.dwItemSpec;
                CString sItem = GetItemText(iRow, iCol);

                penBorder.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
                brBackground.CreateSolidBrush(RGB(255, 255, 255));

                // get the device context.
                pDC= CDC::FromHandle(pLVCD->nmcd.hdc);

                pOldPen = (CPen *) pDC->SelectObject(&penBorder);
                pOldBrush = (CBrush *) pDC->SelectObject(&brBackground);

                // paint the border...
                pDC->Rectangle(&((pLVCD->nmcd).rc));

                pDC->SelectObject(pOldPen);
                pDC->SelectObject(pOldBrush);
                
                *pResult= CDRF_SKIPDEFAULT;
                break;
          }

          default: // pass through...
                *pResult = CDRF_DODEFAULT;
          }
    }

    It's doing some interesting results (and doesn't draw the text yet...)

    Cheers,

    Dave
    0
     
    LVL 43

    Expert Comment

    by:AndyAinscow
    To be honest I use ownerdraw (I didn't know about custom draw when I coded it initially and stayed with owner draw whenever I needed a customised list control).
    0
     
    LVL 43

    Expert Comment

    by:AndyAinscow
    This could be of interest

    http://www.nwlink.com/~mikeblas/samples/
    CUSTLIST.ZIP


    I have also added
          else if (lplvcd->nmcd.dwDrawStage == CDDS_ITEMPOSTPAINT)
          {
            int    nItem = static_cast<int>( lplvcd->nmcd.dwItemSpec );
                // Get the rect that holds the item's icon.
                CListCtrl* pCtrl = (CListCtrl*) GetDlgItem(IDC_LIST1);

            CRect rcIcon;
            pCtrl->GetItemRect ( nItem, &rcIcon, LVIR_LABEL );

            CDC*  pDC = CDC::FromHandle ( lplvcd->nmcd.hdc );
                pDC->DrawEdge(&rcIcon, EDGE_ETCHED, BF_RECT);
     
           *pResult = CDRF_SKIPDEFAULT;
          }

    and can get a rectangle around the first item in the first column for those rows that have data
    0
     

    Author Comment

    by:208Fireball
    Thanks for the resource links. My custom-draw routine is getting closer to the mark!

    Sounds like it would be possible to do what the following link is doing in OnPaint, but in the postpaint section of the custom draw...might have to think about it. http://www.codeguru.com/Cpp/controls/listview/gridlines/article.php/c963/

    The best I have is an adaptation of my previous source that incorporates a couple more of the ideas (at the moment, the "grid lines" are just sections of the background.) Note that for this one, I return FALSE for OnEraseBackground, and colour the background in the prepaint section of custom draw (otherwise there were nasty effects on tracking the header control.)

    Do you see anywhere where this setup can be improved?

    void CPropertyListCtrl::OnCustomDrawList(NMHDR* pNMHDR, LRESULT* pResult)
    {
          CDC *pDC;
          CBrush brBackground, *pOldBrush;
          CPen penBorder, *pOldPen;

          NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*> ( pNMHDR );

        // Default value...
        *pResult = CDRF_DODEFAULT;

          // What custom draw stage are we up to?
          switch(pLVCD->nmcd.dwDrawStage)
          {
          case CDDS_PREPAINT:
                // request item draw notifications...
                pDC= CDC::FromHandle(pLVCD->nmcd.hdc);
                pDC->FillSolidRect(&((pLVCD->nmcd).rc), m_crBack);

                *pResult = CDRF_NOTIFYITEMDRAW;
                break;

          case CDDS_ITEMPREPAINT:
                // request sub-item draw notifications...
                *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NOTIFYPOSTPAINT;
                break;

          case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
          {
                CRect rcSI;
                int iCol = static_cast<int>(pLVCD->iSubItem);
                int iRow = static_cast<int>(pLVCD->nmcd.dwItemSpec);

                penBorder.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
                brBackground.CreateSolidBrush(RGB(255, 255, 255));

                CString sItem = GetItemText(iRow, iCol);

                // get the device context.
                pDC= CDC::FromHandle(pLVCD->nmcd.hdc);

                pOldPen = (CPen *) pDC->SelectObject(&penBorder);
                pOldBrush = (CBrush *) pDC->SelectObject(&brBackground);

                if (iCol != 0)
                {
                      GetSubItemRect(iRow, iCol, LVIR_LABEL, rcSI);

                      // paint the background...
                      pDC->FillSolidRect(rcSI.left, rcSI.top, rcSI.Width() - 1, rcSI.Height() - 1, RGB(255, 255, 255));

                      // paint the border...
                      //pDC->MoveTo(rcSI.left, rcSI.bottom);
                      //pDC->LineTo(rcSI.right + 1, rcSI.bottom);
                      //pDC->LineTo(rcSI.right + 1, rcSI.top - 1);

                      pDC->SetBkMode(TRANSPARENT);

                      pDC->DrawText(sItem, rcSI, DT_CENTER);
                }
                else
                {
                      GetItemRect(iRow, rcSI, LVIR_LABEL);
                      
                      // paint the background...
                      pDC->FillSolidRect(rcSI.left, rcSI.top, rcSI.Width() - 1, rcSI.Height() - 1, RGB(255, 255, 255));
                      
                      // paint the border...
                      //pDC->MoveTo(rcSI.left, rcSI.bottom);
                      //pDC->LineTo(rcSI.right, rcSI.bottom);
                      //pDC->LineTo(rcSI.right, rcSI.top);

                      pDC->SetBkMode(TRANSPARENT);

                      pDC->DrawText(sItem, rcSI, DT_LEFT);
                }

                pDC->SelectObject(pOldPen);
                pDC->SelectObject(pOldBrush);
                
                *pResult= CDRF_SKIPDEFAULT;
                //*pResult = CDRF_DODEFAULT;
                break;
          }
          case CDDS_ITEMPOSTPAINT:
          {
                // TODO: draw something here?

                *pResult = CDRF_DODEFAULT;
                break;
          }
          default:
                // pass through...

                *pResult = CDRF_DODEFAULT;
          }
    }

    Dave
    0
     
    LVL 43

    Expert Comment

    by:AndyAinscow
        case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
         {
              CRect rcSI;
              int iCol = static_cast<int>(pLVCD->iSubItem);
              int iRow = static_cast<int>(pLVCD->nmcd.dwItemSpec);

              penBorder.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
              brBackground.CreateSolidBrush(RGB(255, 255, 255));

              CString sItem = pCtrl->GetItemText(iRow, iCol);

              // get the device context.
              pDC= CDC::FromHandle(pLVCD->nmcd.hdc);

              pOldPen = (CPen *) pDC->SelectObject(&penBorder);
              pOldBrush = (CBrush *) pDC->SelectObject(&brBackground);

              if (iCol != 0)
              {
                   pCtrl->GetSubItemRect(iRow, iCol, LVIR_LABEL, rcSI);
                         rcSI.DeflateRect(1, 1);//     <<<----------------------------------------------------------

                   // paint the background...
                   pDC->FillSolidRect(rcSI.left, rcSI.top, rcSI.Width() - 1, rcSI.Height() - 1, RGB(255, 255, 255));

                   pDC->SetBkMode(TRANSPARENT);

                   pDC->DrawText(sItem, rcSI, DT_CENTER);
              }
              else
              {
                   pCtrl->GetItemRect(iRow, rcSI, LVIR_LABEL);
                         rcSI.DeflateRect(1, 1);//     <<<----------------------------------------------------------
                   
                   // paint the background...
                   pDC->FillSolidRect(rcSI.left, rcSI.top, rcSI.Width() - 1, rcSI.Height() - 1, RGB(255, 255, 255));
                   
                   pDC->SetBkMode(TRANSPARENT);

                   pDC->DrawText(sItem, rcSI, DT_LEFT);
              }
                   rcSI.InflateRect(1, 1);//     <<<----------------------------------------------------------

               // paint the border...
               pDC->MoveTo(rcSI.left, rcSI.bottom);
               pDC->LineTo(rcSI.right, rcSI.bottom);
               pDC->LineTo(rcSI.right, rcSI.top);

              pDC->SelectObject(pOldPen);
              pDC->SelectObject(pOldBrush);
             
              *pResult= CDRF_SKIPDEFAULT;
              //*pResult = CDRF_DODEFAULT;
              break;
         }



    general point - you have RGB(255,255,255) hard coded for the background.  Use GetSysColor to retrieve the currently selected colour
    0
     

    Author Comment

    by:208Fireball
    Here (temporarily) is a 2x image of the current results of the function ( with the changes made above, but the minus 1s removed from the FillSolidRect function.

    It appears to be still a little off, when I remove the deflaterect(1,1)s , I lose all our drawn lines, but when the deflaterect(1,1) is included, there is still an extra horizontal and vertical gridline of background, as well as our lines, and it appears that some of the lines are still missing.

    What should the next mod be? :)

    Cheers,

    Dave



    0
     
    LVL 43

    Accepted Solution

    by:
    If you want only the gridlines for the rows with data turn off the LVS_EX_GRIDLINES setting (else don't try to draw the border)
    Using LVIR_BOUNDS instead of LVIR_LABEL brings some improvement.
    eg.
                   pCtrl->GetItemRect(iRow, &rcSI, LVIR_BOUNDS);



    To be honest I still prefer the owner draw.  This seems (drawing grid lines ourselves) to be getting beyond a simple change which IMHO is the reason for custom draw.  eg. Changing colour of alternate rows/columns
    With owner draw one gets a message to draw the item along with the hdc and other info.  Check if selected/focused and then draw what is required in the rectangle supplied.

    something like the following should be suitable

        LV_ITEM lvi;
         RECT rcClip;
         CListCtrl &lc = GetListCtrl();    

         // Get the item state to be displayed
         lvi.mask = LVIF_STATE;
         lvi.iItem = lpDrawItem->itemID;
         lvi.iSubItem = 0;
         lc.GetItem(&lvi);
         SetTextColor(lpDrawItem->hDC, GetSysColor(COLOR_WINDOWTEXT));
         SetBkColor(lpDrawItem->hDC, GetSysColor(COLOR_WINDOW));

         rcClip.left = lpDrawItem->rcItem.left;
         rcClip.right = lpDrawItem->rcItem.left + lc.GetColumnWidth(0);
         rcClip.top = lpDrawItem->rcItem.top;
         rcClip.bottom = lpDrawItem->rcItem.bottom;
         CString szString = "text";

         ExtTextOut(hdc, rcClip.left + 2, rcClip.top + 1, ETO_CLIPPED | ETO_OPAQUE, &rcClip, szString, lstrlen(szString), NULL);

    now draw the border
    0
     

    Author Comment

    by:208Fireball
    Ok, I will give that a go. It might take a while, so I think it's time to hand over the points :)
    0
     
    LVL 43

    Expert Comment

    by:AndyAinscow
    Thanks, if it is still unclear put another comment here.
    0

    Write Comment

    Please enter a first name

    Please enter a last name

    We will never share this with anyone.

    Featured Post

    Product Review - Android Remix

    Come along for the ride with our Senior Product Manager, Brian Matis, as he reviews the Android Remix.

    This is to be the first in a series of articles demonstrating the development of a complete windows based application using the MFC classes.  I’ll try to keep each article focused on one (or a couple) of the tasks that one may meet.   Introductio…
    Introduction: Dialogs (1) modal - maintaining the database. Continuing from the ninth article about sudoku.   You might have heard of modal and modeless dialogs.  Here with this Sudoku application will we use one of each type: a modal dialog …
    This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
    Internet Business Fax to Email Made Easy - With eFax Corporate (http://www.enterprise.efax.com), you'll receive a dedicated online fax number, which is used the same way as a typical analog fax number. You'll receive secure faxes in your email, fr…

    931 members asked questions and received personalized solutions in the past 7 days.

    Join the community of 500,000 technology professionals and ask your questions.

    Join & Ask a Question

    Need Help in Real-Time?

    Connect with top rated Experts

    18 Experts available now in Live!

    Get 1:1 Help Now