Teach Yourself Visual C++® 5 in 24 Hours

Previous chapterNext chapterContents


- Hour 18 -
List View Controls

List views are extremely flexible controls that allow information to be displayed in a variety of ways. In this hour, you will learn

What Is a List View Control?

List view controls are one of the new common controls first released with Windows 95. A list view control is used to display information and an associated icon in one of four different formats:

When you use a list view control, you can provide a menu or other method to enable the user to switch between the different viewing modes.

The Windows Explorer uses a list view control and offers all four view styles. The Explorer is shown in Figure 18.1 with the contents of the C:\ directory contained in a large icon view.

Figure 18.1.
The Windows Explorer uses a list view control.

List views are very popular with users because they offer several different ways to display information. When you allow the user to switch between view styles, the list view control puts the user in charge of how information is displayed.

List view controls can be used to associate different icons with items stored in the list view control, as the Explorer does for filenames. The user is free to select between different sized icons, or even the report view, which can display extra information about each item. List view controls also support drag-and-drop operations, which enable the user to easily move items to or from a list view control.

List View Control Properties

The list view control's properties are set using the Properties dialog box in the same way other controls are. Some of the properties available for list view controls are also available for list boxes. The list view control property options include the following:

A List View Control Example

As an example of how the basic properties of list view controls are used, create a dialog box-based application. This program displays a list view control containing three items. You can use radio buttons to switch between the different view styles.

Use AppWizard to create a dialog box-based application named ListEx. Feel free to accept any of the options offered by AppWizard; this example works with any AppWizard parameters.

Adding Controls to the ListEx Dialog Box

You must add a total of five controls to the ListEx main dialog box: four radio buttons and one list view control. Add the controls to the dialog box as shown in Figure 18.2.

Figure 18.2.
The ListEx main dialog box in the dialog editor.

Properties for the list view and radio button controls are listed in Table 18.1. All properties that aren't listed should be set to the default values.

Table 18.1. Property values for controls in the ListEx main dialog box.

Control Resource ID Caption
Icon view radio IDC_RADIO_ICON &Icon
Small radio IDC_RADIO_SMALL &Small
List radio IDC_RADIO_LIST &List
Report radio IDC_RADIO_REPORT &Report
List view control IDC_LIST none

Use ClassWizard to associate a CListCtrl member variable with IDC_LIST, using the values from Table 18.2.

Table 18.2. Values for a new CListCtrl member variable in CListExDlg.

Control ID Variable Name Category Type
IDC_LIST m_listCtrl Control CListCtrl

Next, use ClassWizard to create message-handling functions that are called when the radio buttons are selected. Add a total of four member functions to the CListExDlg class, using the values from Table 18.3.

Table 18.3. Values for new CListCtrl member functions in CListExDlg.

Object ID Class Name Message Function
IDC_RADIO_ICON CListExDlg BN_CLICKED OnRadioIcon
IDC_RADIO_SMALL CListExDlg BN_CLICKED OnRadioSmall
IDC_RADIO_LIST CListExDlg BN_CLICKED OnRadioList
IDC_RADIO_REPORT CListExDlg BN_CLICKED OnRadioReport

Associating Image Lists with a List Control

The images displayed in the list view next to each item are stored in image lists that are associated with the list view control. Constructing and managing image lists was discussed in Hour 17, "Using Image Lists and Bitmaps." An image list is added to a list view control with the SetImageList function:

m_listCtrl.SetImageList( &m_imageSmall, LVSIL_SMALL );

Two parameters are passed to the list view control: the address of the image list and a style parameter that indicates the type of images stored in the image list. There are three image list types.


Just a Minute: After the image list control has been added to the list view control, the list view control takes responsibility for destroying the image list.


CAUTION: If the Share image list property has been selected, the list view control will not destroy the image list. You must destroy it yourself after the list view has been destroyed.
If you don't destroy the image list, you will create a memory leak. If you destroy the image list too early, the list view control will behave unpredictably.

Creating the Image Lists

You must create two bitmaps for the ListEx application. One bitmap is used for the large icon bitmap and one for the small icon bitmap. The two bitmaps are shown in Figure 18.3. Each of the bitmaps contains two balls of the same size, and each ball is a different color.

Figure 18.3.
Bitmaps used for the ListEx image lists.

The properties for the two bitmaps are provided in Table 18.4.

Table 18.4. Properties for the ListEx image list bitmaps.

