CListCtrl - Making unused rows shaded gray

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
208FireballAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

AndyAinscowFreelance programmer / ConsultantCommented:
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
208FireballAuthor Commented:
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
AndyAinscowFreelance programmer / ConsultantCommented:
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
Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

208FireballAuthor Commented:
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
AndyAinscowFreelance programmer / ConsultantCommented:
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
208FireballAuthor Commented:
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
AndyAinscowFreelance programmer / ConsultantCommented:
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
AndyAinscowFreelance programmer / ConsultantCommented:
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
208FireballAuthor Commented:
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
AndyAinscowFreelance programmer / ConsultantCommented:
    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
208FireballAuthor Commented:
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
AndyAinscowFreelance programmer / ConsultantCommented:
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

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
208FireballAuthor Commented:
Ok, I will give that a go. It might take a while, so I think it's time to hand over the points :)
0
AndyAinscowFreelance programmer / ConsultantCommented:
Thanks, if it is still unclear put another comment here.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
System Programming

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.