List Views

[Previous] [Next]

List views are similar to tree views in that they provide a powerful infrastructure for presenting complex collections of data to the user. But whereas tree views are ideal for depicting hierarchical relationships, list views are best suited for presenting "flat" collections of data, such as lists of file names.

Like items in a tree view, items in a list view can include both text and images. In addition, items can have text-only subitems containing additional information about the associated items. The subitems are visible when the control is in "report" mode, which is one of four presentation styles that a list view supports. The other presentation styles are large icon mode, small icon mode, and list mode. You can see examples of all four presentation styles by starting the Windows Explorer and using the View menu to change the view in the right pane. The Large Icons command in the View menu corresponds to large icon mode, Small Icons corresponds to small icon mode, List corresponds to list mode, and Details corresponds to report mode.

Initializing a List View

MFC's CListView class is the base class for list views. CListView derives most of its functionality from list view controls, which, like tree view controls, are part of the common controls library. MFC wraps list view controls in the class CListCtrl. To program a list view, you call CListView::GetListCtrl to acquire a CListCtrl reference to the control that appears inside the list view, and then you call CListCtrl functions using the returned reference.

When you derive from CListView, you'll almost always override PreCreateWindow in the derived class and apply one or more default styles to the view. The following table lists the styles that all list views support. Additional list view styles are available on systems running Internet Explorer 3.0 or later.

List View Styles

StyleDescription
LVS_ICON Selects large icon mode.
LVS_SMALLICON Selects small icon mode.
LVS_LIST Selects list mode.
LVS_REPORT Selects report mode.
LVS_NOCOLUMNHEADER Removes the header control that's normally displayed in report mode.
LVS_NOSORTHEADER Disables the LVN_COLUMNCLICK notifications that are sent by default when a column header is clicked in report mode.
LVS_ALIGNLEFT Aligns items along the left border in large and small icon mode.
LVS_ALIGNTOP Aligns items along the top border in large and small icon mode.
LVS_AUTOARRANGE Automatically arranges items in rows and columns in large and small icon mode.
LVS_EDITLABELS Enables in-place label editing notifications.
LVS_NOLABELWRAP Restricts labels to single lines in large icon mode.
LVS_NOSCROLL Disables scrolling. Scrolling is enabled by default.
LVS_OWNERDRAWFIXED Specifies that the control's owner will draw the items in response to WM_DRAWITEM messages.
LVS_SHAREIMAGELISTS Prevents a list view from automatically deleting the image lists associated with it when the view itself is deleted.
LVS_SINGLESEL Disables multiple-selection support.
LVS_SHOWSELALWAYS Specifies that the selected items should always be highlighted. By default, the highlight is removed when the view loses the input focus.
LVS_SORTASCENDING Specifies that items should be sorted in ascending order (for example, A through Z).
LVS_SORTDESCENDING Specifies that items should be sorted in descending order (for example, Z through A).

Like a tree view control, a list view control is empty when it's first created. Initialization is a five-step process:

  1. Create a pair of image lists containing images for the list view items. One image list contains "large" images used in large icon mode; the other contains "small" images used in small icon, list, and report modes.

  2. Use CListCtrl::SetImageList to associate the image lists with the list view control. Pass SetImageList an LVSIL_NORMAL flag for the image list containing large images and an LVSIL_SMALL flag for the image list containing small images.

  3. Add columns to the list view control with CListCtrl::InsertColumn. The leftmost column displays the items added to the control. The columns to the right display subitems and are visible only in report mode.

  4. Add items to the control with CListCtrl::InsertItem.

  5. Assign text strings to the item's subitems with CListCtrl::SetItemText.