Resource ID Width Height Background
IDB_BALLS 64 32 White
IDB_SM_BALLS 32 16 White

Adding Items to a List View Control

The LV_ITEM structure is used to represent an item in a list view control. This structure is used when adding, modifying, or fetching list view items. The data members for the LV_ITEM structure include the following:

The LV_ITEM structure's mask member is used to indicate which parts of the structure are valid or should be filled in. It can be one or more of the following values:


Time Saver: When more than one value is needed, combine them using the C++ OR | operator:

listItem.mask = LVIF_TEXT | LVIF_IMAGE;



Inserting a List View Item

The InsertItem function is used to add an item to a list view control:

m_listItem.InsertItem( &listItem );

A pointer to an LV_ITEM structure is passed as the parameter to InsertItem. LV_ITEM data members are filled with data for the new item before it is inserted.

listItem.mask = LVIF_TEXT;
listItem.iItem = 0;
listItem.pszText = szText;
m_listCtrl.InsertItem( &listItem );

Adding Column Information for the Report View

Unlike the other three list view styles, the report view displays additional information for each item contained in the list. The extra items are subitems that are arranged in columns. Each list view item must have the same number of subitems. For example, in the Windows Explorer, the subitems are used for file information such as file size, file type, and modified date.

Columns for subitems are added to a list view control in two steps: first, the LV_COLUMN data structure is initialized and then the columns are added. List view columns are inserted using LV_COLUMN structures and the InsertColumn function. The LV_COLUMN structure has the following members:

The mask member variable is used to specify which member values are valid. Possible values include the following:

After you fill in the values for an LV_COLUMN structure, the column is added to the list view control using the InsertColumn function:

m_listCtrl.InsertColumn( nColumn, &listColumn );

Determining Which Items Are Selected

Unlike a list box control, no single message or function exists to determine which items are selected in a list view control. Instead, you must use the CListCtrl::GetNextItem function, as in this example:

int nSel = m_listCtrl.GetNextItem( -1, LVNI_SELECTED );

This code returns the index of the first selected item in the list view control. GetNextItem has two parameters: the start item and a search flag. If the start item is -1, the search starts with the first item. The flag variable can include one geometric value and one state value. The following are the geometric values:

The following are the possible state values:

If no item can be found that matches the search parameters, -1 is returned. Otherwise, the index of the first list item that satisfies the criteria is returned.

Modifications to the CListExDlg Class

The CListExDlg class must have two small modifications made to its class declaration:

Add the source code provided in Listing 18.1 to the implementation section of the CListExDlg class declaration.

TYPE: Listing 18.1. Changes to the CListExDlg class declaration.

// Implementation
protected:
    void SetListView( DWORD dwView );
    CImageList  m_imageLarge;
    CImageList  m_imageSmall;

The CListExDlg::OnInitDialog member function is called when the main dialog box is initialized. Add the source code in Listing 18.2 to the OnInitDialog function, just after the // TODO comment provided by AppWizard.

TYPE: Listing 18.2. Changes to the CListExDlg class implementation.

// TODO: Add extra initialization here
// Create and set image lists
m_imageLarge.Create( IDB_BALLS, 32, 1, RGB(255,255,255) );
m_imageSmall.Create( IDB_SM_BALLS, 16, 1, RGB(255,255,255) );
m_listCtrl.SetImageList( &m_imageLarge, LVSIL_NORMAL );
m_listCtrl.SetImageList( &m_imageSmall, LVSIL_SMALL );
// Create list view columns
LV_COLUMN   listColumn;
LV_ITEM     listItem;
char*       arColumns[3] = { "City", "Football", "Baseball" };
listColumn.mask = LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM;
listColumn.fmt = LVCFMT_LEFT;
listColumn.cx = 60;
for( int nColumn = 0; nColumn < 3; nColumn++ )
{
    listColumn.iSubItem = nColumn;
    listColumn.pszText = arColumns[nColumn];
    m_listCtrl.InsertColumn( nColumn, &listColumn );
}
// Add list items
listItem.mask = LVIF_TEXT | LVIF_IMAGE;
listItem.iSubItem = 0;
char* arCity[3]      = { "Oakland", "San Diego", "Seattle" };
char* arFootball[3]  = { "Raiders", "Chargers", "Seahawks" };
char* arBaseball[3]  = { "Athletics", "Padres", "Mariners" };
for( int nItem = 0; nItem < 3; nItem++ )
{
    listItem.iItem = nItem;
    listItem.pszText = arCity[nItem];
    listItem.iImage = nItem % 2;
    m_listCtrl.InsertItem( &listItem );
    m_listCtrl.SetItemText( nItem, 1, arFootball[nItem] );
    m_listCtrl.SetItemText( nItem, 2, arBaseball[nItem] );
}

