This section contains code examples that demonstrate how to create and use list-view controls in your applications.
Creating List-View Controls
To create a list-view control, use the CreateWindow or CreateWindowEx function and specify the WC_LISTVIEW window class. This window class is registered when the common controls DLL loads. To ensure that this DLL loads, use the InitCommonControls or InitCommonControlsEx function.
The following code example creates a list-view control in report view.
// CreateListView - creates a list-view control in report view.
// Returns the handle to the new control
// TO DO: calling procedure needs to check whether the handle is NULL, in case
// of an error in creation.
//
// HINST hInst is the global handle to the application instance.
// HWND hWndParent is the handle to the control's parent window.
HWND CreateListView (HWND hwndParent)
{
RECT rcl;
INITCOMMONCONTROLSEX icex;
// Ensure that the common control DLL is loaded.
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&icex);
// Create the list-view window in report view with label editing enabled.
GetClientRect (hwndParent, &rcl);
HWND hWndListView = CreateWindow(WC_LISTVIEW,
"",
WS_CHILD | LVS_REPORT | LVS_EDITLABELS,
0,
0,
rcl.right - rcl.left,
rcl.bottom - rcl.top,
hwndParent,
(HMENU)ID_LISTVIEW,
hInst,
NULL);
return (hWndListView);
}
Note When you create a list-view control, you can also send it a
WM_SETFONT message to set the font to be used for the text. You should send this message before inserting any items. By default, a list view uses the icon title font. Although you can customize the font per-item as described at
Customizing a Control's Appearance Using Custom Draw, the control uses the dimensions of the font specified by the
WM_SETFONT message to determine spacing and layout.
A list-view control can also be created as part of a dialog box template. Specify WC_LISTVIEW as the class name. Note that if you use a list-view control as part of a dialog box template, you must call InitCommonControls or InitCommonControlsEx before the dialog box template is created, in order to ensure that the list-view window class is properly registered. Otherwise, the dialog box template does not create the list-view control successfully.
Typically, list-view applications enable the user to change from one view to another. The following code example changes the list-view's window style, which in turn changes the view.
// SetView - sets a list-view's window style to change the view.
// hWndListView - handle to the list-view control.
// dwView - value specifying the new view style.
VOID SetView(HWND hWndListView, DWORD dwView)
{
// Retrieve the current window style.
DWORD dwStyle = GetWindowLong(hWndListView, GWL_STYLE);
// Only set the window style if the view bits have changed.
if ((dwStyle & LVS_TYPEMASK) != dwView)
{
SetWindowLong(hWndListView,
GWL_STYLE,
(dwStyle & ~LVS_TYPEMASK) | dwView);
}
}
Adding List-View Image Lists
When a list-view control is created, by default it does not display item images. You must assign an image list to the list-view control. To do this, use the LVM_SETIMAGELIST message or the corresponding macro ListView_SetImageList, specifying whether the image list contains full-sized icons, small icons, or state images. To retrieve the handle to an image list currently assigned to a list-view control, use the LVM_GETIMAGELIST message. You can use the GetSystemMetrics function to determine appropriate dimensions for the full-sized and small icons.
You need to create only the image lists that the control uses. For example, if your application does not allow the user to switch to icon view, you do not need to create and assign a large icon list. If you create both large and small image lists, they must contain the same images in the same order, because a single value is used to identify a list-view item's icon in both image lists.
// InitListViewImageLists - creates image lists for a list-view control.
// This function only creates image lists. It does not insert the items into
// the control, which is necessary for the control to be visible.
// Returns TRUE if successful, or FALSE otherwise.
// hWndListView - handle to the list-view control.
// global variable g_hInst - the handle to the module of either a
// dynamic-link library (DLL) or executable (.exe) that contains
// the image to be loaded. If loading a standard or system
// icon, set g_hInst to NULL.
BOOL InitListViewImageLists(HWND hWndListView)
{
HICON hiconItem; // icon for list-view items
HIMAGELIST hLarge; // image list for icon view
HIMAGELIST hSmall; // image list for other views
// Create the full-sized icon image lists.
hLarge = ImageList_Create(GetSystemMetrics(SM_CXICON),
GetSystemMetrics(SM_CYICON),
ILC_MASK, 1, 1);
hSmall = ImageList_Create(GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
ILC_MASK, 1, 1);
// Add an icon to each image list.
hiconItem = LoadIcon(g_hinst, MAKEINTRESOURCE(IDI_ITEM));
ImageList_AddIcon(hLarge, hiconItem);
ImageList_AddIcon(hSmall, hiconItem);
DestroyIcon(hiconItem);
/************************************************************************
Usually you have multiple icons; therefore, the previous four lines of
code can be inside a loop. The following code shows such a loop. The
icons are defined in the application's header file as resources, which
are numbered consecutively starting with IDS_FIRSTICON. The number of
icons is defined in the header file as C_ICONS.
for(index = 0; index < C_ICONS; index++)
{
hIconItem = LoadIcon (g_hinst, MAKEINTRESOURCE(IDS_FIRSTICON + index));
ImageList_AddIcon(hSmall, hIconItem);
ImageList_AddIcon(hLarge, hIconItem);
Destroy(hIconItem);
}
************************************************************************/
// Assign the image lists to the list-view control.
ListView_SetImageList(hWndListView, hLarge, LVSIL_NORMAL);
ListView_SetImageList(hWndListView, hSmall, LVSIL_SMALL);
return TRUE;
}
Adding List-View Columns
Columns are used when a list-view control is in report (details) view to display the items and subitems. Text from selected columns can also be displayed in tile view.
The following example adds columns to a list-view control. The column headings are defined in the application's header file as string resources, which are numbered consecutively starting with IDS_FIRSTCOLUMN. The number of columns is defined in the header file as C_COLUMNS.
// InitListViewColumns - adds columns to a list-view control.
// Returns TRUE if successful, or FALSE otherwise.
// hWndListView - handle to the list-view control.
BOOL InitListViewColumns(HWND hWndListView)
{
char szText[256]; // temporary buffer
LVCOLUMN lvc;
int iCol;
// Initialize the LVCOLUMN structure.
// The mask specifies that the format, width, text, and subitem members
// of the structure are valid.
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
// Add the columns
for (iCol = 0; iCol < C_COLUMNS; iCol++)
{
lvc.iSubItem = iCol;
lvc.pszText = szText;
lvc.cx = 100; // width of column in pixels
if ( iCol < 2 )
lvc.fmt = LVCFMT_LEFT; // left-aligned column
else
lvc.fmt = LVCFMT_RIGHT; // right-aligned column
LoadString(g_hinst,
IDS_FIRSTCOLUMN + iCol,
szText,
sizeof(szText)/sizeof(szText[0]));
if (ListView_InsertColumn(hWndListView, iCol, &lvc) == -1)
return FALSE;
}
return TRUE;
}
Adding List-View Items and Subitems
To add an item to a list-view control, an application must first define an LVITEM structure and then send an LVM_INSERTITEM message, specifying the address of the LVITEM structure.
If an application uses report view, subitem text must be entered. The following code examples fill an LVITEM structure and add the list-view items by using the LVM_INSERTITEM message or the corresponding macro ListView_InsertItem. Because the application saves its own text, it specifies the LPSTR_TEXTCALLBACK value for the pszText member of the LVITEM structure. Specifying the LPSTR_TEXTCALLBACK value causes the control to send an LVN_GETDISPINFO notification message to its owner window whenever it needs to display an item.
// This code example adds three items, each with three subitems, to
// a list-view control.
// hWndListView - handle to the list-view control.
// PETINFO - an application-specific structure.
typedef struct tagPETINFO
{
TCHAR szKind[10];
TCHAR szBreed[50];
TCHAR szPrice[20];
}PETINFO;
// A PETINFO variable is declared and initialized as follows:
PETINFO rgPetInfo[ ] =
{
{TEXT("Dog"), TEXT("Poodle"), TEXT("$300.00")},
{TEXT("Cat"), TEXT("Siamese"), TEXT("$100.00")},
{TEXT("Fish"), TEXT("Angel Fish"), TEXT("$10.00")},
};
.
.
.
// Some code to create the list-view control.
// Initialize LVITEM members that are common to all items.
lvI.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
lvI.state = 0;
lvI.stateMask = 0;
// Initialize LVITEM members that are different for each item.
for (index = 0; index < 3; index++)
{
lvI.iItem = index;
lvI.iImage = index;
lvI.iSubItem = 0;
lvI.lParam = (LPARAM) &rgPetInfo[index];
lvI.pszText = LPSTR_TEXTCALLBACK; // sends an LVN_GETDISP message.
ListView_InsertItem(hWndListView, &lvI) == -1);
}
.
.
.
// The following case statement is in the message handler for the window that hosts
// the control.
case WM_NOTIFY:
switch (((LPNMHDR) lParam)->code)
{
case LVN_GETDISPINFO:
NMLVDISPINFO* plvdi = (NMLVDISPINFO*)lParam;
switch (plvdi->item.iSubItem)
{
case 0:
plvdi->item.pszText = rgPetInfo[plvdi->item.iItem].szKind;
break;
case 1:
plvdi->item.pszText = rgPetInfo[plvdi->item.iItem].szBreed;
break;
case 2:
plvdi->item.pszText = rgPetInfo[plvdi->item.iItem].szPrice;
break;
default:
break;
}
return 0;
}
// NOTE: in addition to setting pszText to point to the item text, you could
// copy the item text into pszText using StringCchCopy. For example:
// StringCchCopy(rgPetInfo[plvdi->item.iItem].szKind,
// sizeof(rgPetInfo[plvdi->item.iItem].szKind),
// plvdi->item.pszText);
Using Tiles
In tile view, each item is represented by a large icon with accompanying text on one or more lines. For an illustration, see About List-View Controls.
You can set general display parameters for tile view by using the ListView_SetTileViewInfo macro. The LVTILEVIEWINFO structure passed to this macro can specify the position of the text in relation to the icon, the size of each tile (including the text), and the maximum number of lines of text. Note that if you do not want tiles to be automatically sized, you must set LVTVIF_FIXEDSIZE in the dwFlags member and LVTVIM_TILESIZE in the dwMask member of LVTILEVIEWINFO, as well as providing the dimensions in the sizeTile member.
The following example code sets the tile view info for a list-view so that a maximum of two subitems are displayed for each item. It also sets the size of each tile.
// hwndList is the HWND of the control.
LVTILEVIEWINFO tileViewInfo = {0};
tileViewInfo.cbSize = sizeof(tileViewInfo);
tileViewInfo.dwFlags = LVTVIF_FIXEDSIZE;
tileViewInfo.dwMask = LVTVIM_COLUMNS | LVTVIM_TILESIZE;
tileViewInfo.cLines = 2;
SIZE size = { 100, 50 };
tileViewInfo.sizeTile = size;
ListView_SetTileViewInfo(hwndList, &tileViewInfo);
For each item in the list, you can set further parameters when the item is inserted in the list, or later. The LVITEM structure used with ListView_InsertItem contains members that specify which columns of data to display underneath the item name, and their alignment. ("Columns" here refers not to display columns in tile view but rather to subitems, which are displayed in columns in details view.) These same display parameters are also found in the LVTILEINFO structure used with ListView_SetTileInfo.
Using Groups
In all views except list view, list-view items can be arranged in groups. Grouping allows a user to arrange lists into groups of items that are visually divided on the page using a horizontal divider and a group title. Grouping items makes it simpler to scan large lists for information. Groups can be created based on item properties, attributes, or other characteristics.
Each group can be distinguished by various kinds of text at the head and foot, and by an image, as specified in the LVGROUP structure. You can also set display parameters for each group by using ListView_SetGroupMetrics.
To use groups in a list-view, make sure the LVS_ALIGNTOP window style is set on the control. See List-View Window Styles.
An optional part of a group header is the task link. When the user clicks this link, an LVN_LINKCLICK notification is sent.
The following example shows how to create a group with a header and insert it at the end of the list of groups.
// hwndList is the HWND of the control.
LVGROUP group;
group.cbSize = sizeof(LVGROUP);
group.mask = LVGF_HEADER | LVGF_GROUPID;
group.pszHeader = TEXT("Dogs");
group.iGroupId = 1;
ListView_InsertGroup(hwndList, -1, &group);
When you add an item to the list, you can assign it to a group by matching the iGroupId member of its LVITEM structure to the iGroupId member of the LVGROUP structure. An item that is not assigned to a group does not appear in the list when group view is enabled.
To turn group view on or off, use the ListView_EnableGroupView macro.
Using List-View Working Areas
When a working area is created, items that lie within the working area become members of it. Similarly, if an item is moved into a working area, the item becomes a member of the working area to which it was moved. If an item does not lie within any working area, it automatically becomes a member of the first (index 0) working area. If you want to create an item and have it placed within a specific working area, you will need to create the item and then move it into the desired working area using an LVM_SETITEMPOSITION or LVM_SETITEMPOSITION32 message.
To determine which working area an item belongs to, retrieve the working areas, retrieve the position of the item, and compare its position to the working areas. The following function returns the index of the working area to which the item belongs. If the function fails, it returns -1. If the function succeeds, but the item is not inside any of the working areas, the function returns 0, because all items that are not inside a working area automatically become a member of working area zero.
int GetItemWorkingArea(HWND hWndListView, int iItem)
{
UINT uWorkAreas = 0;
int nReturn = -1;
LPRECT pRects;
POINT pt;
if(!ListView_GetItemPosition(hWndListView, iItem, &pt))
return nReturn;
ListView_GetNumberOfWorkAreas(hWndListView, &uWorkAreas);
if(uWorkAreas)
{
pRects = (LPRECT)GlobalAlloc(GPTR, sizeof(RECT) * uWorkAreas);
if(pRects)
{
UINT i;
nReturn = 0;
ListView_GetWorkAreas(hWndListView, uWorkAreas, pRects);
for(i = 0; i < uWorkAreas; i++)
{
if(PtInRect((pRects + i), pt))
{
nReturn = i;
break;
}
}
GlobalFree((HGLOBAL)pRects);
}
}
return nReturn;
}
Multiple working areas can be used for creating different areas within one view. You can create areas in a single view that have different meanings. For example, a view of a file system might have an area for read/write files and another area for read-only files. The user can categorize items by placing them in different working areas. If a file were moved into the read-only area, it would automatically become read-only.
Multiple working areas can intersect, but any items that lie within the intersection become members of the area with the lower index; therefore, it is best to avoid this situation. When sorting multiple work areas, the items are sorted compared to the other items in the same working area.
The following code examples show how to create working areas. The first example creates one working area with a 25-pixel empty space around the left, top, and right sides of the items.
{
#define EMPTY_SPACE 25
RECT rcClient;
GetClientRect(hWndListView, &rcClient);
rcClient.left += EMPTY_SPACE;
rcClient.top += EMPTY_SPACE;
rcClient.right -= (EMPTY_SPACE * 2);
SendMessage(hWndListView, LVM_SETWORKAREAS, 1, (LPARAM)&rcClient);
}
This example demonstrates how to create two working areas in a control that take up about half of the client area and have a 25-pixel border around each working area.
{
#define EMPTY_SPACE 25
RECT rcClient;
RECT rcWork[2];
GetClientRect(hWndListView, &rcClient);
rcWork[0].left = rcClient.left + EMPTY_SPACE;
rcWork[0].top = rcClient.top + EMPTY_SPACE;
rcWork[0].right = (rcClient.right/2) - EMPTY_SPACE;
rcWork[0].bottom = rcClient.bottom;
rcWork[1].left = (rcClient.right/2) + EMPTY_SPACE;
rcWork[1].top = rcClient.top + EMPTY_SPACE;
rcWork[1].right = rcClient.right - EMPTY_SPACE;
rcWork[1].bottom = rcClient.bottom;
SendMessage(hWndListView, LVM_SETWORKAREAS, 2, (LPARAM)rcWork);
}
Using Virtual List-View Controls
This section includes example code used to implement a virtual list-view control. The code contains application-defined functions and structures that implement aspects of a virtual list-view control and perform cache management.
Note This example code assumes that the cache used is a dynamically allocated array of the application-defined structure Sample Structure: RndItem. The list-view control in the example is assumed to have three columns—one column for the item label and two columns for subitems.
Sample Function: OnNotify
This application-defined function handles notification messages commonly sent from a virtual list-view control.
LRESULT OnNotify(HWND hwnd, NMHDR* pnmhdr)
{
// MAXCOUNT and SIZE are application-defined values.
HRESULT hr;
LRESULT lrt = FALSE;
switch (pnmhdr->code)
{
case LVN_GETDISPINFO:
{
RndItem rndItem;
NMLVDISPINFO* plvdi = (NMLVDISPINFO*) pnmhdr;
if (-1 == plvdi->item.iItem)
{
OutputDebugString(TEXT("LVOWNER: Request for -1 item?\n"));
DebugBreak();
}
// Retrieve information for item at index iItem.
RetrieveItem( &rndItem, plvdi->item.iItem );
if(plvdi->item.mask & LVIF_STATE)
{
// Fill in the state information.
plvdi->item.state |= rndItem.state;
}
if(plvdi->item.mask & LVIF_IMAGE)
{
// Fill in the image information.
plvdi->item.iImage = rndItem.iIcon;
}
if(plvdi->item.mask & LVIF_TEXT)
{
// Fill in the text information.
switch (plvdi->item.iSubItem)
{
case 0:
// Copy the main item text.
hr = StringCchCopy(plvdi->item.pszText, MAX_COUNT, rndItem.Title);
if(FAILED(hr))
{
// Insert code for error handler here. MAX_COUNT
// is a user-defined value. You must not enter
// more characters than specified by MAX_COUNT or
// the text will be truncated.
}
break;
case 1:
// Copy subitem 1 text.
hr = StringCchCopy
( plvdi->item.pszText, MAX_COUNT,
rndItem.SubText1 );
if(FAILED(hr))
{
// Insert code for error handler here. MAX_COUNT
// is a user-defined value. You must not enter
// more characters than specified by MAX_COUNT or
// the text will be truncated..
}
break;
case 2:
// Copy subitem 2 text.
hr = StringCchCopy
( plvdi->item.pszText, MAX_COUNT,
rndItem.SubText2 );
if(FAILED(hr))
{
// Insert code for error handler here. MAX_COUNT
// is a user-defined value. You must not enter
// more characters than specified by MAX_COUNT or
// the text will be truncated..
}
break;
default:
break;
}
}
lrt = FALSE;
break;
}
case LVN_ODCACHEHINT:
{
NMLVCACHEHINT* pcachehint = (NMLVCACHEHINT*) pnmhdr;
// Load the cache with the recommended range.
PrepCache( pcachehint->iFrom, pcachehint->iTo );
break;
}
case LVN_ODFINDITEM:
{
LPNMLVFINDITEM pnmfi = (LPNMLVFINDITEM)pnmhdr;
// Call a user-defined function to find the index according to
// LVFINDINFO, which is embedded in the LPNMLVFINDITEM structure.
// Set return value to -1 if no hits are found.
break;
}
default:
break;
} // end switch
return( lrt );
}
Sample Function: PrepCache
This function accepts the range values for the cache suggested by a virtual list-view control. It performs a verification to determine that the requested range is not already cached, and then it allocates the required global memory and fills the cache if necessary.
void PrepCache(int iFrom, int iTo)
{
/* Global Variables
* g_priCache[ ] is the main cache.
* g_iCache is the index of the first item in the main cache.
* c_cCache is the count of items in the main cache.
*
* g_priEndCache[ ] is the cache of items at the end of the list.
* g_iEndCache is the index of the first item in the end cache.
* g_cEndCache is the count of items in the end cache.
*/
// Local Variables
int i;
BOOL fOLFrom = FALSE;
BOOL fOLTo = FALSE;
// Check to see if this is the end cache.
if ((iTo == g_cItems - 1) && ((iTo - iFrom) < 30)) // 30 entries wide
{
// Check to see if this is a portion of the current end cache.
if ((g_cCache) &&
(iFrom >= g_iEndCache) &&
(iFrom < g_iEndCache+g_cEndCache))
return;
// If it is a part of current end cache, no loading is necessary.
// This is a new end cache; free the old memory.
if ( g_priEndCache )
GlobalFree( g_priEndCache );
// Set the index and count values for the new end cache; then
// retrieve the memory.
g_iEndCache = iFrom;
g_cEndCache = (iTo - iFrom + 1);
g_priEndCache = (PRndItem)GlobalAlloc(GPTR, sizeof(RndItem) * g_cEndCache);
if (! g_priEndCache);
// Out of memory. Perform error handling operations.
// Loop to fill the cache with the recommended items.
for (i=0; i<g_cEndCache; i++);
// Call function that accesses item information and
// fills a cache element here.
}
else
{
// It is not a member of the current end cache.
// Try the primary cache instead.
// Check to see if iFrom is within the primary cache.
if ((g_cCache) &&
(iFrom >= g_iCache) &&
(iFrom < g_iCache+g_cCache))
fOLFrom = TRUE;
// Check to see if iTo is within the primary cache.
if ((g_cCache) &&
(iTo >= g_iCache) &&
(iTo <= g_iCache+g_cCache))
fOLTo = TRUE;
// If both iFrom and iTo are within current cache, no work is needed.
if (fOLFrom && fOLTo)
return;
// Enlarge the cache size rather than make it specific to this hint.
if (fOLFrom)
iFrom = g_iCache;
else if (fOLTo)
iTo = g_iCache + g_cCache;
// A new primary cache is needed; free the old one.
if ( g_priCache )
GlobalFree( g_priCache );
// Set the index and count values for the new primary cache, and
// then retrieve the memory.
g_iCache = iFrom;
g_cCache = (iTo - iFrom + 1);
g_priCache = (PRndItem)GlobalAlloc( GPTR, sizeof
( RndItem ) * g_cCache );
if (!g_priEndCache);
// Out of memory. Do error handling.
// Loop to fill the cache with the recommended items.
for (i=0; i<g_cEndCache; i++)
// Call function that accesses item information and fills a
// cache element here.
}
}
Sample Function: RetrieveItem
This example function accepts two parameters—the address of the application-defined structure Sample Structure: RndItem and an integer value representing the index of the item in the list. It checks the index value to discover if the desired item is cached. If it is, the pointer that was passed to it is set to a location in the cache. If the item is not in the main or end cache, the item information must be located manually.
void RetrieveItem( PRndItem prndItem, int index )
{
/* Global Variables
* g_priCache[ ] is the main cache.
* g_iCache is the index of the first item in the main cache.
* c_cCache is the count of items in the main cache.
*
* g_priEndCache[ ] is the cache of items at the end of the list.
* g_iEndCache is the index of the first item in the end cache.
* g_cEndCache is the count of items in the end cache.
*/
// Check to see if the item is in the main cache.
if ((index >= g_iCache) && (index < g_iCache + g_cCache))
*prndItem = g_priCache[index-g_iCache];
// If it is not in the main cache, check to see if the item
// is in the end area cache.
else if ((index >= g_iEndCache) && (index < g_iEndCache + g_cEndCache))
*prndItem = g_priEndCache[index-g_iEndCache];
else
{
// The item is not in either cache;
// you should retrieve the item information manually.
}
}
Sample Structure: RndItem
This example user-defined structure holds information placed in the cache. The structure has the following form.
typedef struct _RndItem
{
int iIcon; // bitmap assigned to this item
TCHAR Title[SIZE]; // SIZE is a user-defined macro value
UINT state; // item state value
TCHAR SubText1[SIZE]; // text for the label of the first subitem
TCHAR SubText2[SIZE]; // text for the label of the second item
} RndItem, *PRndItem;
Related Topics
For a list of the window messages processed by a list-view control, see Default List-View Message Processing.