This procedure isn't as difficult as it sounds. The following code fragment initializes a list view with items representing eight of the states in the United States. Each item consists of a label and an image. The label is the name of a state, and the image presumably shows a thumbnail rendition of the state's outline. Each item also contains a pair of subitems: a text string naming the state capital and a text string describing the state's land area. In report mode, the subitems appear in columns under headers labeled "Capital" and "Area (sq. miles)."

 static CString text[8][3] = {     _T ("Tennessee"),        _T ("Nashville"),    _T ("41,154"),     _T ("Alabama"),          _T ("Montgomery"),   _T ("50,766"),     _T ("Mississippi"),      _T ("Jackson"),      _T ("47,234"),     _T ("Florida"),          _T ("Tallahassee"),  _T ("54,157"),     _T ("Georgia"),          _T ("Atlanta"),      _T ("58,060"),     _T ("Kentucky"),         _T ("Frankfort"),    _T ("39,674"),     _T ("North Carolina"),   _T ("Raleigh"),      _T ("48,843"),     _T ("South Carolina"),   _T ("Columbia"),     _T ("30,207") }; // Assign image lists. GetListCtrl ().SetImageList (&ilLarge, LVSIL_NORMAL); GetListCtrl ().SetImageList (&ilSmall, LVSIL_SMALL); // Add columns. GetListCtrl ().InsertColumn (0, _T ("State"), LVCFMT_LEFT, 96); GetListCtrl ().InsertColumn (1, _T ("Capital"), LVCFMT_LEFT, 96); GetListCtrl ().InsertColumn (2, _T ("Area (sq. miles)"),     LVCFMT_RIGHT, 96); // Add items and subitems. for (int i=0; i<8; i++) {     GetListCtrl ().InsertItem (i, (LPCTSTR) text[i][0], i);     GetListCtrl ().SetItemText (i, 1, (LPCTSTR) text[i][1]);     GetListCtrl ().SetItemText (i, 2, (LPCTSTR) text[i][2]); } 

The parameters passed to InsertColumn specify, in order, the column's 0-based index, the label that appears at the top of the column, the column's alignment (whether data displayed in the column is left justified, right justified, or centered), and the column width in pixels. You can base column widths on the widths of characters in the control font by using CListCtrl::GetStringWidth to convert text strings into pixel counts. The parameters passed to InsertItem specify the item's 0-based index, the item label, and the index of the corresponding images in the image lists. The parameters passed to SetItemText specify the item number, the subitem number, and the subitem text, in that order.

Changing the Presentation Style

When a list view is created, its presentation style—LVS_ICON, LVS_SMALLICON, LVS_LIST, or LVS_REPORT—determines whether it starts up in large icon mode, small icon mode, list mode, or report mode. The default presentation style is applied in PreCreateWindow. However, you can switch modes on the fly by changing the presentation style. The following statement switches a list view to small icon mode:

 ModifyStyle (LVS_TYPEMASK, LVS_SMALLICON);

Similarly, this statement switches the view to report mode:

 ModifyStyle (LVS_TYPEMASK, LVS_REPORT);

ModifyStyle is a CWnd function that's handed down through inheritance to CListView. The first parameter passed to ModifyStyle specifies the style bits to turn off, and the second parameter specifies the style bits to turn on. LVS_TYPEMASK is a mask for all four presentation styles.

LVS_ICON, LVS_SMALLICON, LVS_LIST, and LVS_REPORT aren't true bit flags, so LVS_TYPEMASK also comes in handy when you query a list view to determine its current presentation style. The following code won't work:

// Wrong! DWORD dwStyle = GetStyle (); if (dwStyle & LVS_ICON)     // Large icon mode. else if (dwStyle & LVS_SMALLICON)     // Small icon mode. else if (dwStyle & LVS_LIST)     // List mode. else if (dwStyle & LVS_REPORT)     // Report mode.

But this code will:

 DWORD dwStyle = GetStyle () & LVS_TYPEMASK; if (dwStyle == LVS_ICON)     // Large icon mode. else if (dwStyle == LVS_SMALLICON)     // Small icon mode. else if (dwStyle == LVS_LIST)     // List mode. else if (dwStyle == LVS_REPORT)     // Report mode.

This is the proper technique for determining the view type before updating menu items or other user interface objects that depend on the list view's presentation style.

Sorting in a List View

