Using text callbacks

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

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;
}



Comments

  • Virtual ListCtrl and Curent

    Posted by Legacy on 07/04/2002 12:00am

    Originally posted by: Ashmarti

    What is difference between Vitual List Control and presented here method??? I think that is the same.
    Is not it?

    Reply
  • Something wrong with function AddItem...

    Posted by Legacy on 07/26/2001 12:00am

    Originally posted by: Aleksey Ulitsky

    Hi all,
    Probably in the function AddItem is missing an assignment of the pointer ItemStruct *pItem. It should be saved in the in the item if the list control as it's data. Therefore, I changed last lines like folowing:

    ...........................................//AddItem
    if ( index< 0)
    return FALSE;
    else{

    return SetItemData(index, (DWORD)pItem );
    }
    }///////////////////////////////// end function....

    Sincerely,
    Aleksey Ulitsky

    Reply
  • Test

    Posted by Legacy on 12/01/2000 12:00am

    Originally posted by: Bill

    This is a test post

    Reply
  • Problem:Optimizing

    Posted by Legacy on 12/01/2000 12:00am

    Originally posted by: Cristian Eigel

    I need a CListCtrl that has a large amount of data (from a
    database).The solutions are:
    1. Set the style of the list control to owner data (LVS_OWNERDATA).The problem is that the list control calls
    OnGetDispInfo every time it needs data (even when the mouse moves over an item).If the cache is big, (put into a map)
    searching slows down the application.
    2. Set the text to LPSTR_TEXTCALLBACK and in OnGetDispInfo
    set pDispInfo->item.mask|=LVIF_DI_SETITEM;This makes the
    list to store the data on itself, but only the data it needs.
    When it needs more data it calls OnGetDispInfo.The problem
    is that i need to set LPSTR_TEXTCALLBACK for all items and
    subitems in the list.This take a wile also, and makes the
    poor user(one that doesn`t own a good computer) to wait a while.

    What i need is a combination of the 2 methods.Lets say that
    I set the ItemCount first, and then when the application needs data, the list control to store onto itself, and never ask me again about the same data

    P.S. Sorry for my bad english.

    Reply
  • Simplifying AddPool

    Posted by Legacy on 02/11/2000 12:00am

    Originally posted by: Ken Hommel

    I have a question about the function AddPool, and the two 
    
    data members it accesses. Since these data members are only
    used in this function, why not use local static variables
    instead? Here's my implementation, based on the version
    submitted by Doron Bubbai, and incorporating the fix from
    Kevin W. Brown:

    LPTSTR CListView1View::AddPool(CString& str)
    {
    static CString cStrPool[3];
    static int nextFree = 0;
    LPTSTR pstrRetVal;
    int nOldest = nextFree;

    nextFree++;
    nextFree %= 3;

    cStrPool[nextFree] = str;
    pstrRetVal = cStrPool[nextFree].LockBuffer();
    cStrPool[nOldest].ReleaseBuffer();

    return pstrRetVal;
    } // AddPool

    By the way, I've tried to take the code from this article
    and create a class derived from CListCtrl, and then
    deriving a parallel class to CListView. When I try to
    instantiate this class, the view for the list control is
    not allocated. Any ideas?

    Thanks,
    Ken Hommel


    Reply
  • AddPool - a better solution?

    Posted by Legacy on 08/20/1999 12:00am

    Originally posted by: Dara

    Not being an MFC expert or anything but i'm wondering if the following is a better solution than having this array of CStrings.
    
    

    I've found that the framework seems to call this OnGetDispInfo function numerous times. i use callbacks for text and the associated image. i have 4 columns and this GetDispinfo method is called 10/11 times per item (i still haven't come up with a reason why!). anyway, why not just have a member CString object and keep an index count in the method to know when to release the locked string object. here is the code i present for your judgement and more likely criticism (i'm sure this code could be tightened up?).


    void CMyRightView::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
    {
    LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
    // TODO: Add your control notification handler code here
    static int nLastIndex = -1;
    BOOL bChanged = TRUE;

    if ( nLastIndex != pDispInfo->item.iItem )
    {
    bChanged = TRUE;
    nLastIndex++;
    m_strBuffer.ReleaseBuffer();
    }
    else
    {
    bChanged = FALSE;
    }

    if (pDispInfo->item.mask & LVIF_TEXT)
    {
    if ( TRUE == bChanged )
    {
    m_strBuffer = GetItemText(pDispInfo->item.iItem,
    pDispInfo->item.iSubItem);

    pDispInfo->item.pszText = m_strBuffer.LockBuffer();
    }
    }
    else if (pDispInfo->item.mask & LVIF_IMAGE)
    {
    pDispInfo->item.iImage = GetItemImage(pDispInfo->item.iItem);
    }
    *pResult = 0;
    }

    Reply
  • Bug fix for "AddPool" function

    Posted by Legacy on 08/05/1999 12:00am

    Originally posted by: Doron Bubbai

    If you have been noticed, the "AddPool" function from the
    
    previouse article had two weird problems. The first is that
    there is no use for the "m_pstrPool" array, and the second
    is that the function indeed locked the added item, but,
    releasing it immediately.
    Here is the fixed version for that function:

    LPTSTR CMyListCtrl::AddPool(CString* pstr)
    {
    LPTSTR pstrRetVal;

    int nOldest = m_nNextFree;

    m_nNextFree++;

    if (m_nNextFree == 3)
    m_nNextFree = 0;

    m_strCPool[m_nNextFree] = *pstr;
    pstrRetVal = m_strCPool[m_nNextFree].LockBuffer();

    m_strCPool[nOldest].ReleaseBuffer();

    return pstrRetVal;
    }

    it is obvious now that you can also remove the "m_pstrPool" variable definition from the .h file.

    Reply
  • Don't forget to intialize m_nNextFree!

    Posted by Legacy on 04/11/1999 12:00am

    Originally posted by: Kevin W. Brown

    m_nNextFree must be initiailized to 0 or the pool array will be written out of bounds

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The software-defined data center (SDDC) and new trends in cloud and virtualization bring increased agility, automation, and intelligent services and management to all areas of the data center. Businesses can now more easily manage the entire lifecycle of their applications and services via the SDDC. This Aberdeen analyst report examines how a strong foundation in both the cloud and internal data centers is empowering organizations to fully leverage their IT infrastructure and is also preparing them to be able …

  • MongoDB has the flexibility, adaptability and extensibility to embrace widely varying data types and rapid design/deployment cycles. Combining MongoDB with our 5100 ECO Enterprise SSD brings amazing results in smaller, simpler deployments compared to legacy storage. In this technical brief, we compare two MongoDB test clusters, each using the Linux Logical Volume Manager (LVM) for RAID configuration: 5100 ECO 3-node cluster: Two Micron 5100 ECO (1.92TB) per node configured as a software RAID 0 (LVM) Legacy …

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date