?
Solved

CListCtrl - Making unused rows shaded gray

Posted on 2004-10-25
14
Medium Priority
?
1,323 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
Comment
Question by:208Fireball
  • 8
  • 6
14 Comments
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 12408135
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
ID: 12408207
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 45

Expert Comment

by:AndyAinscow
ID: 12408714
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
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:208Fireball
ID: 12440540
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 45

Expert Comment

by:AndyAinscow
ID: 12442234
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
ID: 12459921
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 45

Expert Comment

by:AndyAinscow
ID: 12460995
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 45

Expert Comment

by:AndyAinscow
ID: 12464318
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
ID: 12469148
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 45

Expert Comment

by:AndyAinscow
ID: 12471014
    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
ID: 12478546
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 45

Accepted Solution

by:
AndyAinscow earned 1000 total points
ID: 12481278
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
ID: 12487800
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 45

Expert Comment

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

Featured Post

Upgrade your Question Security!

Your question, your audience. Choose who sees your identity—and your question—with question security.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

In this article, I'll describe -- and show pictures of -- some of the significant additions that have been made available to programmers in the MFC Feature Pack for Visual C++ 2008.  These same feature are in the MFC libraries that come with Visual …
Introduction: Load and Save to file, Document-View interaction inside the SDI. Continuing from the second article about sudoku.   Open the project in visual studio. From the class view select CSudokuDoc and double click to open the header …
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.
Exchange organizations may use the Journaling Agent of the Transport Service to archive messages going through Exchange. However, if the Transport Service is integrated with some email content management application (such as an anti-spam), the admin…
Suggested Courses

615 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