When a list view that lacks the LVS_NOCOLUMNHEADER style switches to report mode, it automatically displays a header control with buttonlike "header items" captioning each column. The user can change the column widths by dragging the vertical dividers separating the header items. (For a nice touch, you can retrieve the column widths with CListCtrl::GetColumnWidth before destroying a list view and save the widths in the registry. Restore the column widths the next time the list view is created, and the user's column width preferences will be persistent.) Unless a list view has the style LVS_NOSORTHEADER, clicking a header item sends an LVN_COLUMNCLICK notification to the list view's parent. The message's lParam points to an NM_LISTVIEW structure, and the structure's iSubItem field contains a 0-based index identifying the column that was clicked.

An application's usual response to an LVN_COLUMNCLICK notification is to call CListCtrl::SortItems to sort the list view items. Great, you say. Now I can create a list view that sorts, and I won't have to write the code to do the sorting. You do have to provide a callback function that the control's built-in sorting routine can call to compare a pair of arbitrarily selected items, but writing a comparison function is substantially less work than writing a full-blown bubble sort or quick sort routine. And the fact that the comparison function is application-defined means that you enjoy complete control over how the items in a list view control are lexically ordered.

The bad news is that the comparison function receives just three parameters: the 32-bit lParam values of the two items being compared and an application-defined lParam value that equals the second parameter passed to SortItems. You can assign an item an lParam value in the call to InsertItem or in a separate call to CListCtrl::SetItemData. Unless an application maintains a private copy of each item's data and stores a value in lParam that allows the item's data to be retrieved, the comparison function can't possibly do its job. It's not difficult for an application to allocate its own per-item memory and stuff pointers into the items' lParams, but it does complicate matters a bit because the memory must be deallocated, too. And an application that stores its own item data uses memory inefficiently if it assigns text strings to the list view's items and subitems because then the data ends up being stored in memory twice. You can avoid such wastefulness by specifying LPSTR_TEXTCALLBACK for the item and subitem text and providing text to the list view control in response to LVN_GETDISPINFO notifications. But this, too, complicates the program logic and means that the infrastructure required to support CListCtrl::SortItems isn't as simple as it first appears. In just a moment, we'll develop an application that implements sortable columns in a list view so that you can see firsthand how it's done.

Hit-Testing in a List View

You can respond to mouse clicks in a list view by processing NM_CLICK, NM_DBLCLK, NM_RCLICK, and NM_RDBLCLK notifications. Very often, the way you respond to these events will depend on what, if anything, was under the cursor when the click (or double-click) occurred. You can use CListCtrl::HitTest to perform hit-testing on the items in a list view. Given the coordinates of a point, HitTest returns the index of the item at that point or at -1 if the point doesn't correspond to an item.

The following code demonstrates how to process double clicks in a list view. The ON_NOTIFY_REFLECT entry in the message map reflects NM_DBLCLK notifications back to the list view. The NM_DBLCLK handler echoes the name of the item that was double-clicked to the debug output window using MFC's TRACE macro:

 // In CMyListView's message map ON_NOTIFY_REFLECT (NM_DBLCLK, OnDoubleClick)        void CMyListView::OnDoubleClick (NMHDR* pnmh, LRESULT* pResult) {     DWORD dwPos = ::GetMessagePos ();     CPoint point ((int) LOWORD (dwPos), (int) HIWORD (dwPos));     GetListCtrl ().ScreenToClient (&point);     int nIndex;     if ((nIndex = GetListCtrl ().HitTest (point)) != -1) {         CString string = GetListCtrl ().GetItemText (nIndex, 0);         TRACE (_T ("%s was double-clicked\n"), string);     }     *pResult = 0; } 

NM_DBLCLK notifications don't include cursor coordinates, so the cursor position is retrieved with ::GetMessagePos. The screen coordinates returned by ::GetMessagePos are converted into client coordinates local to the list view and passed to CListCtrl::HitTest. If HitTest returns an item index, the index is used to retrieve the item's text.

The WinDir Application

The WinDir application pictured in Figure 10-8 is so named because its output is reminiscent of the MS-DOS DIR command, albeit in a graphical format. It uses a CListView-derived class named CFileView to display a list of all the files in a specified directory. You pick the directory by selecting the New Directory command from the File menu and entering a path name. After retrieving the path name that you entered, WinDir passes the path name to CFileView::Refresh to display the directory's contents. You can see this for yourself in FileView.cpp, which, along with other parts of WinDir's source code, is reproduced in Figure 10-9.

click to view at full size.

Figure 10-8. The WinDir window.

Here's a synopsis of how CFileView works. First, CFileView::Refresh builds a list of file names using ::FindFirstFile and ::FindNextFile. For each file that it identifies, Refresh adds an item to the list view by calling CFileView::AddItem. AddItem, in turn, allocates memory for an ITEMINFO data structure (defined in FileView.h); initializes the structure with the file's name, size, and date-and-time stamp; and adds an item to the list view whose lParam is the structure's address. Here's how it looks with error-checking code removed:

ITEMINFO* pItem; pItem = new ITEMINFO; pItem->strFileName = pfd->cFileName; pItem->nFileSizeLow = pfd->nFileSizeLow; pItem->ftLastWriteTime = pfd->ftLastWriteTime; LV_ITEM lvi; lvi.mask = LVIF_TEXT ¦ LVIF_IMAGE ¦ LVIF_PARAM;  lvi.iItem = nIndex;  lvi.iSubItem = 0;  lvi.iImage = 0; lvi.pszText = LPSTR_TEXTCALLBACK;  lvi.lParam = (LPARAM) pItem; GetListCtrl ().InsertItem (&lvi);

Notice the LPSTR_TEXTCALLBACK value specified in the LV_ITEM structure's pszText field. Rather than assign the item a text string, AddItem tells the list view, "Call me back when you need a label for the item." It's not necessary to initialize the subitems because LPSTR_TEXTCALLBACK is the default for subitems.

Figure 10-9. The WinDir application.

MainFrm.h

 // MainFrm.h : interface of the CMainFrame class // /////////////////////////////////////////////////////////////////////////// #if !defined(AFX_MAINFRM_H__18BD7B7C_95C6_11D2_8E53_006008A82731__INCLUDED_) #define AFX_MAINFRM_H__18BD7B7C_95C6_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 class CMainFrame : public CFrameWnd {      protected: // create from serialization only     CMainFrame();     DECLARE_DYNCREATE(CMainFrame) // Attributes public: // Operations public: // Overrides     // ClassWizard generated virtual function overrides     //{{AFX_VIRTUAL(CMainFrame)     virtual BOOL PreCreateWindow(CREATESTRUCT& cs);     //}}AFX_VIRTUAL // Implementation public:     virtual ~CMainFrame(); #ifdef _DEBUG     virtual void AssertValid() const;     virtual void Dump(CDumpContext& dc) const; #endif // Generated message map functions protected:     //{{AFX_MSG(CMainFrame)        // NOTE - the ClassWizard will add and remove member functions here.        //    DO NOT EDIT what you see in these blocks of generated code!     //}}AFX_MSG     DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif  // !defined(AFX_MAINFRM_H__18BD7B7C_95C6_11D2_8E53_006008A82731__INCLUDED_) 

MainFrm.cpp

 // MainFrm.cpp : implementation of the CMainFrame class // #include "stdafx.h" #include "WinDir.h" #include "MainFrm.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CMainFrame IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd) BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)     //{{AFX_MSG_MAP(CMainFrame)         // NOTE - the ClassWizard will add and remove mapping macros here.         //    DO NOT EDIT what you see in these blocks of generated code !     //}}AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CMainFrame construction/destruction CMainFrame::CMainFrame() { } CMainFrame::~CMainFrame() { } BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) {     if( !CFrameWnd::PreCreateWindow(cs) )         return FALSE;     cs.style &= ~FWS_ADDTOTITLE;     return TRUE; } /////////////////////////////////////////////////////////////////////////// // CMainFrame diagnostics #ifdef _DEBUG void CMainFrame::AssertValid() const {     CFrameWnd::AssertValid(); } void CMainFrame::Dump(CDumpContext& dc) const {     CFrameWnd::Dump(dc); } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CMainFrame message handlers 

FileView.h

 // FileView.h : interface of the CFileView class // ///////////////////////////////////////////////////////////////////////////// #if !defined(AFX_FILEVIEW_H__18BD7B80_95C6_11D2_8E53_006008A82731__INCLUDED_) #define AFX_FILEVIEW_H__18BD7B80_95C6_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 typedef struct tagITEMINFO {     CString     strFileName;      DWORD       nFileSizeLow;      FILETIME    ftLastWriteTime;  } ITEMINFO; class CFileView : public CListView { protected: // create from serialization only     CFileView();     DECLARE_DYNCREATE(CFileView) // Attributes public:     CWinDirDoc* GetDocument(); // Operations public:     static int CALLBACK CompareFunc (LPARAM lParam1, LPARAM lParam2,         LPARAM lParamSort); // Overrides     // ClassWizard generated virtual function overrides     //{{AFX_VIRTUAL(CFileView)     public:     virtual void OnDraw(CDC* pDC);  // overridden to draw this view     virtual BOOL PreCreateWindow(CREATESTRUCT& cs);     protected:     virtual void OnInitialUpdate(); // called first time after construct     //}}AFX_VIRTUAL // Implementation public:     int Refresh (LPCTSTR pszPath);     virtual ~CFileView(); #ifdef _DEBUG     virtual void AssertValid() const;     virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected:     CString m_strPath;     void FreeItemMemory ();     BOOL AddItem (int nIndex, WIN32_FIND_DATA* pfd);     CImageList m_ilSmall;     CImageList m_ilLarge;     //{{AFX_MSG(CFileView)     afx_msg void OnDestroy();     afx_msg void OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult);     afx_msg void OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult);     afx_msg void OnViewLargeIcons();     afx_msg void OnViewSmallIcons();     afx_msg void OnViewList();     afx_msg void OnViewDetails();     afx_msg void OnUpdateViewLargeIcons(CCmdUI* pCmdUI);     afx_msg void OnUpdateViewSmallIcons(CCmdUI* pCmdUI);     afx_msg void OnUpdateViewList(CCmdUI* pCmdUI);     afx_msg void OnUpdateViewDetails(CCmdUI* pCmdUI);     afx_msg void OnFileNewDirectory();     //}}AFX_MSG     DECLARE_MESSAGE_MAP() }; #ifndef _DEBUG  // debug version in FileView.cpp inline CWinDirDoc* CFileView::GetDocument()     { return (CWinDirDoc*)m_pDocument; } #endif /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif  // !defined( //     AFX_FILEVIEW_H__18BD7B80_95C6_11D2_8E53_006008A82731__INCLUDED_) 

FileView.cpp

 // FileView.cpp : implementation of the CFileView class // #include "stdafx.h" #include "WinDir.h" #include "PathDialog.h" #include "WinDirDoc.h" #include "FileView.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CFileView IMPLEMENT_DYNCREATE(CFileView, CListView) BEGIN_MESSAGE_MAP(CFileView, CListView)     //{{AFX_MSG_MAP(CFileView)     ON_WM_DESTROY()     ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetDispInfo)     ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnClick)     ON_COMMAND(ID_VIEW_LARGE_ICONS, OnViewLargeIcons)     ON_COMMAND(ID_VIEW_SMALL_ICONS, OnViewSmallIcons)     ON_COMMAND(ID_VIEW_LIST, OnViewList)     ON_COMMAND(ID_VIEW_DETAILS, OnViewDetails)     ON_UPDATE_COMMAND_UI(ID_VIEW_LARGE_ICONS, OnUpdateViewLargeIcons)     ON_UPDATE_COMMAND_UI(ID_VIEW_SMALL_ICONS, OnUpdateViewSmallIcons)     ON_UPDATE_COMMAND_UI(ID_VIEW_LIST, OnUpdateViewList)     ON_UPDATE_COMMAND_UI(ID_VIEW_DETAILS, OnUpdateViewDetails)     ON_COMMAND(ID_FILE_NEW_DIR, OnFileNewDirectory)     //}}AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CFileView construction/destruction CFileView::CFileView() { } CFileView::~CFileView() { } BOOL CFileView::PreCreateWindow(CREATESTRUCT& cs) {     if (!CListView::PreCreateWindow (cs))         return FALSE;     cs.style &= ~LVS_TYPEMASK;     cs.style ¦= LVS_REPORT;     return TRUE; } /////////////////////////////////////////////////////////////////////////// // CFileView drawing void CFileView::OnDraw(CDC* pDC) {     CWinDirDoc* pDoc = GetDocument();     ASSERT_VALID(pDoc);     // TODO: add draw code for native data here } void CFileView::OnInitialUpdate() {     CListView::OnInitialUpdate();     //     // Initialize the image list.     //     m_ilLarge.Create (IDB_LARGEDOC, 32, 1, RGB (255, 0, 255));     m_ilSmall.Create (IDB_SMALLDOC, 16, 1, RGB (255, 0, 255));     GetListCtrl ().SetImageList (&m_ilLarge, LVSIL_NORMAL);     GetListCtrl ().SetImageList (&m_ilSmall, LVSIL_SMALL);     //     // Add columns to the list view.     //     GetListCtrl ().InsertColumn (0, _T ("File Name"), LVCFMT_LEFT, 192);     GetListCtrl ().InsertColumn (1, _T ("Size"), LVCFMT_RIGHT, 96);     GetListCtrl ().InsertColumn (2, _T ("Last Modified"), LVCFMT_CENTER, 128);     //     // Populate the list view with items.     //     TCHAR szPath[MAX_PATH];     ::GetCurrentDirectory (sizeof (szPath) / sizeof (TCHAR), szPath);     Refresh (szPath); } /////////////////////////////////////////////////////////////////////////// // CFileView diagnostics #ifdef _DEBUG void CFileView::AssertValid() const {     CListView::AssertValid(); } void CFileView::Dump(CDumpContext& dc) const {     CListView::Dump(dc); } CWinDirDoc* CFileView::GetDocument() // non-debug version is inline {     ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CWinDirDoc)));     return (CWinDirDoc*)m_pDocument; } #endif //_DEBUG /////////////////////////////////////////////////////////////////////////// // CFileView message handlers int CFileView::Refresh(LPCTSTR pszPath) {     CString strPath = pszPath;     if (strPath.Right (1) != _T ("\\"))         strPath += _T ("\\");     strPath += _T ("*.*");     HANDLE hFind;     WIN32_FIND_DATA fd;     int nCount = 0;     if ((hFind = ::FindFirstFile (strPath, &fd)) != INVALID_HANDLE_VALUE) {         //         // Delete existing items (if any).         //         GetListCtrl ().DeleteAllItems ();              //         // Show the path name in the frame window's title bar.         //         TCHAR szFullPath[MAX_PATH];         ::GetFullPathName (pszPath, sizeof (szFullPath) / sizeof (TCHAR),             szFullPath, NULL);         m_strPath = szFullPath;         CString strTitle = _T ("WinDir - ");         strTitle += szFullPath;         AfxGetMainWnd ()->SetWindowText (strTitle);         //         // Add items representing files to the list view.         //         if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))             AddItem (nCount++, &fd);         while (::FindNextFile (hFind, &fd)) {             if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))                 if (!AddItem (nCount++, &fd))                     break;         }         ::FindClose (hFind);     }     return nCount; } BOOL CFileView::AddItem(int nIndex, WIN32_FIND_DATA *pfd) {     //     // Allocate a new ITEMINFO structure and initialize it with information     // about the item.     //     ITEMINFO* pItem;     try {         pItem = new ITEMINFO;     }     catch (CMemoryException* e) {         e->Delete ();         return FALSE;     }     pItem->strFileName = pfd->cFileName;     pItem->nFileSizeLow = pfd->nFileSizeLow;     pItem->ftLastWriteTime = pfd->ftLastWriteTime;     //     // Add the item to the list view.     //     LV_ITEM lvi;     lvi.mask = LVIF_TEXT ¦ LVIF_IMAGE ¦ LVIF_PARAM;      lvi.iItem = nIndex;      lvi.iSubItem = 0;      lvi.iImage = 0;     lvi.pszText = LPSTR_TEXTCALLBACK;      lvi.lParam = (LPARAM) pItem;     if (GetListCtrl ().InsertItem (&lvi) == -1)         return FALSE;     return TRUE; } void CFileView::FreeItemMemory() {     int nCount = GetListCtrl ().GetItemCount ();     if (nCount) {         for (int i=0; i<nCount; i++)             delete (ITEMINFO*) GetListCtrl ().GetItemData (i);     } } void CFileView::OnDestroy()  {     FreeItemMemory ();     CListView::OnDestroy (); } void CFileView::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult)  {     CString string;     LV_DISPINFO* pDispInfo = (LV_DISPINFO*) pNMHDR;     if (pDispInfo->item.mask & LVIF_TEXT) {         ITEMINFO* pItem = (ITEMINFO*) pDispInfo->item.lParam;         switch (pDispInfo->item.iSubItem) {         case 0: // File name.             ::lstrcpy (pDispInfo->item.pszText, pItem->strFileName);             break;         case 1: // File size.             string.Format (_T ("%u"), pItem->nFileSizeLow);             ::lstrcpy (pDispInfo->item.pszText, string);             break;         case 2: // Date and time.             CTime time (pItem->ftLastWriteTime);             BOOL pm = FALSE;             int nHour = time.GetHour ();             if (nHour == 0)                 nHour = 12;             else if (nHour == 12)                 pm = TRUE;             else if (nHour > 12) {                 nHour -= 12;                 pm = TRUE;             }             string.Format (_T ("%d/%0.2d/%0.2d (%d:%0.2d%c)"),                 time.GetMonth (), time.GetDay (), time.GetYear () % 100,                 nHour, time.GetMinute (), pm ? _T (`p') : _T (`a'));             ::lstrcpy (pDispInfo->item.pszText, string);             break;         }     }     *pResult = 0; } void CFileView::OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult)  {     NM_LISTVIEW* pNMListView = (NM_LISTVIEW*) pNMHDR;     GetListCtrl ().SortItems (CompareFunc, pNMListView->iSubItem);     *pResult = 0; } int CALLBACK CFileView::CompareFunc (LPARAM lParam1, LPARAM lParam2,     LPARAM lParamSort) {     ITEMINFO* pItem1 = (ITEMINFO*) lParam1;     ITEMINFO* pItem2 = (ITEMINFO*) lParam2;     int nResult;     switch (lParamSort) {     case 0: // File name.         nResult = pItem1->strFileName.CompareNoCase (pItem2->strFileName);         break;     case 1: // File size.         nResult = pItem1->nFileSizeLow - pItem2->nFileSizeLow;         break;     case 2: // Date and time.         nResult = ::CompareFileTime (&pItem1->ftLastWriteTime,             &pItem2->ftLastWriteTime);         break;     }     return nResult; } void CFileView::OnViewLargeIcons()  {     ModifyStyle (LVS_TYPEMASK, LVS_ICON); } void CFileView::OnViewSmallIcons()  {     ModifyStyle (LVS_TYPEMASK, LVS_SMALLICON); } void CFileView::OnViewList()  {     ModifyStyle (LVS_TYPEMASK, LVS_LIST); } void CFileView::OnViewDetails()  {     ModifyStyle (LVS_TYPEMASK, LVS_REPORT); } void CFileView::OnUpdateViewLargeIcons(CCmdUI* pCmdUI)  {     DWORD dwCurrentStyle = GetStyle () & LVS_TYPEMASK;     pCmdUI->SetRadio (dwCurrentStyle == LVS_ICON); } void CFileView::OnUpdateViewSmallIcons(CCmdUI* pCmdUI)  {     DWORD dwCurrentStyle = GetStyle () & LVS_TYPEMASK;     pCmdUI->SetRadio (dwCurrentStyle == LVS_SMALLICON); } void CFileView::OnUpdateViewList(CCmdUI* pCmdUI)  {     DWORD dwCurrentStyle = GetStyle () & LVS_TYPEMASK;     pCmdUI->SetRadio (dwCurrentStyle == LVS_LIST); } void CFileView::OnUpdateViewDetails(CCmdUI* pCmdUI)  {     DWORD dwCurrentStyle = GetStyle () & LVS_TYPEMASK;     pCmdUI->SetRadio (dwCurrentStyle == LVS_REPORT); } void CFileView::OnFileNewDirectory()  {     CPathDialog dlg;     dlg.m_strPath = m_strPath;     if (dlg.DoModal () == IDOK)         Refresh (dlg.m_strPath); } 

CFileView uses callbacks for item and subitem text so that it can maintain its own item data without forcing the control to maintain copies of the data, too. Callbacks come in the form of LVN_GETDISPINFO notifications, which CFileView reflects to its own OnGetDispInfo handler with an ON_NOTIFY_REFLECT message-map entry. When OnGetDispInfo is called, pNMHDR points to an LV_DISPINFO structure. The structure's item.lParam field contains the address of the ITEMINFO structure for the item in question, and the item.iSubItem field contains the index of the requested subitem. CFileView::OnGetDispInfo formulates a text string from the data stored in the ITEMINFO structure's strFileName, nFileSizeLow, or ftLastWriteTime field and copies the result to the address contained in the LV_DISPINFO structure's item.pszText field. The list view then displays the text on the screen.

CFileView maintains its own item data so that CListCtrl::SortItems can be called and CFileView::CompareFunc can retrieve any or all of an item's data by dereferencing the pointer stored in the item's lParam. If the user clicks a column header while the list view is in report mode, an ON_NOTIFY_REFLECT entry in the message map activates CFileView::OnColumnClick, and OnColumnClick, in turn, calls the list view's SortItems function, passing in the index of the column that was clicked:

 GetListCtrl ().SortItems (CompareFunc, pNMListView->iSubItem); 

CompareFunc is the application-defined sorting routine called to compare pairs of items. It's declared static because it's a callback function. CompareFunc uses the ITEMINFO pointers passed in lParam1 and lParam2 to retrieve the data for the items it's asked to compare and uses the column index in lParamSort to determine which of the items' subitems to use as the basis for the comparison. The entire function requires fewer than 20 lines of code:

int CALLBACK CFileView::CompareFunc (LPARAM lParam1, LPARAM lParam2,     LPARAM lParamSort) {     ITEMINFO* pItem1 = (ITEMINFO*) lParam1;     ITEMINFO* pItem2 = (ITEMINFO*) lParam2;     int nResult;     switch (lParamSort) {     case 0: // File name.         nResult =             pItem1->strFileName.CompareNoCase (pItem2->strFileName);         break;     case 1: // File size.         nResult = pItem1->nFileSizeLow - pItem2->nFileSizeLow;         break;     case 2: // Date and time.         nResult = ::CompareFileTime (&pItem1->ftLastWriteTime,             &pItem2->ftLastWriteTime);         break;     }     return nResult; } 

A negative return value from CompareFunc indicates that item 1 is less than (should come before) item 2, 0 means that they're equal, and a positive return value means that item 1 is greater than item 2. The ::CompareFileTime API function makes it easy to compare dates and times encapsulated in FILETIME values. You can also create CTime objects from FILETIME values and use <, >, and other operators to compare dates and times.

It might not be obvious to you yet, but you just saw why a list view with sortable columns must store its own data. The only information CompareFunc receives about the items it's asked to compare is the items' lParam values. Therefore, lParam has to provide full access to all of an item's data. One way to make sure that it does is to store item data in memory allocated by the application (in WinDir's case, in ITEMINFO structures allocated with new) and to store a pointer to the data in each item's own lParam. Storing item data yourself rather than converting it to text and handing it over to the list view provides greater flexibility in sorting because the data can be stored in binary form. How else could you sort the information that appears in CFileView's Last Modified column? A string sort wouldn't work very well because "1/1/96" comes before "9/30/85" even though the former represents a later calendar date. But since CFileView stores dates and times in their native FILETIME format, sorting is a piece of cake.

A final note concerning CFileView has to do with the method used to delete the ITEMINFO structures allocated by AddItem. CFileView::FreeItemMemory deallocates the memory set aside for each item by iterating through the items in the list view and calling delete on the pointers stored in the items' lParams. FreeItemMemory is called by the view's WM_DESTROY handler to free the ITEMINFO structures before the application shuts down.



Programming Windows with MFC
Programming Windows with MFC, Second Edition
ISBN: 1572316950
EAN: 2147483647
Year: 1999
Pages: 101
Authors: Jeff Prosise

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net