Click here to Skip to main content
Click here to Skip to main content

Using text callbacks in ListView Controls

By , 26 Nov 1999
 
  • Download files - 16 Kb

    If you store you application data and you wish to save some time and memory, you can have the list ask your application for the strings to display in the list on the fly, instead of having the list store the strings explicitly. This will reduce the amount of memory your final app needs and will make filling and deleting the list contents quicker, at the expense of slightly slower display time. (If you are displaying several thousand items in the list, the time saved in filling and cleaning the list more than makes up for fractionally slower display). Furthermore, list sorting can become lightning fast.

    Suppose your data is stored in your app in structures "ItemStruct" declared:

    typedef struct {
    	int nItemNo;
    	CString strName;
    } ItemStruct;
    

    and each structure is stored in an array, list or map. The easiest way to let the list know about each item is to store a pointer to each item in the lParam field of the LV_ITEM structure you pass to the CListCtrl::InsertItem function.

    Firstly, when you add a new item to the list you should set the LVIF_PARAM bit in the mask field of your LV_ITEM structure that you are using. This lets the list know that the lParam field of your LV_ITEM contains data.

    You may want to create a helper function to add a new item to the list like:

    BOOL CMyListCtrl::AddItem(ItemStruct* pItem, int nPos /* = -1 */)
    {
    	int nNumItems = GetItemCount();
    	int nInsertPos = (nPos >= 0 && nPos <= nNumItems)? nPos : nNumItems;
    
    	LV_ITEM Item;
    	Item.lParam	  = (LPARAM) pItem;		// Store the pointer to the data
    	Item.pszText  = LPSTR_TEXTCALLBACK;		// using callbacks to get the text
    	Item.mask	  = LVIF_TEXT | LVIF_PARAM;	// lParam and pszText fields active
    	Item.iItem	  = nInsertPos;			// position to insert new item
    	Item.iSubItem = 0;
    	
    	if (InsertItem(&Item) < 0) 
    		return FALSE;					
    	else
    		return TRUE;
    }
    

    The LPSTR_TEXTCALLBACK value for the pszText field tells the list that it should use callbacks to get the text to display, instead of storing the text itself. Every time the list needs to display text, it sends a LVN_GETDISPINFO. We don't add text for the subitems, since they will be dealt with in the LVN_GETDISPINFO handler as well.

    You need to handle the windows notification LVN_GETDISPINFO using the following:

    BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
    	//{{AFX_MSG_MAP(CMyListCtrl)
    	ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetDispInfo)
    	//}}AFX_MSG_MAP
    END_MESSAGE_MAP()
    

    and have an accompanying function declared in you class definition:

    class CMyListCtrl 
    {
    // .. other declarations
    
    // Generated message map functions
    protected:
    	//{{AFX_MSG(CMyListCtrl)
    	afx_msg void OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult);
    	//}}AFX_MSG
    };
    

    The handler function should look something like:

    void CMyListCtrl::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult) 
    {
    	LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
    
    	if (pDispInfo->item.mask & LVIF_TEXT)
    	{
    		ItemStruct* pAppItem = (ItemStruct*) pDispInfo->item.lParam;
    		CString strField = QueryItemText(pDispInfo->item.iItem, 
    							   pDispInfo->item.iSubItem);
    		LPTSTR pstrBuffer = AddPool(&strField);	
    
    		pDispInfo->item.pszText = pstrBuffer;
    	}	
    	*pResult = 0;
    }
    

    The NMHDR variable contains the list view display info, which in turn holds a LV_ITEM member specifying the item and subitem it's after, and what sort of info it's after. In our case we are only specifying text, so we only deal with notifications where the pDispInfo->item.mask equals LVIF_TEXT. We get a pointer to our Apps data through the lParam field of LV_ITEM, and from this we get a text representation of our data (I'm using another helper function "QueryItemText" implemented as

    // returns a CString representation for the data in row nRow, column nCol
    CString CMyListCtrl::QueryItemText(int nRow, int nCol)
    {
    	CString strField;
    	ItemStruct* pAppItem = (ItemStruct*) GetItemData(nRow);
    	if (!pAppItem) return strField;
    
    	switch (nCol) {
    		case 0:	strField.Format("%d",pAppItem->nItemNo);	break;
    		case 1: strField = pAppItem->strName;			break;
    		default: ASSERT(FALSE);
    	}
    
    	return strField;
    }
    

    The main reason I use the "QuesryItemText" function is that since we are now using text callbacks, functions like CListCtrl::GetItemText no longer work. If you use GetItemText (for instance in TitleTips or InPlaceEdit's) then you must replace them with QueryItemText in order for them to work. The two lines:

    	LPTSTR pstrBuffer = AddPool(&strField);	
    	pDispInfo->item.pszText = pstrBuffer;
    

    in the OnGetDispInfo function were taken from Mike Blaszczak's program "ApiBrow" (get it either online or from his book "Proffessional MFC with Visual C++ 5"). It handles the bizarre situtation that the list control requires the buffer you pass back from the OnGetDispInfo to be valid for 2 more LVN_GETDISPINFO notifications. His solution was a simple caching to store sufficient copies so the list control doesn't have a cow. It goes something like this:

    LPTSTR CMyListCtrl::AddPool(CString* pstr)
    {
    	LPTSTR pstrRetVal;
    	int nOldest = m_nNextFree;
    
    	m_strCPool[m_nNextFree] = *pstr;
    	pstrRetVal = m_strCPool[m_nNextFree].LockBuffer();
    	m_pstrPool[m_nNextFree++] = pstrRetVal;
    	m_strCPool[nOldest].ReleaseBuffer();
    
    	if (m_nNextFree == 3)
    		m_nNextFree = 0;
    	return pstrRetVal;
    }
    

    You will need to declare the protected attributes

    	CString m_strCPool[3];
    	LPTSTR  m_pstrPool[3];
    	int     m_nNextFree;
    

    in your class definition.

    And that's all there is to it!

    One advantage of storing the data in the lParam field is that sorting become very quick. A column sort function as already been provided in in the MFC programmers sourcebook. It is prototyped:

    BOOL CMasterListCtrl::SortTextItems(int nCol, BOOL bAscending, 
    					int low /*= 0*/, int high /*= -1*/ );
    

    I will ignore the high and low parameters (sorry!) and instead implement a different version: (see the docs on CListCtrl::SortItems for details)

    typedef struct {
    	int nColumn;
    	BOOL bAscending;
    } SORTINFO;
    
    BOOL CMasterListCtrl::SortTextItems(int nCol, BOOL bAscending)
    {
    	CWaitCursor waiter;
    
    	SORTINFO si;
    	si.nColumn = m_nSortColumn;
    	si.bAscending = m_bSortAscending;
    	return SortItems(CompareFunc, (LPARAM) &si);
    }
    
    int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
    {
    	ItemStruct *pItem1 = (ItemStruct*) lParam1;
    	ItemStruct *pItem2 = (ItemStruct*) lParam2;
    	SORTINFO* pSortInfo = (SORTINFO*) lParamSort;
    	ASSERT(pItem1 && pItem2 && pSortInfo);
    
    	int result;
    	switch (pSortInfo->nColumn)
    	{
    		case 0: 
    		  if (pItem1->nItemNo < pItem2->nItemNo) 
    			result = -1;
    		  else if (pItem1->nItemNo == pItem2->nItemNo) 
    			result = 0;
    		  else 
    			result = 1;
    		  break;
    
    		case 1: 
    		  result = pItem1->strName.Compare(pItem2->strName); 
    		  break;
    
    		default:
    		  ASSERT(FALSE);
    	}
    
    	if (!pSortInfo->bAscending) 
    	  result = -result;
    
    	return result;
    }
    
  • License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

    About the Author

    Chris Maunder
    Founder CodeProject
    Canada Canada
    Member
    Chris is the Co-founder, Administrator, Architect, Chief Editor and Shameless Hack who wrote and runs The Code Project. He's been programming since 1988 while pretending to be, in various guises, an astrophysicist, mathematician, physicist, hydrologist, geomorphologist, defence intelligence researcher and then, when all that got a bit rough on the nerves, a web developer. He is a Microsoft Visual C++ MVP both globally and for Canada locally.
     
    His programming experience includes C/C++, C#, SQL, MFC, ASP, ASP.NET, and far, far too much FORTRAN. He has worked on PocketPCs, AIX mainframes, Sun workstations, and a CRAY YMP C90 behemoth but finds notebooks take up less desk space.
     
    He dodges, he weaves, and he never gets enough sleep. He is kind to small animals.
     
    Chris was born and bred in Australia but splits his time between Toronto and Melbourne, depending on the weather. For relaxation he is into road cycling, snowboarding, rock climbing, and storm chasing.

    Sign Up to vote   Poor Excellent
    Add a reason or comment to your vote: x
    Votes of 3 or less require a comment

    Comments and Discussions

     
    You must Sign In to use this message board.
    Search this forum  
        Spacing  Noise  Layout  Per page   
    GeneralI can't get this to work in a Windows Explorer architecture project...memberBen Aldhouse24 Jun '09 - 11:10 
    I'm looking to update my Creating a Simple Drives Explorer Program[^] using callback functions.
     
    I've made a simple Windows Explorer project and adapted the above code as shown below. But it crashes with 'Unhandled Exception... Access Violation' at the following conditional...
     
    const CString& CString::operator=(const CString& stringSrc)
    {
    	if (m_pchData != stringSrc.m_pchData)
     
    I've got...
     
    void CRightView::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult) 
    {
    	LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
    	// TODO: Add your control notification handler code here
    
       if (pDispInfo->item.mask & LVIF_TEXT)
       {
     	   // The lParam field contains a pointer to our application specific
    	   // data (in this case a CListItem). We need to get the string representation
    	   // of this data, add it to the pool and return a pointer to the pooled
    	   // string.
    
    	   CListItem* pItem = (CListItem*) pDispInfo->item.lParam;
    	   if (!pItem) return;
     
           CString strField;
    	   switch (pDispInfo->item.iSubItem) 
    	   {
    			case 0: strField = pItem->szFrom;    break;
    			case 1: strField = pItem->szSubject; break;
    			case 2: strField = pItem->szDate;    break;
    			default: ASSERT(FALSE);
    	   }
     
           LPTSTR pstrBuffer = AddPool(&strField); //AddPool(strField); 
    
           pDispInfo->item.pszText = pstrBuffer;
       }       
    	
    	*pResult = 0;
    }
     
    LPTSTR CRightView::AddPool(CString *pstr)
    {
    	LPTSTR pstrRetVal;
    	int nOldest = m_nNextFree;
     
    	m_strCPool[m_nNextFree] = *pstr;
    	pstrRetVal = m_strCPool[m_nNextFree].LockBuffer();
    	m_pstrPool[m_nNextFree++] = pstrRetVal;
    	m_strCPool[nOldest].ReleaseBuffer();
     
    	if (m_nNextFree == 3)
    		m_nNextFree = 0;
    	return pstrRetVal;
     
    }
     
    BOOL CRightView::Add(CString szFrom, CString szSubject, CString szDate, int nPos)
    {
     
    
    	CListCtrl &ctlRightView = this->GetListCtrl();
    	int nNumItems = ctlRightView.GetItemCount();
        int nInsertPos = (nPos >= 0 && nPos <= nNumItems)? nPos : nNumItems;
    	
    	// Create a new ListItem object and fill its contents
    	CListItem* pItem = new CListItem;
    	if (!pItem) return FALSE;
     
    	pItem->szFrom = szFrom;
    	pItem->szSubject = szSubject;
    	pItem->szDate = szDate;
     
        LV_ITEM Item;
        Item.lParam   = (LPARAM) pItem;				// Store the pointer to the object
        Item.pszText  = LPSTR_TEXTCALLBACK;         // using callbacks to get the text
        Item.mask     = LVIF_TEXT | LVIF_PARAM;     // lParam and pszText fields active
        Item.iItem    = nInsertPos;                 // position to insert new item
        Item.iSubItem = 0;
            
        if (ctlRightView.InsertItem(&Item) < 0) 
           return FALSE;                                   
        else
           return TRUE;
     
    }
     
    and...
     
     
    void CLeftView::OnInitialUpdate()
    {
    	CTreeView::OnInitialUpdate();
     
    	// TODO: You may populate your TreeView with items by directly accessing
    	//  its tree control through a call to GetTreeCtrl().
    
    	//SetIcon(m_hIcon, TRUE);			// Set big icon
    	//SetIcon(m_hIcon, FALSE);		// Set small icon
    
        CFTreeCallBkTestDoc *pDoc = GetDocument();
    	
    	CListCtrl &m_List = pDoc->pRightView->GetListCtrl();
    	//pDoc->pRightView->DisplayFiles(strSearchPath.GetBuffer(1));
    	CRightView *myRight = pDoc->pRightView;
    	
     
    	m_List.InsertColumn(0,"From",LVCFMT_LEFT,90);
    	m_List.InsertColumn(1,"Subject",LVCFMT_CENTER,90);
    	m_List.InsertColumn(2,"Date",LVCFMT_RIGHT,90);
     
    	myRight->Add("Me", "Nothing", "today",-1);	
    	myRight->Add("You", "Something", "yesterday",-1);	
    	myRight->Add("Someone", "Something", "sometime",-1);	
     
    }
     
    
     
    Any ideas?
     
    Thanks.
    GeneralUpdating the object datamemberJohn Andzelik3 Sep '08 - 10:02 
    I have a dialog that uses the CListCtrl class. I am using the LV_ITEM structure to store a pointer to my object. Like so:
     
    LV_ITEM* PrepareItem(TestClass* item, int nPos)
    {
    LV_ITEM* lvi = new LV_ITEM();
    lvi->mask = LVIF_TEXT|LVIF_STATE|LVIF_PARAM;
    lvi->iItem = nPos;
    lvi->iSubItem = 0;
    lvi->state = LVIS_SELECTED;
    lvi->stateMask = LVIS_SELECTED;
    lvi->lParam = (LPARAM)item;
    lvi->iImage = 0;
    lvi->pszText = LPSTR_TEXTCALLBACK;
    return lvi;
    }
     
    Add()
    {
    pair<map><int,>::iterator, bool> pr;
    pair<int,> p1;
    pr = map.insert(p1)//I have a map object set as private as part of the dialog class
    LV_ITEM* lvi = PrepareItem(&pr.first->second, list.GetItemCount());
    int nPos = list.InsertItem(lvi);
    }
     
    My problem is when I modify the data within that object like so:
    Save()
    {
    POSITION pos = list.GetFirstSelectedItemPosition();
    int nPos = list.GetNextSelectedItem(pos);
    TestClass *tc = (TestClass*)list.GetItemData(nPos);
    tc->setvalue("hello world");
    m_lcAddressDisplay.SetItemData(nPos, (LPARAM)tc);
    m_lcAddressDisplay.Update(nPos);
    }
     
    The OnGetDispInfo() no longer triggers on the changed index. I looked at the memory, and as far as I can tell it has not changed, but I am unsure as to why the LV_DISPINFO.item.iItem is no longer hitting on my current index? </map>
    QuestionHow do Listview controls store data internally?sussSlimso13 Apr '05 - 11:10 
    Is there a way to access the data stored in a listview and possibly modify it directly?
     
    I know this isn't something anyone would want to do, but if it could be done it would make adding and removing items much faster.
     
    ... And yes, I know all about LVS_OWNERDATA and what it does, but I'm hoping for a different approach.
    GeneralFlickering if using more than 300 itemsmemberBenj1028 Dec '04 - 4:28 

    I am using this functionality and have found that the item list will flicker after selecting
    to different one in the list. The list control has more than 300 items. Any ideal?
     
    thanks

    GeneralTime to set &quot;LPSTR_TEXTCALLBACK&quot;memberv_fsr5 Dec '03 - 9:32 
    In my problem, I take several times seting, for all items, the property "LPSTR_TEXTCALLBACK". Is there a automatic form of set all items at the same time, instead of setting one by one?
    GeneralItem RectmemberBrad Bruce20 Oct '03 - 8:52 
    I have a structure that is queried for each item being displayed. Smile | :)
     
    When I update the text and make it longer or shorter than the original text, the un-selected text displays properly Smile | :) but when the item is selected it displays using the length of the original string. WTF | :WTF:
     
    HELP!!!!!
     
    [edit]
    I've switched to full row select and it looks like it's working. I would be good to find a solution though.
    [/edit]
    GeneralHELP! PLEASE! Dialog &amp; List Control Data ExchangememberCharlieC20 Aug '03 - 9:39 
    Hi,
     
    I have looked at every piece of code I can find and looked at CodeGuru's site as well and have found nothing to show me how to do what I am trying to do.
    I have a CListCtrl on a Dialog. The CListCtrl is fully editable. Now I have placed an Edit box on the dialog. What I am trying to do is to get the Edit box on the Dialog box to fill with the data from whichever subitem I click on in the CListCtrl.
    I have tried everything I can think of to make this happen, but nothing has worked. The closest I have come is using the WM_ONCLICK Message handler. However, the handler only seems to work with the first column. If I click on any other column it still places the data from the first column in the edit box.
    I have been programming Visual C++ 6.0 for several years now so I can usually make things work. However, this one has me stumped.
    So I would appreciate it if someone could help me out. I would really like a piece of code that would show how to accomplish this. So if anyone has played around with this and has come up with a solution I would be very thankful if you could pass it on!
     
    Many Thanks in advance
    Charlie
     
    Confused | :confused:
    GeneralHELP!!membersimonzb7 Aug '03 - 21:02 
    I used a cListCtrl to display 50 rows or so. while these 50 rows data are frequently changed, I am headache about the flashing screen. what should I do to make it not flashing?
     
    The listCtrl is in a formview, and the updateallview(null) were called frequently. Hmmm | :|
    GeneralUpdating LV_ITEM doesn't update in list view display.sussWilliam Welbes1 May '03 - 11:05 
    I have created a CListCtrl that uses the text callbacks to get the text for the list view. However, I have created a function that modifies the text of the items after the items have been added to the control. I can access the items from the control, and update the peritnent information using the LV_ITEM object parameters, however, this data is not changed in the application until the LVN_GETDISPINFO message has been sent and the text callback is called. How can I send that message within my code so that the items are all updated when I make changes to the LV_ITEM. I have tried:
     
    SendMessage(LVN_GETDISPINFO, NULL, NULL);
     
    Any help would be much appreciated Smile | :)
     
    William
    GeneralRe: Updating LV_ITEM doesn't update in list view display.memberBhawnaA2 Jul '07 - 20:53 
    CMyListCtrl* ptrList = (CMyListCtrl*)GetDlgItem(IDC_LIST1);
    if(ptrList != NULL)
    {
    LVITEM* pItem= NULL;
    int nItem = ptrList->FindItem(obj);
    ptrList->RedrawItems(nItem,nItem);
     

    UpdateWindow();
    }
     

    Find the LVITEM position for the CMyClass object instance which is updated. And call redrawIems for that item.
     
    int CMyListCtrl::FindItem(CMyClass* pItem)
    {
    for (int nItem = 0; nItem < GetItemCount(); )
    {
    DWORD temp = GetItemData(nItem);
    CNode *node = (CNode*) temp;
    if((node != NULL) && (dynamic_cast(node) != 0))
    {
    CMyClass *ptrleaf = (CMyClass*)temp;
    if(strcmp(ptrleaf->m_FullName,pItem->m_FullName) == 0)
    {
    return nItem;
    }
    else
    ++nItem;
    }
    }

    return -1;
    }

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    Permalink | Advertise | Privacy | Mobile
    Web01 | 2.6.130523.1 | Last Updated 27 Nov 1999
    Article Copyright 1999 by Chris Maunder
    Everything else Copyright © CodeProject, 1999-2013
    Terms of Use
    Layout: fixed | fluid