Filling the List View Control

Now let's look at how to fill the list view control in our samples with the contents of the folder selected in the tree view control. In Figure 14-3, the contents of my root C drive are displayed in a list view control in large icon view. (In Figure 14-2 on page 340, the same contents are displayed in list view.)

Figure 14-3.

The contents of the Nancycl2 [C:] folder in large icon view.

NOTE: You might have noticed that ENUMDESK, the C sample, contains a feature that is missing from the MFC version: a splitter bar. When I first ported the code to MFC, I ignored the splitter bar, thinking that I would add it later. But of course I ran out of time. So I leave the implementation of splitter bars in MFC to you, as an interesting exercise.

In the MFCENUM sample, I populate the list view control whenever a new folder is selected in the tree view control. The list view control first deletes all its current items. When the control is empty, the application uses the SHGetFileInfo function to retrieve the image lists (large and small) associated with the shell.

BOOL CMfcenumView::InitListViewImageLists ()
{
HIMAGELIST himlSmall;
HIMAGELIST himlLarge;
SHFILEINFO sfi;
BOOL bSuccess = TRUE;

himlSmall = (HIMAGELIST) SHGetFileInfo ((LPCSTR) "C:\\",
0, &sfi, sizeof (SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_SMALLICON);

himlLarge = (HIMAGELIST) SHGetFileInfo ((LPCSTR) "C:\\",
0, &sfi, sizeof (SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_LARGEICON);

if (himlSmall && himlLarge)
{
::SendMessage (m_ListCtl.m_hWnd, LVM_SETIMAGELIST,
(WPARAM)LVSIL_SMALL, (LPARAM)himlSmall);
::SendMessage (m_ListCtl.m_hWnd, LVM_SETIMAGELIST,
(WPARAM)LVSIL_NORMAL, (LPARAM)himlLarge);
}
else
bSuccess = FALSE;

return bSuccess;
}

After you've associated the image lists, it is time to fill the list view control. This procedure is nearly identical to the procedure used to fill the tree view control: get a task allocator for the folder, enumerate the objects, and add those that can be displayed to the list view control.

BOOL CMfcenumView::InitListViewItems (
LPTVITEMDATA lptvid, LPSHELLFOLDER lpsf)
{
LV_ITEM lvi;
int iCtr;
HRESULT hr;
LPMALLOC lpMalloc;
LPITEMIDLIST lpifqThisItem;
LPITEMIDLIST lpi = NULL;
LPENUMIDLIST lpe = NULL;
LPLVITEMDATA lplvid;
ULONG ulFetched, ulAttrs;
HWND hwnd = ::GetParent (m_ListCtl.m_hWnd);
UINT uFlags;

lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;

hr = SHGetMalloc (&lpMalloc);
if (FAILED (hr))
return FALSE;

if (SUCCEEDED (hr))
{
hr = lpsf->EnumObjects (hwnd, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS,
&lpe);

if (SUCCEEDED (hr))
{
iCtr = 0;

while (S_OK == lpe->Next (1, &lpi, &ulFetched))
{
// Get some memory for the ITEMDATA structure.
lplvid = (LPLVITEMDATA) lpMalloc->Alloc (sizeof (LVITEMDATA));
if (! lplvid)
goto Done;

// Since you are interested in the display attributes
// as well as other attributes, you need to set ulAttrs to
// SFGAO_DISPLAYATTRMASK before calling GetAttributesOf().
ulAttrs = SFGAO_DISPLAYATTRMASK;
lpsf->GetAttributesOf (1, (const struct _ITEMIDLIST **)&lpi,
&ulAttrs);
lplvid->ulAttribs = ulAttrs;

lpifqThisItem = ConcatPidls (lptvid->lpifq, lpi);

lvi.iItem = iCtr++;
lvi.iSubItem = 0;
lvi.pszText = LPSTR_TEXTCALLBACK;
lvi.cchTextMax = MAX_PATH;
uFlags = SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON;
lvi.iImage = I_IMAGECALLBACK;

lplvid->lpsfParent = lpsf;
lpsf->AddRef ();

// Now make a copy of the ITEMIDLIST.
lplvid->lpi = CopyITEMID (lpMalloc, lpi);

lvi.lParam = (LPARAM)lplvid;

// Add the item to the list view control.
if (m_ListCtl.InsertItem (&lvi) == -1)
return FALSE;

lpMalloc->Free (lpifqThisItem);
lpifqThisItem = 0;
lpMalloc->Free (lpi); // free the PIDL the shell gave you
lpi = 0;
}
}
}

Done:
if (lpe)
lpe->Release ();

// The following two if statements will be TRUE only if you got here
// on an error condition from the goto statement. Otherwise, free
// this memory at the end of the while loop above.
if (lpi && lpMalloc)
lpMalloc->Free (lpi);
if (lpifqThisItem && lpMalloc)
lpMalloc->Free (lpifqThisItem);

if (lpMalloc)
lpMalloc->Release ();

return TRUE;
}

The final step is to sort the list view control by using the LVM_SORTITEMS message or the CListCtrl::SortItems member function. Items can be sorted through a callback procedure that uses the IShellFolder::CompareIDs member function. This function compares two item ID lists and returns the result. Windows Explorer always passes 0 as the lParam parameter to indicate that the items should be sorted by name. The compare function returns 0 if the objects are the same, a negative value if pidl1 should be placed before pidl2, and a positive value if pidl2 should be placed before pidl1.

int CALLBACK CMfcenumView::ListViewCompareProc (
LPARAM lparam1, LPARAM lparam2, LPARAM lparamSort)
{
LPLVITEMDATA lplvid1 = (LPLVITEMDATA)lparam1;
LPLVITEMDATA lplvid2 = (LPLVITEMDATA)lparam2;
HRESULT hr;

hr = lplvid1->lpsfParent->CompareIDs (0, lplvid1->lpi, lplvid2->lpi);

if (FAILED (hr))
return 0;

return hr;
}