The source code provided in Listing 18.2 creates two image lists for the list view control and then creates the list view's column headers. After the columns are created, the three list items are inserted into the list view. The SetItemText function is used to add subitem text strings to each list item--in this case, the name of the professional football and baseball teams for each city.

Changing the Current View for a List View Control

Switching views in a list view control requires just a few lines of code. The current view style is stored in a structure maintained by Windows. This information can be retrieved using the GetWindowLong function:

DWORD dwOldStyle = GetWindowLong( hWndList, GWL_STYLE );

The GetWindowLong function has two parameters:

The return value from GetWindowLong contains all the Windows style information for the list view control. If you are interested in the current view, the unnecessary information should be masked off using LVS_TYPEMASK.

dwOldStyle &= ~LVS_TYPEMASK; // Mask off extra style info

After the mask has been applied, the style information is one of the following values:

To change to another view, you use the SetWindowLong function. When applying a new list view style, you must make sure that the style bits that are not associated with the list view style are left undisturbed. This is usually done in the following four steps:

1. Get the existing window style bit information using GetWindowLong.

2. Strip off the list view style information, leaving the other style information intact.

3. Combine a new list view style with the old style information.

4. Apply the new style information using SetWindowLong.

These steps are often combined into a few lines of code in the following way:

DWORD dwNewStyle = LVS_ICON;     // Changing to icon view
DWORD dwOldStyle = GetWindowLong( hWndList, GWL_STYLE );
dwNewStyle |= ( dwOldStyle &= ~LVS_TYPEMASK );
SetWindowLong( hWndList, GWL_STYLE, dwNewStyle );

Switching Between View Styles

The source code provided in Listing 18.3 is used to switch between list view styles. When a radio button is selected, its message-handling function is called. Each message-handling function passes a different list view style parameter to the SetListView member function. The SetListView function uses the SetWindowLong function to change the list view to the selected style.

TYPE: Listing 18.3. Functions used to change the control's view style.

void CListExDlg::OnRadioIcon()
{
    SetListView( LVS_ICON );
}
void CListExDlg::OnRadioList()
{
    SetListView( LVS_LIST );
}
void CListExDlg::OnRadioReport()
{
    SetListView( LVS_REPORT );
}
void CListExDlg::OnRadioSmall()
{
    SetListView( LVS_SMALLICON );
}
void CListExDlg::SetListView( DWORD dwNewStyle )
{
    DWORD   dwOldStyle;
    HWND    hWndList;

    hWndList = m_listCtrl.GetSafeHwnd();

    dwOldStyle = GetWindowLong( hWndList, GWL_STYLE );

    if( (dwOldStyle & LVS_TYPEMASK) != dwNewStyle )
    {
        // Don't forget the tilde before LVS_TYPEMASK !
        dwOldStyle &= ~LVS_TYPEMASK;
        dwNewStyle |= dwOldStyle;
        SetWindowLong( hWndList, GWL_STYLE, dwNewStyle );
    }
}

Compile and run the ListEx sample program. The list view initially displays its contents in the icon view. Try using the radio buttons to switch between views. When the report view is displayed, use the header control to change the spacing between columns. Figure 18.4 shows the ListEx program running.

Figure 18.4.
The ListEx sample program shows subitems and small icons in Report view.

Congratulations! You now have an example of a basic list view control. In the next few sections, you will add label editing and drag-and-drop functionality.

Editing Items in Place

The list view control offers a built-in edit control that you can use to edit items contained in the control. You can add this feature to the ListEx example in a few minutes. In order to take advantage of this capability, the list view control must have the LVS_EDITLABELS style, which is set by clicking the Edit Labels check box on the list view's property page.

In addition to setting the list view control's style, you must handle two notification messages that relate to label editing:

When your application receives the LVN_BEGINLABELEDIT message, you have the opportunity to allow or prevent editing of the label. As with other notification messages, one of the parameters passed to your handler for the notification messages is *pResult. You should set this value to FALSE or zero to allow the label edit to begin, or TRUE to prevent the label edit from starting.


Time Saver: To access the edit control used to edit the label, use the GetEditControl member function in the CListCtrl class.

