?
Solved

MFC: CListCtrl derived class tutorial problem

Posted on 2007-07-30
31
Medium Priority
?
4,484 Views
Last Modified: 2013-12-14
Hello,

I'm following this tutorial for MFC/C++: http://www.codeproject.com/listctrl/creportctrl.asp . This concerns implementing a CListCtrl-derived class with certain "out of the box" functionality that the MFC "report style" CListCtrl lacks. For example, the user can re-order items by clicking on a column header, and, optionally, the programmer can include a checkbox beside nodes.

I created a dialog that uses this CReporCtrl, but that didn't work -  I could see entries, but there were no headings, and no lines, even though I'd called the SetGridLines() member function.

I then called ModifyStyle(0,LVS_REPORT), which is meant to be used with the CListCtrl base class. Now, CReportCtrl was mostly fully functional. The column headers were visible. When I called SetGridLines(TRUE), gridlines appeared. However, one last important piece of functionality was still missing: I couldn't re-order column entries by clicking on a column header, in the style of windows explorer.

I'd be very greatly if someone could point me in the right direction,

Thanks a lot,
Sternocera
0
Comment
Question by:sternocera
  • 15
  • 13
  • 3
31 Comments
 
LVL 22

Expert Comment

by:mahesh1402
ID: 19593693
look at sort styles
http://msdn2.microsoft.com/en-us/library/ms670561.aspx

along with LVS_REPORT you can modify that to set sort order :
ModifyStyle(0,LVS_REPORT | LVS_SORTASCENDING );

-MAHESH
0
 
LVL 22

Expert Comment

by:mahesh1402
ID: 19593893
About grids :
you may set extended style LVS_EX_GRIDLINES using SetExtendedStyle or ModifyStyleEx

e.g
ctrl.SetExtendedStyle(LVS_EX_GRIDLINES);

-MAHESH
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19598791
The actual sorting - well you have to code that yourself.  All you get is an event that the suer has clicked the header.
Have you used the 'class wizard'  to add an event handler for you?
Is it actually being called ?     ;-)
0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 

Author Comment

by:sternocera
ID: 19598911
I know that is ordinarily the case Andy, but my understanding of the tutorial was, I simply had to do this:

void CLustreView::DoDataExchange(CDataExchange* pDX)
{
      CFormView::DoDataExchange(pDX);
      DDX_Control(pDX, IDC_LIST1, SampleReportCtrl);
}

and all the message handling would be delegated to the supplied class. Isn't it so?

Regards,
Sternocera
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19598978
That gives you a member variable, of type CListCtrl (I assume) that is called SampleReportCtrl.
0
 

Author Comment

by:sternocera
ID: 19598984
SampleReportCtrl is a variable that I instantiated of type CReportCtrl, which is a sub-class of CListCtrl with the extra "out-of-the-box" functionality that I've described.
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19599025
Programming windows with MFC, second edition.  author - Jeff Prosise

It will be money well spent.




Nope - the functionality is there no matter if you have a variable mapped to the control or not.  It would be nice that one just had to think and the program does what you think but that is going to be a few years before it happens.  Currently one has to write some code oneself.
The wizards help you in giving a foundation, but that is all it is, a foundation rather than a hole in the ground.  Not a house.
0
 
LVL 22

Expert Comment

by:mahesh1402
ID: 19599066
You may also look into basic classwizard reference :
http://msdn2.microsoft.com/en-us/library/aa461412.aspx

I will suggest you to look at functionx tutorials for using controls
( difference : step by step explanation along with snap shots ) : http://www.functionx.com

e.g for your list control see :
http://www.functionx.com/visualc/controls/listcontrol.htm


-MAHESH
0
 

Author Comment

by:sternocera
ID: 19599280
Andy,

There is no need for sarcasm. Granted, I'm a novice programmer who asks a lot of questions, but sometimes the only way one can answer a specific question is by asking someone who knows, rather then by refering to documentation. For example, yesterday,I knew a member function of a ListCtrl set its style - I just didn't know which one, because MSDN documentation is generally just a rote discription of each function rather then an overview of how someone might use the class's functionality, like a book might give. I have a 1400 page MFC book - Ivor horton's beginning visual C++ 2005, and I've read it.

