Weird problem with CListCtrl::InsertItem() in Windows 8

I have written a program in C++ using MFC (VS 2013).  The program works just fine as long as it's running on a WIndows  7 machine.  When I install the program on Windows 8, it crashes with a problem related to CLIstCtrl.  The list control in question has 13 visible lines.  However, when the 14th line of data is added using CListCtrl::InsertItem(), the program crashes.  I use the loop below to fill the list, which is a member variable called mDispList:

for (int row = 0; row < NmbrRows; row++)
{
    lvi.iItem = row;
    mDispList.InsertItem(LVIF_TEXT, row, _T("A"), 0, 0, 0, 0);
    mDispList.SetItemText(row, 1, _T("B"));
    mDispList.SetItemText(row, 2, _T("C"));
    mDispList.SetItemText(row, 3, _T("D"));
}

Open in new window


I have found a few cryptic references to this problem online but nothing with a clear answer.  Does anyone know how to solve this problem?

Thanks very much.
debrunsonAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
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.

evilrixSenior Software Engineer (Avast)Commented:
>>  it crashes with a problem related to CLIstCtrl.
What is the specific error you get when it "crashes"?

>> I have found a few cryptic references to this problem online
References to these would also be helpful
ZoppoCommented:
Just as addition: The way you use InsertItem and SetItemText is not safe because the new item's index can differ from the row passed to these functions, i.e. if sorting is activated using LVS_SORTASCENDING or LVS_SORTDESCENDING style - you should use the index returned by InsertItem instead, i.e.:
for (int row = 0; row < NmbrRows; row++)
{
    lvi.iItem = row; // I'm nor sure if this is used later, if so you should even set it to idx after the next line
    int idx = mDispList.InsertItem(LVIF_TEXT, row, _T("A"), 0, 0, 0, 0);
    mDispList.SetItemText(idx, 1, _T("B"));
    mDispList.SetItemText(idx, 2, _T("C"));
    mDispList.SetItemText(idx, 3, _T("D"));
}

Open in new window

ZOPPO
sarabandeCommented:
lvi.iItem = row; // I'm nor sure if this is used later
the lvi is a struct object of type LV_ITEM which offers an alternative call for CListCtrl::InsertItem.

int InsertItem(
   const LVITEM* pItem 
);

Open in new window


the structure allows to add image and status properties for each row with the InsertItem call.

for a simple list control with text columns and without image or status handling, it is not recommended to use this call since it requires a temporary writeable text buffer for the "label" of the row to be passed with the LV_ITEM structure what makes the use incommodious.

you may remove or uncomment the lvi if it is not used.

The way you use InsertItem and SetItemText is not safe
Zoppo is right. i had often problems when using a list control and forgot to switch off the default sorting. if you don't need your rows to be sorted by the first column, or need to to provide an individual ordering anyhow, you should set the sort property of the control to not sorting what also should help to  avoid the crash.

Sara
Amazon Web Services

Are you thinking about creating an Amazon Web Services account for your business? Not sure where to start? In this course you’ll get an overview of the history of AWS and take a tour of their user interface.

debrunsonAuthor Commented:
Friends:

The first crash dialog says "[Program Name] has stopped working.  Windows is checking for a solution to the problem".  

Quickly thereafter, it is replaced with a second dialog, "[Program Name] has stopped working.  A problem caused the program to stop working correctly.  Windows will close the program and notify you if a solution is available."

I am developing on a Window 7 machine and cannot get a crash or error while running in my debugger - only when I install it on a Windows 8 machine.

As for the line "lvi.iItem = row;", this is a leftover, sorry.  It is not used.  I also thank you for the explanation about the sorting issue, I was not aware of this but it does make sense.  However as it happens, sorting was turned off on this CListCtrl (I sort the data myself then redisplay it in my custom order).

As for references, I couldn't find one that exactly reflects my particular problem.  However I did find this one,  It indicates that Windows 8 has some sort of problem(s) with CListCtrl.

I appreciate all of your comments and assistance!

debrunson
sarabandeCommented:
how did you make the installation at the windows 8? did you install a release build or debug build? if installing a release build (what is recommended), did you check whether the release build runs correctly on windows 7 platform?

did you use a setup program designed for windows 8 installations or did you copy files? if the latter, did you install runtime of visual studio 2013 to the target machine? did you have the same .net environment for both windows 7 and windows 8. are the windows 7 and windows 8 both 64-bit? note, the list control comes with comctl32.dll which you find at system32 folder for 32-bit windows and syswow64 folder for 64-bit os. the comctl32.dll version must fit. if you have different versions of the dll at windows 7 and windows 8 and the windows 7 version, then it could be the reason for your problem, if the windows 8 version is older, this could be solved by installing the runtime of visual studio 2013 at the target machine.