In most cases, you respond to the LVN_ENDLABELEDIT message and update the application's data. To allow a change, set *pResult to TRUE; to reject a label change, set *pResult to FALSE.


CAUTION: If you allow the user to change a label, you might need to update the underlying data. There are two cases in which you should not update your data: if the LV_ITEM pszText member passed with the message is NULL, or if the LV_ITEM iItem member is set to -1. In these cases, the user has canceled the label-editing operation.

To add label editing to the ListEx example, begin by opening the IDD_LISTEX_DIALOG dialog box resource. Check the Edit Labels check box in the styles property sheet for the list control. Next, add message-handling functions for the label editing messages, using the values from Table 18.5.

Table 18.5. Values for drag-and-drop functions in CListExDlg.

Object ID Class Name Message Function
IDC_RADIO_LIST CListExDlg LVN_BEGINEDIT OnBeginlabeleditList
IDC_RADIO_LIST CListExDlg LVN_ENDEDIT OnEndlabeleditList

Listing 18.4 shows the source code for the message-notification handlers to be added to the CListExDlg class.

TYPE: Listing 18.4. Handling the LVN_ENDLABELEDIT and LVN_ENDLABELEDIT messages.

void CListExDlg::OnBeginlabeleditList(NMHDR* pNMHDR,
                                      LRESULT* pResult)
{
    *pResult = FALSE;
}

void CListExDlg::OnEndlabeleditList(NMHDR* pNMHDR,
                                    LRESULT* pResult)
{
    LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;

    if((pDispInfo->item.pszText != NULL)&&
       (pDispInfo->item.iItem != -1))
    {
        // Label edit not cancelled by user
    }
    *pResult = TRUE;
}


Just a Minute: Remember that the Edit control updates only what is displayed in the list view control; you must update your document or other database in response to LVN_ENDLABELEDIT.


DO/DON'T:
DO
set the Edit labels property for the list control.
DO handle the LVN_BEGINLABELEDIT message and set *pResult to FALSE to allow an edit to begin.
DO handle the LVN_ENDLABELEDIT message and set *pResult to TRUE to allow an edit to be completed.
DO remember to update your application's data after an edit has been completed.
DON'T forget to test iItem and pszText to see whether the user has canceled the edit operation.

Summary

In this hour, you learned about the list view control and how it is used in an MFC-based program. You have also learned how this control interacts with image lists and header controls, and you built a sample application to demonstrate how a list view control is used.

Q&A

Q How can I limit the amount of text the user can enter when editing a label?

A When handling the LVN_BEGINLABELEDIT message, set the maximum text limit by calling the edit control's LimitText member function:
void CListExDlg::OnBeginlabeleditList(NMHDR* pNMHDR, LRESULT* pResult)
{
    LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
    CEdit* pEdit = m_listCtrl.GetEditControl();
    pEdit->LimitText( 15 );
    *pResult = 0;
}
Q In a multiple selection list view, how can I determine which items have been selected?

A As discussed earlier this hour, you can use CListCtrl's GetNextItem member function to determine the first selected item in a list view control. To determine all selected items, you must use a while loop to continue to search for selected items until you reach the end of the list, like this:
void CListExDlg::OnTest()
{
    int nTotalSelected = 0;
    int nSel = m_listCtrl.GetNextItem(-1, LVNI_SELECTED);
    while(nSel != -1)
    {
        nTotalSelected++;
        nSel = m_listCtrl.GetNextItem(nSel, LVNI_SELECTED);
    }
    TRACE("Total selected = %d\n", nTotalSelected);
}

Workshop

The Workshop is designed to help you anticipate possible questions, review what you've learned, and begin thinking ahead to put your knowledge into practice. The answers to the quiz are in Appendix B, "Quiz Answers."

Quiz

1. What styles are available for the list view?

2. What are the sizes of the icons used by the list view control?

3. What messages must be handled when the user edits a label?

4. How do you discover that the user has canceled a label edit?

5. What Windows function is used to change list view styles?

6. What steps are required to add a column to a list view?

7. How do you add text for a list view subitem?

8. What is the return value when the InsertItem function fails?

Exercises

1. Make it possible for a user to add new items to the list view control in the ListEx project.

2. Add a pop-up menu to the ListEx project that enables the user to select which list view style should be used. Display the pop-up menu when the user right-clicks the list view control.


Previous chapterNext chapterContents


Macmillan Computer Publishing USA

© Copyright, Macmillan Computer Publishing. All rights reserved.