I'm surprised how scant MFC documentation is.

I know how to use class wizard.

To be honest, I am a bit of an interloper. I undertook to create a particular application, essentially a database frontend. However, I'm actually pretty close to having implemented my basic user interface. All that remains is to create a bunch of views and forms with handler functions that use a database API - figuring out the logic of the program, which won't be as challenging, as the API fairly well encapsulates everything.

I'm sorry if holding my hand hasn't been particularly personally satisfying for the last number of days. Your help has been absolutely invaluable to me.

Regards,
Sternocera
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19599332
I didn't mean to be sarcastic - I was trying to give an analogy.

I agree - ask questions and learn.  However if the foundation is missing then you could be building on sand.  If you don't have a basic understanding of how MFC (and windows programs) functions then you might not ask the questions that will actually help you.

The boook I mentioned is specific to MFC.  (OK - not the newest MFC but 98% of it hasn't changed since the book was written).
0
 

Author Comment

by:sternocera
ID: 19599426
Perhaps I didn't explain this particular problem very well (don't think I've ignored what you said and am once again trying to get you to do the work - I've absolutely taken what you've said to heart. In all sincerity, I was actually quite upset by it, because you were mostly right and I have quite a lot personally invested in this undertaking).

I thought that by mapping my CReportCtrl class (subclass of CListCtrl with extra bells and whistles) in the way described, I could delegate messages to the class, and use its message handling functions, which are mapped thusly in the ReportCtrl class:

ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnClick)

Is it not so?

Thanks
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19601911
That ought to be the case.
Have you checked that the function is actually being called when you click on a column header?
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19601949
Also - what has the data that the list control displays?  That is where one probably should be doing any sorting.
0
 

Author Comment

by:sternocera
ID: 19607156
Andy,

I can confirm that the function(OnColumnClick) is called when I click on a column header. The list control is populated with filler data:

(this is from my view's OnInitialUpdate. Remember, this isn't a standard CListCtrl, so some member functions won't be familiar to you)
      SampleReportCtrl.SetGridLines(TRUE);
      SampleReportCtrl.SetCheckboxes(TRUE);
      SampleReportCtrl.ModifyStyle(0,LVS_REPORT); // In the tutorial, it isn't nessecary to set style by calling this function. I've had to do it, otherwise nothing is displayed. I don't know why.
      SampleReportCtrl.SetHeadings(_T("column1, 90; column2, 90; column3, 90"));
      SampleReportCtrl.InsertItem(0, "test1");
      SampleReportCtrl.InsertItem(1, "test2");
      SampleReportCtrl.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER);
      SampleReportCtrl.SetColumnWidth(1, LVSCW_AUTOSIZE_USEHEADER);
      SampleReportCtrl.SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER);

Here is the function that is mapped to LVN_COLUMNCLICK:

void CReportCtrl::OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
      NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
      const int iColumn = pNMListView->iSubItem;

      // if it's a second click on the same column then reverse the sort order,
      // otherwise sort the new column in ascending order.
      Sort(iColumn, iColumn == m_wndHeader.GetSortedColumn() ? !m_wndHeader.IsSortAscending() : TRUE);

      *pResult = 0;
}
Here is the sort function it calls:

void CReportCtrl::Sort(int iColumn, BOOL bAscending)
{
      if (iColumn < 0 || iColumn >= GetColumnCount())
            return;
      
      m_wndHeader.SetSortedColumn(iColumn);
      m_wndHeader.SetSortAscending(bAscending);

      // show the appropriate arrow in the header control.
      m_wndHeader.UpdateSortArrow(); // my corresponding header control

      VERIFY(SortItems(_CompareFunction, reinterpret_cast<DWORD>(this)));
}

and the functions that sort calls:

void CReportCtrl::CReportHeaderCtrl::SetSortedColumn(int nCol)
{
      m_iSortColumn = nCol;
}

