[2 days left] What’s wrong with your cloud strategy? Learn why multicloud solutions matter with Nimble Storage.Register Now

x
?
Solved

CListCtrl - Making unused rows shaded gray

Posted on 2004-10-25
14
Medium Priority
?
1,303 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 

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

Important Lessons on Recovering from Petya

In their most recent webinar, Skyport Systems explores ways to isolate and protect critical databases to keep the core of your company safe from harm.

Question has a verified solution.

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

Exception Handling is in the core of any application that is able to dignify its name. In this article, I'll guide you through the process of writing a DRY (Don't Repeat Yourself) Exception Handling mechanism, using Aspect Oriented Programming.
Have you tried to learn about Unicode, UTF-8, and multibyte text encoding and all the articles are just too "academic" or too technical? This article aims to make the whole topic easy for just about anyone to understand.
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.
In response to a need for security and privacy, and to continue fostering an environment members can turn to for support, solutions, and education, Experts Exchange has created anonymous question capabilities. This new feature is available to our Pr…

649 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