Sara
debrunsonAuthor Commented:
Sara:

I compiled a Release build in VS 2013.  I used the Sourceforge NSIS (using the HM NIS editor) to create an installation program.  I use the resulting installer to move the required program executable, plus icons, and these two files from my system:

"C:\Program Files (x86)\VC\redist\x86\Microsoft.VC120.MFC\mfc120u.dll"
"C:\Program Files (x86)\VC\redist\x86\Microsoft.VC120.CRT\msvcr120.dll"

I think these two files are the runtime of Visual Studio 2013...(?)

When the program is installed on the Windows 7 machine using the installer program, it runs fine.

I checked, and I have .NET 4.5 on my Windows 7 machine.  The Windows 8 machine has .NET 4.5.1.

Both computers are 64 bit.

I found the comctl32.dll in the syswow64 folder on both machines.  On my Windows 7 machine, comctrl32.dll has a "product version" of 6.1.7601.18201 and a "modified date" of July 4, 2013.  On the Windows 8 machine, comctl32.dll has a product version of 6.3.9600.17415 and a modified date of Oct 28, 2014.  So I think the Windows 8 machine has a newer version of comctrl32.dll.
debrunsonAuthor Commented:
Sara:

I think we have found the answer.  It is perhaps related to something you said in your first post.  I was handling the WM_NCCALCSIZE message, to reset the last column's width, depending upon whether a vertical scroll bar was present or not.  I was doing this in the OnNcCalcSize() method:

ModifyStyle(WS_HSCROLL, 0);			     //Remove horizontal scroll bar

RECT Client = lpncsp->rgrc[2];                         //Get the client rectangle from incoming parameter "lpncsp"

LVCOLUMN Col;
GetColumn(nLastCol , &Col);                           //Get characteristics of the last column
Col.mask = LVCF_WIDTH;                                 //Allow change in width of the column
int LastColWd = (Client.right - Client.left) - FirstColsWd;    //Calculate width of last column (client area minus total of other columns)
Col.cx = LastColWd;
SetColumn(nLastCol - 1, &Col);                       //This statement causes the exception in Windows 8.

Open in new window


I simply changed the "SetColumn()" statement above to this one, and it works.  I don't understand why, however.

SetColumnWidth(nLastCol, LastColWd));

Open in new window

debrunsonAuthor Commented:
Sorry, the statement that caused the Win 8 exception was:

SetColumn(nLastCol, &Col);

Open in new window

debrunsonAuthor Commented:
Well, the plot thickens.  The changes indicated above do prevent the program from crashing in Windows 8.  However, they also prevent me from clicking on the last entry in the list control.   Here is the complete code from the OnNcCalcSize handler:

void CListCtrlEx::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
  //Hide the scroll bars to reflect caller's wishes 
  //via previously set member var mHideSB.
  BOOL ResetLastCol = FALSE;
  switch (mHideSB)
  {
    case SB_HORZ:
	ModifyStyle(WS_HSCROLL, 0);	   //Remove horizontal scroll bar
	ResetLastCol = TRUE;
	break;
    case SB_VERT:
	ModifyStyle(WS_VSCROLL, 0);	   //Remove vertical scroll bar
	break;
    case SB_BOTH:
	ModifyStyle(WS_HSCROLL, 0);	   //Remove horizontal scroll bar
	ModifyStyle(WS_VSCROLL, 0);	   //AND vertical scroll bar
	break;
  }

  //Reset the width of the last column if the
  //vertical scroll bar is not hidden.
  if (ResetLastCol)
  {
	RECT Client = lpncsp->rgrc[2];
	int nCount = GetHeaderCtrl()->GetItemCount();

	//Add up the total width of all but the last column.
	int TotWd = 0;
	for (int i = 0; i < nCount - 1; i++)
  	  TotWd += GetColumnWidth(i);

	//Set the last column width equal to the client area minus
	//the total width of the first 'n-1' columns, where 'n' is
	//the total number of columns.
	int WdLastCol = (Client.right - Client.left) - TotWd;

	LVCOLUMN Col;
	GetColumn(nCount - 1, &Col);
	Col.mask = LVCF_WIDTH;
	Col.cx = (Client.right - Client.left) - TotWd;

        //The following call causes the exception in Windows 8,
        //but works in Windows 7.
	SetColumn(nCount - 1, &Col);            

        //If the statement immediately above is replaced with this,
        //the program does NOT crash Windows 8, but I cannot click
        //(select) the last line of the list control.
        // SetColumnWidth(nCount-1, WdLastCol);
    }

    CListCtrl::OnNcCalcSize(bCalcValidRects, lpncsp);
}

Open in new window