and

void CReportCtrl::CReportHeaderCtrl::SetSortAscending(BOOL bAscending)
{
      m_bSortAscending = bAscending;
}

m_iSortColumn is an int initialised as -1, and m_bSortAscending a BOOL initialised as TRUE.

Thanks a lot,
Sternocera
0
 

Author Comment

by:sternocera
ID: 19607335
Lastly, here is the _CompateFunction function:

int CALLBACK CReportCtrl::_CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamData)
{
      CReportCtrl* pListCtrl = reinterpret_cast<CReportCtrl*>(lParamData);
      ASSERT(pListCtrl->IsKindOf(RUNTIME_CLASS(CListCtrl)));

      ItemData* pid1 = reinterpret_cast<ItemData*>(lParam1);
      ItemData* pid2 = reinterpret_cast<ItemData*>(lParam2);

      ASSERT(pid1);
      ASSERT(pid2);

      CString s1 = pid1->arrStr[ pListCtrl->m_wndHeader.GetSortedColumn()];
      CString s2 = pid2->arrStr[ pListCtrl->m_wndHeader.GetSortedColumn()];

      s1.TrimLeft();
      s1.TrimRight();
      s2.TrimLeft();
      s2.TrimRight();

      if (s1.IsEmpty() || s2.IsEmpty())
            return pListCtrl->m_wndHeader.IsSortAscending() ? _StringCompare(s1, s2) : _StringCompare(s2, s1);

      if(_IsNumber(s1))
            return pListCtrl->m_wndHeader.IsSortAscending() ? _NumberCompare(s1, s2) : _NumberCompare(s2, s1);
      else if(_IsDate(s1))
            return pListCtrl->m_wndHeader.IsSortAscending() ? _DateCompare(s1, s2) : _DateCompare(s2, s1);
      else
            // text.
            return pListCtrl->m_wndHeader.IsSortAscending() ? _StringCompare(s1, s2) : _StringCompare(s2, s1);
}

Thanks a lot,
Sternocera
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19609667
I don't see what 'SortItems' is

What is the data - text, date, numeric...
0
 

Author Comment

by:sternocera
ID: 19609748
Sortitems is an overloaded function:

BOOL CGridCtrl::SortItems(int nCol, BOOL bAscending, LPARAM data /* = 0 */)
{
    SetSortColumn(nCol);
    SetSortAscending(bAscending);
    ResetSelectedRange();
    SetFocusCell(-1, - 1);

      if (m_pfnCompare == NULL)
            return CGridCtrl::SortItems(pfnCellTextCompare, nCol, bAscending, data);
      else
          return CGridCtrl::SortItems(m_pfnCompare, nCol, bAscending, data);
}

// Sorts on a given column using the supplied compare function (see CListCtrl::SortItems)
BOOL CGridCtrl::SortItems(PFNLVCOMPARE pfnCompare, int nCol, BOOL bAscending,
                          LPARAM data /* = 0 */)
{
    SetSortColumn(nCol);
    SetSortAscending(bAscending);
    ResetSelectedRange();
    SetFocusCell(-1, -1);
    return SortItems(pfnCompare, nCol, bAscending, data, GetFixedRowCount(), -1);
}

The data can be text, date or numeric. The CReportCtrl class is supposed to be flexible like that.

Thanks,
Sternocera
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19615121
PLease don't take offence but I see some code being busy but not actually doing anything.  You are COMPARING items.

I suspect HALF the problem is covered here.
http://www.experts-exchange.com/Programming/Editors_IDEs/C_CPP_CS/Visual_CPP/Q_22728297.html

Sorry to be cryptic but if you look at the link it will hopefully give you an idea.



ps.  I might be bing dumb but I don't see each item in the list being checked.
0
 

Author Comment

by:sternocera
ID: 19615352
Andy,

This post was for a generic CListCtrl, as apposed to for this particular tutorial.

For whatever reason, this thing doesn't work for me. I've probably failed to take account of something very trivial. I know it works, because there is a demo application. It must be something to do with compiler incompatibilities, because this code was written in visual studio 6.

