Using a Virtual CListCtrl to Display Text and Bitmaps






4.90/5 (21 votes)
Includes code to create a virtual list control that also displays bitmaps from files at run time.

Introduction
This article shows an example of using a list control to do the following:
- Access data object virtually
- Access bitmaps larger than the typical icon used in views
- Access bitmaps virtually from a filename
Background
My company, Rimage, manufactures high end CD, DVD, and Blu-ray production equipment. The software I work on specifically is called QuickDisc. It is the interface that helps desktop users collect files using drag and drop and select the options that they want to use when creating discs. Our equipment automatically loads the disc drives and prints on the disc using a printer we developed. I was given the task of adding a feature to QuickDisc that would show a bitmap (thumbnail) in a list control.
In our case, there could be thousands of them so I didn't want to load them all into memory at the same time and certainly didn't want to host them all in the list control. I looked around for examples of loading bitmaps dynamically and found only a few so I decided to share this result. The code is heavily commented and the techniques are pretty easy once figured out.
Using the Code/Points of Interest
This is just an example program so users could cut and paste any of the code into their own programs and tailor to their particular needs. I chose to use BMP files, but it would be a simple matter to use the CImage
class to load any kind of graphic (JPG, PNG, etc.) and convert it to the ImageList
format at run time. One of the nice things about a virtual list control is that only the data that is showing on the screen is actually in the control. The rest is neatly stored elsewhere in data collections or on the disc. Thus, the control does not slow down regardless of how many items are displayed.
The specific items of interest in the code are listed below. Note: I am not actually using the imagelist
s for anything besides a holding place for the eventual bitmaps.
if(m_imageList.GetSafeHandle() == NULL) {
m_imageList.Create(210, 160, ILC_COLOR24 | ILC_MASK, 8, 1);
m_cList.SetImageList(&m_imageList, LVSIL_SMALL);
m_cList.SetImageList(&m_imageList, LVSIL_NORMAL);
m_imageList.SetImageCount(1);
} CString csFilePath = _T("C:\\TestFiles");
I am setting the number of items in the list control to the count in my corresponding data array. The list thinks it has this number of items even though I never actually loaded anything to it.
// This sets the count in the list without adding any items. This is very fast.
m_cList.SetItemCountEx((int)m_MyDataArray.GetCount(),
LVSICF_NOSCROLL|LVSICF_NOINVALIDATEALL);
This is the workhorse routine. It gets called by the list control for every line that it wants to display on the screen. Notice that there are two masks I am handling, 1 for the text and the other for the bitmap.
void CBMPListDlg::GetDispInfo(NMHDR *pNMHDR, LRESULT *pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
//Create a pointer to the item
LV_ITEM* pItem= &(pDispInfo)->item;
//Which item number?
int nItem = pItem->iItem;
CMyDataInfo *pInfo = NULL;
if(nItem > m_MyDataArray.GetSize()-1)
return; // Just to be safe
pInfo = (CMyDataInfo *)m_MyDataArray.GetAt(nItem);
if(pInfo == NULL)
return;
//Do we need text information?
if (pItem->mask & LVIF_TEXT) {
CString csText;
//Which column?
if(pItem->iSubItem == 0)
csText = pInfo->m_csColumn1;
else if (pItem->iSubItem == 1) // Column 1
csText = pInfo->m_csColumn2;
else if (pItem->iSubItem == 2) // Column 2
csText = pInfo->m_csColumn3;
else if (pItem->iSubItem == 3) // Column 3
csText = pInfo->m_csColumn4;
//Copy the text to the LV_ITEM structure
//Maximum number of characters is in pItem->cchTextMax
lstrcpyn(pItem->pszText, csText, pItem->cchTextMax);
}
//Does the list need image information?
if( pItem->mask & LVIF_IMAGE) {
// Need to reset first item of image list to the
// correct bitmap
HBITMAP hBmp = (HBITMAP)::LoadImage(AfxGetInstanceHandle(),
pInfo->m_csImage,
IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
if(hBmp != NULL) {
CBitmap *cBmp = CBitmap::FromHandle(hBmp);
if(cBmp != NULL)
m_imageList.Replace(0,cBmp,NULL);
}
// Note, there is no Navigator 0.bmp so that image will
// always display blank to
// illustrate what would happen when the file is not found.
else {
CBitmap *cBmp = new CBitmap();
cBmp->LoadBitmap(IDB_BLANK);
m_imageList.Replace(0,cBmp,NULL);
delete cBmp;
}
pItem->iImage = 0; // Always use 0 since only one element
}
*pResult = 0;
}
History
- Version 1.0 - April 5, 2009