ZoppoCommented:
There's one thing which you'll IMO need to take care: in you're OnNcCalcSize you assume rgrc[2] is valid, but in the MSDN article about OnNcCalcSize (https://msdn.microsoft.com/de-de/library/bxb29894.aspx) you can find that The rgrc[1] and rgrc[2] rectangles are valid only if bCalcValidRects is TRUE.
sarabandeCommented:
BOOL bCalcValidRects
your code didn't check for this input argument?

the docs say:
The rgrc[1] and rgrc[2] rectangles are valid only if bCalcValidRects is TRUE.
and
The default implementation calculates the size of the client area based on the window characteristics (presence of scroll bars, menu, and so on), and places the result in lpncsp.
and
This member function is called by the framework to allow your application to handle a Windows message. The parameters passed to your function reflect the parameters received by the framework when the message was received. If you call the base-class implementation of this function, that implementation will use the parameters originally passed with the message and not the parameters you supply to the function.

actually i don't see any of these points being considered in your code.

why did you hide the horizontal scrollbar before calculating the width?

from the calculation it seems to me that you actually don't want a horizontal scrollbar. why don't you remove the horizontal scrolling in the resources or before creating the list control? then the width of the last column easily can be calculated before the list control initially was shown. i prefer to already reserve the space for a vertical scrollbar (or even show it grayed) and so never have to recalculate beside after a WM_SIZE or WM_SIZING message of the main frame.

generally, the WM_NCxxxxx messages are very late messages what means that the message handling for the last action which caused a resize is nearly finished. if you call functions like ModifyStyle(WS_HSCROLL, 0) you make all message handling and calculations done before sending WM_NCCALCSIZE worthless.

Sara
sarabandeCommented:
The rgrc[1] and rgrc[2] rectangles are valid only if bCalcValidRects is TRUE.
sorry, Zoppo, i didn't refresh and didn't see that you already mentioned this.

Sara
ZoppoCommented:
No problem :o)
debrunsonAuthor Commented:
Friends:

Thanks, I can definitely incorporate the three points that you have made.  However, let me explain more about what I am trying to do, maybe there is a better way.  The width and height of the list control never change.  The list consists of four columns of data, all of known width.  The columns are sized to the proper width for the data in each.  I want the horizontal scroll bar always to be hidden (never shown).  It is a single selection, no-sort list control.  However, the vertical scroll bar must appear or disappear as needed.  If the user adds only a few lines of data, there is no vertical scroll bar.  However if they add several more lines, beyond the number of visible items in the list control, the vertical scroll bar appears.  However, I believe that I need to set the width of the last column depending upon whether or not the scroll bar is present.  I was having a lot of problems with the list control looking nice if I don't do this.

I don't know how to eliminate just one scrollbar (horizontal) by settings in the resource editor.  Looks like I have to eliminate both, or neither one.  This is what led me down the path of handling the OnNcCalcSize() message.  It's the only message that gets called by the framework when a scroll bar appears or disappears, so I thought it was the perfect time to a) hide the horizontal scroll bar only and b) adjust the last column's width depending upon whether the vertical scroll bar is there or not.  I would rather not have a narrow fifth empty column showing if I can avoid it.  Can you suggest a better way to accomplish my goals?

Many thanks.
sarabandeCommented:
yes, you may use the following member function for to adjust the column widths:

struct LVDEF
{
     int col;
     int wid;
};

void YourDialogOrFormView::AdjustListview()
{
     static LVDEF coldef[] =
     {
          { 0, 25, },
          { 1, 50, },
          { 2, 35, }, 
          { 3, 40, },
     };

     int ncols   = sizeof(coldef)/sizeof(coldef[0]);
     int ntotal  = 0;
     for (int n = 0; n < ncols; ++n)
            ntotal += coldef[n].wid;
     CRect rect;
     m_list1.GetClientRect(rect);
     int nwid = rect.right;
     int nsum = 0;
     for (int n = 0; n < ncols-1; ++n)
     {
            int wid = (coldef[n].wid*nwid)/ntotal;
            m_list1.SetColumnWidth(coldef[n].col, wid);
            nsum += wid;
     }
     m_list1.SetColumnWidth(coldef[ncols-1].col,  nwid - nsum);
}

Open in new window


if you call the function after each InsertItem and each DeleteItem the list control always is properly sized (and there is never a horizontal scrollbar).

of course you may enhance the LVDEF structure by text, and format members and use it also for to create the columns in OnInitDialog or OnInitialUpdate.

Sara

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
debrunsonAuthor Commented:
Yes, that's great.  I don't know why I didn't try that in the first place.  You have saved me again.  Thank you.

Deighton
debrunsonAuthor Commented:
The fog was too thick.  Sara turned on the fog lamps for me.
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
C++

From novice to tech pro — start learning today.