I think I've found something better: http://www.codeproject.com/miscctrl/gridctrl.asp . It has a lot of additional functionality that I consider valuable too. It's a custom, Cwnd derived class. Just the kind of flexibility I value,

Thanks for everything,
Sternocera
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19616090
I've been having another look at the code - I think I got distracted and made a silly comment. The SortItems is a member of the list control and it does look to be being called correctly.
Did you check that the function CReportCtrl::_CompareFunction was actually being called?  (You might need to have the properties of the list control set to allow sorting).
0
 

Author Comment

by:sternocera
ID: 19616378
I've apended a messagebox function call to _CompareFunction:

int CALLBACK CReportCtrl::_CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamData)
{
      AfxMessageBox("_CompareFunction called");
      CReportCtrl* pListCtrl = reinterpret_cast<CReportCtrl*>(lParamData);
      ASSERT(pListCtrl->IsKindOf(RUNTIME_CLASS(CListCtrl)));

      ItemData* pid1 = reinterpret_cast<ItemData*>(lParam1);
      ItemData* pid2 = reinterpret_cast<ItemData*>(lParam2);

      ASSERT(pid1);
      ASSERT(pid2);

      CString s1 = pid1->arrStr[ pListCtrl->m_wndHeader.GetSortedColumn()];
      CString s2 = pid2->arrStr[ pListCtrl->m_wndHeader.GetSortedColumn()];

      s1.TrimLeft();
      s1.TrimRight();
      s2.TrimLeft();
      s2.TrimRight();

      if (s1.IsEmpty() || s2.IsEmpty())
            return pListCtrl->m_wndHeader.IsSortAscending() ? _StringCompare(s1, s2) : _StringCompare(s2, s1);

      if(_IsNumber(s1))
            return pListCtrl->m_wndHeader.IsSortAscending() ? _NumberCompare(s1, s2) : _NumberCompare(s2, s1);
      else if(_IsDate(s1))
            return pListCtrl->m_wndHeader.IsSortAscending() ? _DateCompare(s1, s2) : _DateCompare(s2, s1);
      else
            // text.
            return pListCtrl->m_wndHeader.IsSortAscending() ? _StringCompare(s1, s2) : _StringCompare(s2, s1);
}

I never see any message box, so it would seem _CompareFunction is never called. I saw a post on the bottom of that page dated 2005 (post release of visual studio 2005) from a guy who had exactly the same problem as me.  I'm going to see how this works in visual studio 6. I strongly suspect it'll work,

Thanks,
Sternocera
0
 
LVL 45

Accepted Solution

by:
AndyAinscow earned 2000 total points
ID: 19616471
There is a list control property - sort - the default is none.  It might need to be set to ascending or descending before the SortItems functionality is activated.  Check that first.
0
 

Author Comment

by:sternocera
ID: 19616597
Andy,

I've tried that:
      SampleReportCtrl.Sort(0,TRUE);
      SampleReportCtrl.Sort(1,TRUE);
      SampleReportCtrl.Sort(2,TRUE);
      SampleReportCtrl.Sort(3,TRUE);
Unfortunately, it didn't make any difference. I'm going to try getting it to compile in vs6. I'll keep you posted,
Thanks,
Sternocera
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19616682
No - not this special class.
In the resource editor, check the properties of the list control itself.
0
 

Author Comment

by:sternocera
ID: 19616809
The plot thickens.

I changed the sort setting. Nothing happened. I decided to absolutely reconcile my my list with the list in the demo. I changed the type from icon to report, which I think was the pertinent bit.

I got this error message in debug mode: Unhandled exception at 0x7814455c in Lustre.exe: 0xC0000005: Access violation reading location 0x488e0475.

this occured here:
      for(int iColumn = 1; iColumn < GetColumnCount(); iColumn++)
      {
            LPCTSTR lpsz = va_arg(list, LPCTSTR);
            CString str = (lpsz == NULL) ? _T("") : lpsz;
            arr.Add(str); // on this line
            CListCtrl::SetItemText(iIndex, iColumn, str);
      }

This might have something to do with character encoding, I don't know.

Thanks for everything,
Sternocera
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19616857
? ulp.

is this giving a valid string?
        LPCTSTR lpsz = va_arg(list, LPCTSTR);
0
 

Author Comment

by:sternocera
ID: 19617017
here is the whole function:

int CReportCtrl::InsertItem(int nIndex, LPCTSTR pszText, ...)
{
      const int iIndex = CListCtrl::InsertItem(nIndex, pszText);

      if (!_IsValidIndex(iIndex))
            return iIndex;

      CStringArray arr;
      arr.Add(pszText);

       va_list list;
      va_start(list, pszText);

      for(int iColumn = 1; iColumn < GetColumnCount(); iColumn++)
      {
            LPCTSTR lpsz = va_arg(list, LPCTSTR);
            CString str = (lpsz == NULL) ? _T("") : lpsz;
            arr.Add(str);
            CListCtrl::SetItemText(iIndex, iColumn, str);
      }

      va_end(list);

      _AssignNewItemData(iIndex, arr.GetData(), arr.GetSize());

      return iIndex;
}

I've looked into it. Apparently, va_list is a standard c datatype that is passed as an argument to va_start, va_end, and, in our case, va_arg.

va_arg is a macro that retrieves the value of an argument from a variable argument list.

http://www.cplusplus.com/reference/clibrary/cstdarg/va_arg.html

(you probably already know all this, I'm aware)

I guess this means that the string ultimately comes from the argument that is padded to InsertItem.

Frankly, I'm stumped. I tried populating my listctrl with the _T() function rather then a straight string literal, but that made no difference either.

Over to you, Andy...

Thanks
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 19617113
That looks like you have a function that adds the main item and the sub items in one go

eg.
InsertItem(1, "col 1", "col 2", "col 3");

Are you passing in the same number of strings as there are columns?
0
 

Author Comment

by:sternocera
ID: 19617235
I Insert columns using a mechanism defined by the class:
SampleReportCtrl.SetHeadings(_T("column1, 90; column2, 90; column3, 90"));

I guess there is code somewhere to parse the string.

However, I insert items in this way:
SampleReportCtrl.InsertItem(0, _T("test1"));
SampleReportCtrl.InsertItem(1, _T("test2"));
SampleReportCtrl.InsertItem(2, _T("test3"));

InsertItem is an overloaded function that is defined in the supplied header file. It would seem they were intended to be used together.
0
 

Author Comment

by:sternocera
ID: 19617376
I tried changing:
SampleReportCtrl.SetHeadings(_T("column1, 90; column2, 90; column3, 90"));

to

SampleReportCtrl.SetHeadings(_T("column1, 90; column2, 90; column3, 90;"));

the extra ";" didn't make any difference.
0
 

Author Comment

by:sternocera
ID: 19623295
Andy,

I've found a solution! I found that by calling a different overloaded SampleReportCtrl, everything works nicely:
SampleReportCtrl.InsertItem(0, _T("test1"),0);

However, since your help led me down a path of reasoning that led to my finding the answer, I think that you should get the 500 points.

Thanks alot
0

Featured Post

Veeam and MySQL: How to Perform Backup & Recovery

MySQL and the MariaDB variant are among the most used databases in Linux environments, and many critical applications support their data on them. Watch this recorded webinar to find out how Veeam Backup & Replication allows you to get consistent backups of MySQL databases.

Question has a verified solution.

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

If you use Adobe Reader X it is possible you can't open OLE PDF documents in the standard. The reason is the 'save box mode' in adobe reader X. Many people think the protected Mode of adobe reader x is only to stop the write access. But this fe…
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.
The viewer will learn how to use NetBeans IDE 8.0 for Windows to connect to a MySQL database. Open Services Panel: Create a new connection using New Connection Wizard: Create a test database called eetutorial: Create a new test tabel called ee…
The viewer will learn how to use and create new code templates in NetBeans IDE 8.0 for Windows.

850 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