Chapter 10

Image List, List View, and Tree View Controls


Two of the most complex controls to use in your programs are the list view and tree view controls. To add to the confusion, both of these controls must be associated with image list controls that contain the icons that'll be displayed in the control. The image list control, although basically little more than an array of pictures, is one of the most misunderstood controls, probably because it doesn't appear on the screen, but rather stays hidden in memory like other types of data structures. In this chapter, you'll get a chance to create and use all three of these special controls.

The Image List Control

Often in programs, you need to use a lot of images that are related in some way. For example, your application might have a toolbar with many command buttons, each of which uses a bitmap for its icon. In a case like this, it would be great to have some sort of program object that could, not only hold the bitmaps, but also organize them so they can be accessed easily. That's exactly what an image list control does for you. An image list does nothing more than store a list of related images. You can use the images any way you see fit in your program. However, several Windows 95 controls rely on or use image lists. These controls are:

Besides the preceding controls, you will undoubtedly come up with many other uses for image lists. You might, for example, have an animation sequence that you'd like to display in a window. An image list is the perfect storage place for the frames that make up the animation because you can easily access any frame just by using an index.

If the word index makes you think of arrays, you're close to understanding how an image list stores images. An image list is much like an array that holds pictures rather than integers or floating-point numbers. Just as with an array, you initialize each element of an image list and thereafter can access any part of the array using an index.

You won't, however, ever see in your running application an image list control in the same way that you can see a status bar or a progress bar control. That's because (again, like an array) an image list is nothing more than a storage structure for pictures. You can display the images stored in an image list, but you can't display the image list itself. Figure 10.1 shows how an image list is organized.


FIG. 10.1

An image list is much like an array of pictures.

Now that you have some idea of what an image list is, you can add one to Win95 Controls App. Because image list controls are so closely related to list view and tree view controls, you'll create the application's image lists in the functions that will eventually create the application's list view and tree view controls. Complete the steps that follow to perform these tasks.

The complete source code and executable file for this part of the Win95 application can be found in the CHAP10\Win95, Part 5 directory of this book's CD-ROM.

  1. Load the WIN95VIEW.CPP file, and add the following lines to the OnCreate() function, right after the line CreateSpinner(), which you placed there previously.
       CreateListView();
        CreateTreeView();
  2. Add the functions shown in Listing 10.1 to the end of the WIN95VIEW.CPP file.

    Listing 10.1 LST10_01.CPP The CreateListView() and CreateTreeView() functions

    void CWin95View::CreateListView()
    {
        // Create the Image List controls.
        m_smallImageList.Create(16, 16, FALSE, 1, 0);
        m_largeImageList.Create(32, 32, FALSE, 1, 0);
        HICON hIcon = ::LoadIcon (AfxGetResourceHandle(),
            MAKEINTRESOURCE(IDI_ICON1));
        m_smallImageList.Add(hIcon);
        hIcon = ::LoadIcon (AfxGetResourceHandle(),
            MAKEINTRESOURCE(IDI_ICON2));
        m_largeImageList.Add(hIcon);
    }
    void CWin95View::CreateTreeView()
    {
        // Create the Image List.
        m_treeImageList.Create(13, 13, FALSE, 3, 0);
        HICON hIcon = ::LoadIcon(AfxGetResourceHandle(),
            MAKEINTRESOURCE(IDI_ICON3));
        m_treeImageList.Add(hIcon);
        hIcon = ::LoadIcon(AfxGetResourceHandle(),
            MAKEINTRESOURCE(IDI_ICON4));
        m_treeImageList.Add(hIcon);
        hIcon = ::LoadIcon(AfxGetResourceHandle(),
            MAKEINTRESOURCE(IDI_ICON5));
        m_treeImageList.Add(hIcon);
    }
  3. Copy the ENVELOP1.ICO, ENVELO2.ICO, ICON3.ICO, ICON4.ICO, and ICON5.ICO files from this book's CD-ROM to Win95 Controls App's RES directory.
  4. Select Developer Studio's Insert, Resource command. The Insert Resource dialog box appears, as shown in Figure 10.2.

    FIG. 10.2

    You use the Insert Resource dialog box to add resources to your project.
  5. Click the Import button, and then use the Import Resource file browser (see Figure 10.3) to select the five icons you copied in step 4. The ENVELOP1.ICO file should get the resource ID IDI_ICON1, and the ENVELOP2.ICO file should get the ID IDI_ICON2. The files ICON3.ICO, ICON4.ICO, and ICON5.ICO should get the IDs IDI_ICON3, IDI_ICON4, and IDI_ICON5, respectively. Click the Import button to finalize your choices.

    FIG. 10.3

    Use the Import Resource file browser to select the icon files.

    To be sure that Developer Studio assigns the right IDs to the icons, you should import them one at a time, in the order listed in step 5. If you choose to import the icons all at once, you might have to change some of the assigned IDs to make them match the required IDs given in this step.

  6. Load the view class's header file (WIN95VIEW.H), and add the following lines to the class's Attributes section, right after the line CEdit m_buddyEdit, which you placed there previously.
        CImageList m_smallImageList;
        CImageList m_largeImageList;
        CImageList m_treeImageList;
  7. Add the following lines to the class's Implementation section, right after the line void CreateSpinner() you placed there previously.
        void CreateListView();
        void CreateTreeView();

To be sure that Developer Studio assigns the right IDs to the icons, you should import them one at a time, in the order listed in step 5. If you choose to import the icons all at once, you might have to change some of the assigned IDs to make them match the required IDs given in this step.

You have now completed the fifth part of Win95 Controls App. Compile and link the application by selecting the Build button in the toolbar, by selecting Developer Studio's Build, Build command, or by pressing F7. When you run the application, it won't look any different from the previous version. However, now the application creates three image list controls that it'll use with the list view and tree view controls you create later in this chapter.

Creating the Image List

In the Win95 Controls App application, image lists are used with the list view and tree view controls, so the image lists for the controls are created in the CreateListView() and CreateTreeView() local member functions, which the program calls from the view class's OnCreate() function. You create an image list, which is an object of the CImageList class, like this:

m_smallImageList.Create(16, 16, FALSE, 1, 0);

The Create() function's five arguments are the width of the pictures in the control, the height of the pictures, a Boolean value indicating whether the images contain a mask, the number of images initially in the list, and the number of images by which the list can dynamically grow. This last value is 0 to indicate that the list is not allowed to grow at runtime. The CImageList class overloads the Create() function so that you can create image lists in various ways. You can find the other versions of Create() in your Visual C++ online documentation.

Initializing the Image List

After you have an image list created, you'll want to add images to it. After all, an empty image list isn't a heck of a lot of use. The easiest way to add the images is to have the images as part of your application's resource file and to load them from there. For example, the following code shows how Win95 Controls App loads an icon into one of its image lists.

HICON hIcon = ::LoadIcon (AfxGetResourceHandle(),
    MAKEINTRESOURCE(IDI_ICON1));
m_smallImageList.Add(hIcon);

Here, the program first gets a handle to the icon. Then, it adds the icon to the image list by calling the image list's Add() member function. Table 10.1 lists other member functions you can use to manipulate an object of the CImageList class. As you can see, you have a lot of control over an image list if you really want to dig in. In the following sections, you'll see how to use an image list with the list view and tree view controls.

Table 10.1 Member Functions of the CImageList Class

Function Description Add()

Adds an image to the image list

Attach() Attaches an existing image list to an object of the CImageList class
BeginDrag() Starts an image-dragging operation
Create() Creates an image list control
DeleteImageList() Deletes an image list
Detach() Detaches an image list from an object of the CImageList class
DragEnter() Locks a window for updates and shows the drag image
DragLeave() Unlocks a window for updates
DragMove() Moves the drag image
DragShowNolock() Handles the drag image without locking the window
Draw() Draws an image that's being dragged
EndDrag() Ends an image-dragging operation
ExtractIcon() Creates an icon from an image
GetBkColor() Gets an image list's background color
GetDragImage() Gets the image for drag operations
GetImageCount() Gets the number of images in the control
GetImageInfo() Gets image information
GetSafeHandle() Gets an image list's handle
Read() Gets an image list from the given archive
Remove() Removes an image from the image list
Replace() Replaces one image with another
SetBkColor() Sets an image list's background color
SetDragCursorImage() Creates an image for drag operations
SetOverlayImage() Sets the index of an overlay mask
Write() Writes an image list to the given archive

The List View Control

Often, computer programs need to work with lists of objects and to organize those objects in such a way that the program's user can easily determine each object's attributes. An example is a group of files on a disk. Each file is a separate object that is associated with a number of attributes including the file's name, size, and the date the file was last modified. Windows shows files either as icons in a window or as a table of entries, each entry showing the attributes associated with the files. The user has full control over the way the file objects are displayed, including which attributes are shown and which are not listed. The Windows 95 common controls include something called a list view control, which enables Windows 95 programmers to organize lists in exactly the same way Windows 95 does with files and other objects.

If you'd like to see an example of a full-fledged list view control, just open the Windows 95 Explorer (see Figure 10.4). The right side of the window shows how the list view control can organize objects in a window. (The left side of the window contains a tree view control, which you learn about later in this chapter, in the section titled "The Tree View Control.") In the figure, the list view is currently set to the report view, in which each object in the list gets its own line showing, not only the object's name, but also the attributes associated with that object.


FIG. 10.4

Explorer uses list view controls to organize file information.

As I mentioned previously in this chapter, the user can change the way objects are organized in a list view control. Figure 10.5, for example, shows the list view portion of the Explorer set to the large icon setting, whereas Figure 10.6 shows the small icon setting, which enables the user to see more objects (in this case, files) in the window. The list view control also provides the user with the ability to edit the names of objects in the list, as well as to sort objects based on data displayed in a particular column. (This latter function works only when the list view control is in report view.)


FIG. 10.5

Here's Explorer's list view control set to large icons.


FIG. 10.6

Here's Explorer's list view control set to small icons.

To set Explorer's list view control to its different views, use the Large Icon, Small Icon, List, and Details commands on Explorer's View menu. If you have Explorer's toolbar displayed, you can also select these commands by clicking the appropriate toolbar buttons.

The Win95 Controls App application also sports a list view control, although it's not as fancy as Explorer's. To add the control to the application, complete the steps that follow.

The complete source code and executable file for this part of the Win95 application can be found in the CHAP10\Win95, Part 6 directory of this book's CD-ROM.

  1. Load the WIN95VIEW.CPP file, and add the following line to the OnDraw() member function, right after the line pDC->TextOut(20, 102, "Spinner Control"), which you placed there previously.

    pDC->TextOut(160, 102, "List-view control");

  2. Add the lines shown in Listing 10.2 to the end of the CreateListView() function.

    Listing 10.2 LST10_02.CPP New lines for the CreateListView() function

        // Create the List-view control.
        m_listView.Create(WS_VISIBLE | WS_CHILD | WS_BORDER |
            LVS_REPORT | LVS_NOSORTHEADER | LVS_EDITLABELS,
            CRect(160, 120, 394, 220), this, 105);
        m_listView.SetImageList(&m_smallImageList, LVSIL_SMALL);
        m_listView.SetImageList(&m_largeImageList, LVSIL_NORMAL);
        // Create the columns.
        LV_COLUMN lvColumn;
        lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
        lvColumn.fmt = LVCFMT_CENTER;
        lvColumn.cx = 75;
        lvColumn.iSubItem = 0;
        lvColumn.pszText = "Column 0";
        m_listView.InsertColumn(0, &lvColumn);
        lvColumn.iSubItem = 1;
        lvColumn.pszText = "Column 1";
        m_listView.InsertColumn(1, &lvColumn);
        lvColumn.iSubItem = 2;
        lvColumn.pszText = "Column 2";
        m_listView.InsertColumn(1, &lvColumn);
        // Create the items.
        LV_ITEM lvItem;
        lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
        lvItem.state = 0;      
        lvItem.stateMask = 0;  
        lvItem.iImage = 0;
        lvItem.iItem = 0;
        lvItem.iSubItem = 0;
        lvItem.pszText = "Item 0";
        m_listView.InsertItem(&lvItem);
        m_listView.SetItemText(0, 1, "Sub Item 0.1");
        m_listView.SetItemText(0, 2, "Sub Item 0.2");
        lvItem.iItem = 1;
        lvItem.iSubItem = 0;
        lvItem.pszText = "Item 1";
        m_listView.InsertItem(&lvItem);
        m_listView.SetItemText(1, 1, "Sub Item 1.1");
        m_listView.SetItemText(1, 2, "Sub Item 1.2");
        lvItem.iItem = 2;
        lvItem.iSubItem = 0;
        lvItem.pszText = "Item 2";
        m_listView.InsertItem(&lvItem);
        m_listView.SetItemText(2, 1, "Sub Item 2.1");
        m_listView.SetItemText(2, 2, "Sub Item 2.2");
        // Create the view-control buttons.
        m_smallButton.Create("Small", WS_VISIBLE | WS_CHILD | WS_BORDER,
            CRect(400, 120, 450, 140), this, 106);
        m_largeButton.Create("Large", WS_VISIBLE | WS_CHILD | WS_BORDER,
            CRect(400, 145, 450, 165), this, 107);
        m_listButton.Create("List", WS_VISIBLE | WS_CHILD | WS_BORDER,
            CRect(400, 170, 450, 190), this, 108);
        m_reportButton.Create("Report",WS_VISIBLE | WS_CHILD | WS_BORDER,
            CRect(400, 195, 450, 215), this, 109);
  3. Add the lines shown in Listing 10.3 to the end of the view class's message map, found near the top of the WIN95VIEW.CPP file. Add the lines right after the line //}}AFX_MSG_MAP, which is already in the message map.

    Listing 10.3 LST10_03.CPP New Message Map Lines

        ON_COMMAND(106, OnSmall)
        ON_COMMAND(107, OnLarge)
        ON_COMMAND(108, OnList)
        ON_COMMAND(109, OnReport)
  4. Add the functions shown in Listing 10.4 to the end of the WIN95VIEW.CPP file.

    Listing 10.4 LST10_04.CPP New functions for the Win95 Controls Applciation

    void CWin95View::OnSmall() 
    {
        SetWindowLong(m_listView.m_hWnd, GWL_STYLE,
            WS_VISIBLE | WS_CHILD | WS_BORDER |
            LVS_SMALLICON | LVS_EDITLABELS);
    }
    void CWin95View::OnLarge() 
    {
        SetWindowLong(m_listView.m_hWnd, GWL_STYLE,
            WS_VISIBLE | WS_CHILD | WS_BORDER |
            LVS_ICON | LVS_EDITLABELS);
    }
    void CWin95View::OnList() 
    {
        SetWindowLong(m_listView.m_hWnd, GWL_STYLE,
            WS_VISIBLE | WS_CHILD | WS_BORDER |
            LVS_LIST | LVS_EDITLABELS);
    }
    void CWin95View::OnReport() 
    {
        SetWindowLong(m_listView.m_hWnd, GWL_STYLE,
            WS_VISIBLE | WS_CHILD | WS_BORDER |
            LVS_REPORT | LVS_EDITLABELS);
    }
  5. Use ClassWizard to add the OnNotify() function to the view class, as shown in Figure 10.7.

    FIG. 10.7

    Use ClassWizard to add OnNotify() to the application.
  6. Click the Edit Code button, and add the lines shown in Listing 10.5 to the OnNotify() function. Place the code right after the TODO: Add your specialized code here and/or call the base class comment.

    Listing 10.5 LST10_05.CPP Lines for the OnNotify() Function

        LV_DISPINFO* lv_dispInfo = (LV_DISPINFO*) lParam;
        if (lv_dispInfo->hdr.code == LVN_BEGINLABELEDIT)
        {
            CEdit* pEdit = m_listView.GetEditControl();
            // Manipulate edit control here.
        }
        else if (lv_dispInfo->hdr.code == LVN_ENDLABELEDIT)
        {
            if ((lv_dispInfo->item.pszText != NULL) &&
                (lv_dispInfo->item.iItem != -1))
            {
                m_listView.SetItemText(lv_dispInfo->item.iItem,
                    0, lv_dispInfo->item.pszText);
            }
        }
  7. Load the view class's header file (WIN95VIEW.H), and add the lines shown in Listing 10.6 to the class's Attributes section, right after the line CImageList m_treeImageList, which you placed there previously.

    Listing 10.6 LST10_06.CPP New Data Member Declarations

        CListCtrl m_listView;
        CButton m_smallButton;
        CButton m_largeButton;
        CButton m_listButton;
        CButton m_reportButton;
  8. Add the lines shown in Listing 10.7 to the class's message map-function section, right after the line //}}AFX_MSG, which is already there.

    Listing 10.7 LST10_07.CPP Message Map Function Declarations

        afx_msg void OnSmall();
        afx_msg void OnLarge();
        afx_msg void OnList();
        afx_msg void OnReport();

You have now completed the sixth part of the Win95 Controls App. Compile and link the application by selecting the Build button in the toolbar, by selecting Developer Studio's Build, Build command, or by pressing F7. When you run the application now, you see the window shown in Figure 10.8.


FIG. 10.8

Here is Win95 Controls App with its new list view control.

To switch between the small icon, large icon, list, and report views, click the appropriate button to the right of the control. Figure 10.9 shows the application's list view control displaying small icons, whereas Figure 10.10 shows the large icons.


FIG. 10.9

Here's the list view control set to small icons.


FIG. 10.10

Here's the list view control set to large icons.

Creating the List View

In Win95 Controls App, the list view control is created in the CreateListView() local member function, which the program calls from the view class's OnCreate() function. In CreateListView(), the program first creates two image lists, as you saw in the previous section on image lists and as is shown in Listing 10.8. One image list will hold the small icon for the list view, and the other will hold the large icon. In this case, each list includes only one icon.

Listing 10.8 LST10_08.CPP Creating the List View Control's Image Lists

m_smallImageList.Create(16, 16, FALSE, 1, 0);
m_largeImageList.Create(32, 32, FALSE, 1, 0);
HICON hIcon = ::LoadIcon (AfxGetResourceHandle(),
    MAKEINTRESOURCE(IDI_ICON1));
m_smallImageList.Add(hIcon);
hIcon = ::LoadIcon (AfxGetResourceHandle(),
    MAKEINTRESOURCE(IDI_ICON2));
m_largeImageList.Add(hIcon);

Now that the program has created the image lists, it can create the list view control, by calling the class's Create() member function, like this:

m_listView.Create(WS_VISIBLE | WS_CHILD | WS_BORDER |
    LVS_REPORT | LVS_NOSORTHEADER | LVS_EDITLABELS,
CRect(160, 120, 394, 220), this, 105);

Here, Create()'s four arguments are the control's style flags, the control's size, a pointer to the control's parent window, and the control's ID. The CListCtrl class, of which m_listView is an object, defines special styles to be used with list view controls. Table 10.2 lists these special styles and their descriptions.

Table 10.2 List View Styles

Style Description
LVS_ALIGNLEFT Left aligns items in the large icon and small icon views
LVS_ALIGNTOP Top aligns items in the large icon and small icon views
LVS_AUTOARRANGE Automatically arranges items in the large icon and small icon views
LVS_EDITLABELS Enables the user to edit item labels
LVS_ICON Sets the control to the large icon view
LVS_LIST Sets the control to the list view
LVS_NOCOLUMNHEADER Shows no column headers in report view
LVS_NOITEMDATA Stores only the state of each item
LVS_NOLABELWRAP Disallows multiple-line item labels
LVS_NOSCROLL Turns off scrolling
LVS_NOSORTHEADER Turns off the button appearance of column headers
LVS_OWNERDRAWFIXED Enables owner-drawn items in report view
LVS_REPORT Sets the control to the report view
LVS_SHAREIMAGELISTS Prevents the control from destroying its image lists when the control no longer needs them
LVS_SINGLESEL Disallows multiple selection of items
LVS_SMALLICON Sets the control to the small icon view
LVS_SORTASCENDING Sorts items in ascending order
LVS_SORTDESCENDING Sorts items in descending order

Initializing the List View

Although not especially difficult, setting up a list view control is quite a bit more work than setting up a simpler control like a progress bar. As you already know, the list view control uses two image lists, one for its small icons and one for its large icons. A list view control also uses column headers for its report view, as well as list items and subitems. In short, to initialize a list view control, you must complete the following steps.

1. Create the list view control.

2. Associate the control with its image lists.

3. Create a column object for each column that will appear in the report view.

4. Create list items and subitems for each item that will be displayed in the list view control.

In the following sections, you'll learn more about how to implement the steps in the previous list.

Associating the List View with Its Image Lists

You've already created your list view control, so step one is out of the way. Now, you must associate the control with its image lists (which have also already been created). Win95 Controls App App handles the task like this:

m_listView.SetImageList(&m_smallImageList, LVSIL_SMALL);
m_listView.SetImageList(&m_largeImageList, LVSIL_NORMAL);

As you can see, the SetImageList() member function takes two parameters, which are a pointer to the image list and a flag indicating how the list is to be used. The SetImageList() function returns a pointer to the previously set image list, if any.

There are three constants defined for SetImageList()'s second argument: LVSIL_SMALL (which indicates that the list contains small icons), LVSIL_NORMAL (large icons), and LVSIL_STATE (state images).

Creating the List View's Columns

The next task is to create the columns for the control's report view. You need one main column for the item itself and one column for each subitem associated with an item. For example, in Explorer's list view, the main column holds file and folder names. Each additional column holds the subitems for each item, including the file's size, type, and modification date. To create a column, you must first declare an LV_COLUMN structure. You use this structure to pass information to and from the system. The LV_COLUMN structure is defined as shown in Listing 10.9.

Listing 10.9 LST10_09.CPP The LV_COLUMN Structure

typedef struct _LV_COLUMN
{
    UINT mask;       // Flags indicating valid fields
    int fmt;         // Column alignment
    int cx;          // Column width
    LPSTR pszText;   // Address of string buffer
    int cchTextMax;  // Size of the buffer
    int iSubItem;    // Subitem index for this column
} LV_COLUMN;

The mask member of the structure must be set so that it indicates which of the other fields in the structure are valid. The flags you can use are LVCF_FMT (meaning fmt is valid), LVCF_SUBITEM (iSubItem is valid), LVCF_TEXT (pszText is valid), and LVCF_WIDTH (cx is valid). Essentially, when you give mask its value, you're telling the system which members of the structure to use and which to ignore.

The fmt member gives the column's alignment and can be LVCFMT_CENTER, LVCFMT_LEFT, or LVCFMT_RIGHT. The alignment determines how the column's label and items are positioned in the column.

The first column, which contains the main items, is always aligned to the left. The other columns in the report view can be aligned however you like.

The cx field specifies the width of each column, whereas pszText is the address of a string buffer. When you're using the structure to create a column (you also can use this structure to obtain information about a column), this string buffer contains the column's label. The cchTextMax member gives the size of the string buffer and is valid only when retrieving information about a column.

In Win95 Controls App's CreateListView() function, the program starts initializing the LV_COLUMN structure as shown in Listing 10.10.

Listing 10.10 LST10_10.CPP Initializing the LV_COLUMN Structure

LV_COLUMN lvColumn;
lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvColumn.fmt = LVCFMT_CENTER;
lvColumn.cx = 75;

The values being set in Listing 10.10 will be the same for every column created, so they are done first and then are not changed as the columns are created.

Next, the program creates its main column by setting the appropriate structure members and then calling the CListCtrl object's InsertColumn() member function:

lvColumn.iSubItem = 0;
lvColumn.pszText = "Column 0";
m_listView.InsertColumn(0, &lvColumn);

Setting iSubItem to 0 indicates that the program is creating the first column. (Column numbers are zero-based like an array's indexes.) Finally, the program sets pszText to the column's label and calls InsertColumn() to add the column to the list view control. InsertColumn()'s two arguments are the column's index and a pointer to the LV_COLUMN structure.

The two subitem columns are created similarly, as shown in Listing 10.11.

Listing 10.11 LST10_11.CPP Creating the Subitem Columns

lvColumn.iSubItem = 1;
lvColumn.pszText = "Column 1";
m_listView.InsertColumn(1, &lvColumn);
lvColumn.iSubItem = 2;
lvColumn.pszText = "Column 2";
m_listView.InsertColumn(1, &lvColumn);

Creating the List View's Items

With the columns created, it's time to create the items that will be listed in the columns when the control is in its report view. Creating items is not unlike creating columns. As with columns, Visual C++ defines a structure that you must initialize and pass to the function that creates the items. This structure is called LV_ITEM and is defined as shown in Listing 10.12.

Listing 10.12 LST10_12.CPP The LV_ITEM Structure

typedef struct _LV_ITEM
{
    UINT   mask;         // Flags indicating valid fields
    int    iItem;        // Item index
    int    iSubItem;     // Sub-item index
    UINT   state;        // Item's current state
    UINT   stateMask;    // Valid item states.
    LPSTR  pszText;      // Address of string buffer
    int    cchTextMax;   // Size of string buffer
    int    iImage;       // Image index for this item
    LPARAM lParam;       // Additional information as a 32-bit value
} LV_ITEM;

In the LV_ITEM structure, the mask member specifies which other members of the structure are valid. The flags you can use are LVIF_IMAGE (iImage is valid), LVIF_PARAM (lParam is valid), LVIF_STATE (state is valid), and LVIF_TEXT (meaning pszText is valid).

The iItem member is the index of the item, which you can kind of think of as the row number in report view (although the position of the items can change when they're sorted). Each item has a unique index. The iSubItem member is the index of the subitem if this structure is defining a subitem. You can think of this value as the number of the column in which the item will appear. If you're defining the main item (the first column), this value should be 0.

The state and stateMask members hold the item's current state and the item's valid states, which can be one or more of LVIS_CUT (the item is selected for cut and paste), LVIS_DROPHILITED (the item is a highlighted drop target), LVIS_FOCUSED (the item has the focus), and LVIS_SELECTED (the item is selected).

The pszText member is the address of a string buffer. When using the LV_ITEM structure to create an item, the string buffer contains the item's text. When obtaining information about the item, pszText is the buffer where the information will be stored, and cchTextMax is the size of the buffer. If pszText is set to LPSTR_TEXTCALLBACK, the item uses the callback mechanism. Finally, the iImage member is the index of the item's icon in the small icon and large icon image lists. If set to I_IMAGECALLBACK, the iImage member indicates that the item uses the callback mechanism.

In Win95 Controls App's CreateListView() function, the program starts initializing the LV_ITEM structure as shown in Listing 10.13.

Listing 10.13 LST10_13.CPP Initializing the LV_ITEM Structure

LV_ITEM lvItem;
lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
lvItem.state = 0;      
lvItem.stateMask = 0;  
lvItem.iImage = 0;

The values being set in Listing 10.13 will be the same for every item created, so they are done first and then are not changed as the items are created.

Now, the program can start creating the items that will be displayed in the list view control. Listing 10.14 shows how the program creates the first item.

Listing 10.14 LST10_14.CPP Creating a List View Item

lvItem.iItem = 0;
lvItem.iSubItem = 0;
lvItem.pszText = "Item 0";
m_listView.InsertItem(&lvItem);

In Listing 10.14, the program sets the item and subitem indexes to 0 and sets the item's text to "Item 0." The program then calls the CListCtrl class's InsertItem() member function to add the item to the list view control. This function's single argument is the address of the LV_ITEM structure that contains the information about the item to be created.

At this point, the list view control has three columns and one item created. However, this single item's subitems display nothing unless you initialize them. Win95 Controls App initializes the first set of subitems like this:

m_listView.SetItemText(0, 1, "Sub Item 0.1");
m_listView.SetItemText(0, 2, "Sub Item 0.2");

The SetItemText() function takes three arguments, which are the item index, the subitem index, and the text to which to set the item or subitem.

As Listing 10.15 shows, the program then creates two more items along with the items' associated subitems.

Listing 10.15 LST10_15.CPP Creating Additional Items and Subitems

lvItem.iItem = 1;
lvItem.iSubItem = 0;
lvItem.pszText = "Item 1";
m_listView.InsertItem(&lvItem);
m_listView.SetItemText(1, 1, "Sub Item 1.1");
m_listView.SetItemText(1, 2, "Sub Item 1.2");
lvItem.iItem = 2;
lvItem.iSubItem = 0;
lvItem.pszText = "Item 2";
m_listView.InsertItem(&lvItem);
m_listView.SetItemText(2, 1, "Sub Item 2.1");
m_listView.SetItemText(2, 2, "Sub Item 2.2");

Make sure to insert a new item before setting the item text. Items that have not been inserted into the list view control will not accept text.

Manipulating the List View

You can set a list view control to four different types of views: small icon, large icon, list, and report. In Explorer, for example, the toolbar features buttons that you can click to change the view, or you can select the view from the View menu. Although Win95 Controls App doesn't have a snazzy toolbar like Explorer, it does include four buttons that you can click to change the view. Those buttons are created in the CreateListView() function as shown in Listing 10.16.

Listing 10.16 LST10_16.CPP Creating the View Buttons

m_smallButton.Create("Small", WS_VISIBLE | WS_CHILD | WS_BORDER,
   CRect(400, 120, 450, 140), this, 106);
m_largeButton.Create("Large", WS_VISIBLE | WS_CHILD | WS_BORDER,
   CRect(400, 145, 450, 165), this, 107);
m_listButton.Create("List", WS_VISIBLE | WS_CHILD | WS_BORDER,
   CRect(400, 170, 450, 190), this, 108);
m_reportButton.Create("Report", WS_VISIBLE | WS_CHILD | WS_BORDER,
   CRect(400, 195, 450, 215), this, 109);

These buttons are associated with entries in the view window's message map with the message-response functions OnSmall(), OnLarge(), OnList(), and OnReport(). In other words, when the user clicks one of these buttons, its matching function gets called, and the program changes the list view control to the requested view type. For example, when the user clicks the Small button, the function shown in Listing 10.17 changes the view to the list view.

Listing 10.17 LST10_17.CPP Changing to the List View

void CWin95View::OnSmall() 
{
    SetWindowLong(m_listView.m_hWnd, GWL_STYLE,
        WS_VISIBLE | WS_CHILD | WS_BORDER |
        LVS_SMALLICON | LVS_EDITLABELS);
}

The SetWindowLong() function sets a window's attribute. Its arguments are the window's handle, a flag that specifies the value to be changed, and the new value. In this case, the GWL_STYLE flag specifies that the window's style should be changed to the style given in the third argument. Changing the list view control's style (in the preceding code, the new view style is LVS_SMALLICON) changes the type of view it displays.

Besides changing the view, there are a number of other features you can program for your list view controls. When the user does something with the control, Windows sends a WM_NOTIFY message to the parent window. By responding to these notifications, you can give your list view control its various capabilities.

The most common notifications sent by a list view control are:

Notification Purpose
LVN_COLUMNCLICK Indicates that the user clicked a column header
LVN_BEGINLABELEDIT Indicates that the user is about to edit an item's label
LVN_ENDLABELEDIT Indicates that the user is ending the label editing process

If you haven't discovered it yet, you can edit the labels of the items in Win95 Controls App's list view items. This works by the program's capturing and handling the notification messages sent by the list view control. To capture the notification messages, just override the window's OnNotify() function.

The three parameters received by OnNotify() are the message's WPARAM and LPARAM values and a pointer to a result code. In the case of a WM_NOTIFY message coming from a list view control, the WPARAM is the list view control's ID. And, if the WM_NOTIFY message is the LVN_BEGINLABELEDIT or LVN_ENDLABELEDIT notifications, the LPARAM is a pointer to a LV_DISPINFO structure, which itself contains NMHDR and LV_ITEM structures. You use the information in these structures to manipulate the item that the user is trying to edit.

In OnNotify(), the program first casts the lParam parameter to an LV_DISPINFO structure, like this:

LV_DISPINFO* lv_dispInfo = (LV_DISPINFO*) lParam;

Next, the program checks whether the function is receiving a LVN_BEGINLABELEDIT notification, like this:

if (lv_dispInfo->hdr.code == LVN_BEGINLABELEDIT)

If the notification is LVN_BEGINLABELEDIT, your program can do whatever preediting initialization it needs to do. In Win95 Controls App App, the function shows you how to get a pointer to the edit control being used to edit the label:

CEdit* pEdit = m_listView.GetEditControl();

The program, however, doesn't actually do anything with the control.

When handling label editing, the other notification to watch out for is LVN_ENDLABELEDIT, which this particular application does like this:

else if (lv_dispInfo->hdr.code == LVN_ENDLABELEDIT)

When the program receives the LVN_ENDLABELEDIT notification, the user has finished editing the label, either by typing the new label or by canceling the editing process. If the user has canceled the process, the LV_DISPINFO structure's item.pszText member will be NULL, or the item.iItem member will be [ms]1. In this case, you need do nothing more than ignore the notification. If, however, the user completed the editing process, the program must copy the new label to the item's text, which OnNotify() does like this:

m_listView.SetItemText(lv_dispInfo->item.iItem,
    0, lv_dispInfo->item.pszText);

The CListCtrl object's SetItemText() member function requires three arguments: the item index, the subitem index, and the new text. As you can see, all of the information you need is stored in the LV_DISPINFO structure.

There are a lot of other things you can do with a list view control. You can learn more about these powerful controls in your Visual C++ online documentation. The ROWLIST sample program in the DevStudio\Vc\Samples\Mfc\General\ directory, for example, might be a good place to start.

The Tree View Control

In the preceding section, you learned how to use the list view control to organize the display of many items in a window. The list view control enables you to display items both as objects in a window and objects in a report organized into columns. Often, however, the data you'd like to organize for your application's user is best placed into a hierarchical view, where elements of the data are shown as they relate to each other. A good example of such a hierarchical display is the directory tree used by Windows to display directories on the hard disk and the files that they contain.

As is the case with other useful controls, Windows 95 includes the tree view control as one of its common controls. MFC provides access to this control through its CTreeCtrl class. This versatile control enables you to display data in various ways, all the while retaining the hierarchical relationship between the data objects in the view.

If you'd like to see an example of a tree view control, just open the Windows 95 Explorer (see Figure 10.11). The left side of the window shows how the tree view control organizes objects in a window. (The right side of the window contains a list view control, which you learned about in the previous section). In the figure, the tree view displays, not only the storage devices on the computer, but also the directories on those devices. The tree clearly shows the hierarchical relationship between the devices, directories, and files and enables the user to open and close branches on the tree to explore it at a different level. To add a tree view control to Win95 Controls App, complete the steps that follow Figure 10.11.


FIG. 10.11

A tree view control shows a hierarchical relationship between items.

The complete source code and executable file for this final part of the Win95 application can be found in the CHAP10\Win95 directory of this book's CD-ROM.

  1. Load the WIN95VIEW.CPP file, and add the following line to the OnDraw() member function, right after the line pDC->TextOut(160, 102, "List-view control"), which you placed there previously.

    pDC->TextOut(20, 240, "Tree View Control");

  2. Add the lines shown in Listing 10.18 to the end of the CreateTreeView() function.

    Listing 10.18 LST10_18.CPP New Lines for the CreateTreeView() Function

        // Create the Tree View control.
        m_treeView.Create(WS_VISIBLE | WS_CHILD | WS_BORDER |
            TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS |
            TVS_EDITLABELS, CRect(20, 260, 160, 360), this, 110);
        m_treeView.SetImageList(&m_treeImageList, TVSIL_NORMAL);
        // Create the root item.
        TV_ITEM tvItem;
        tvItem.mask =
            TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
        tvItem.pszText = "Root";
        tvItem.cchTextMax = 4;
        tvItem.iImage = 0;
        tvItem.iSelectedImage = 0;
        TV_INSERTSTRUCT tvInsert;
        tvInsert.hParent = TVI_ROOT;
        tvInsert.hInsertAfter = TVI_FIRST;
        tvInsert.item = tvItem;
        HTREEITEM hRoot = m_treeView.InsertItem(&tvInsert);
        // Create the first child item.
        tvItem.pszText = "Child Item 1";
        tvItem.cchTextMax = 12;
        tvItem.iImage = 1;
        tvItem.iSelectedImage = 1;
        tvInsert.hParent = hRoot;
        tvInsert.hInsertAfter = TVI_FIRST;
        tvInsert.item = tvItem;
        HTREEITEM hChildItem = m_treeView.InsertItem(&tvInsert);
        // Create a child of the first child item.
        tvItem.pszText = "Child Item 2";
        tvItem.cchTextMax = 12;
        tvItem.iImage = 2;
        tvItem.iSelectedImage = 2;
        tvInsert.hParent = hChildItem;
        tvInsert.hInsertAfter = TVI_FIRST;
        tvInsert.item = tvItem;
        m_treeView.InsertItem(&tvInsert);
        // Create another child of the root item.
        tvItem.pszText = "Child Item 3";
        tvItem.cchTextMax = 12;
        tvItem.iImage = 1;
        tvItem.iSelectedImage = 1;
        tvInsert.hParent = hRoot;
        tvInsert.hInsertAfter = TVI_LAST;
        tvInsert.item = tvItem;
        m_treeView.InsertItem(&tvInsert);
  3. Add the lines shown in Listing 10.19 near the end of the OnNotify() function, right before the line return CView::OnNotify(wParam, lParam, pResult).

    Listing 10.19 LST10_19.CPP New Lines for the OnNotify() Function

        TV_DISPINFO* tv_dispInfo = (TV_DISPINFO*) lParam;
        if (tv_dispInfo->hdr.code == TVN_BEGINLABELEDIT)
        {
            CEdit* pEdit = m_treeView.GetEditControl();
            // Manipulate edit control here.
        }
        else if (tv_dispInfo->hdr.code == TVN_ENDLABELEDIT)
        {
            if (tv_dispInfo->item.pszText != NULL)
            {
                m_treeView.SetItemText(tv_dispInfo->item.hItem,
                    tv_dispInfo->item.pszText);
            }
        }
  4. Load the view class's header file (WIN95VIEW.H), and add the line that follows to the class's Attributes section, right after the line CButton m_reportButton, which you placed there previously.

    CTreeCtrl m_treeView;

You have now completed the final part of the Win95 Controls App. Compile and link the application by selecting the Build button in the toolbar, by selecting Developer Studio's Build, Build command, or by pressing F7. When you run the application now, you see the window shown in Figure 10.12.


FIG. 10.12

Here's the Win95 Controls App with its new tree view control.

The Win95 Controls App application now contains a tree view control. You can click the tree's various nodes to expose new levels of the tree (Figure 10.13). You can even edit the labels of the items in the tree. To do this, select an item and then click it. An edit box appears into which you can type the new label.


FIG. 10.13

By clicking nodes in the tree, you can expose the tree's child nodes.

Creating the Tree View

In the Win95 Controls App application, the tree view control is created in the CreateTreeView() local member function, which the program calls from the view class's OnCreate() function. In CreateTreeView(), the program first creates the image list that holds the icons used with each item in the view. Listing 10.20 shows how the program creates the image list.

Listing 10.20 LST10_20.CPP Creating the Tree View Control's Image List

m_treeImageList.Create(13, 13, FALSE, 3, 0);
HICON hIcon = ::LoadIcon(AfxGetResourceHandle(),
   MAKEINTRESOURCE(IDI_ICON3));
m_treeImageList.Add(hIcon);
hIcon = ::LoadIcon(AfxGetResourceHandle(),
   MAKEINTRESOURCE(IDI_ICON4));
m_treeImageList.Add(hIcon);
hIcon = ::LoadIcon(AfxGetResourceHandle(),
   MAKEINTRESOURCE(IDI_ICON5));
m_treeImageList.Add(hIcon);

Now that the program has created the image list, it can create the tree view control, by calling the CTreeCtrl class's Create() member function:

m_treeView.Create(WS_VISIBLE | WS_CHILD | WS_BORDER |
    TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS |
    TVS_EDITLABELS, CRect(20, 260, 160, 360), this, 110);

Here, Create()'s four arguments are the control's style flags, the control's size, a pointer to the control's parent window, and the control's ID. The CTreeCtrl class, of which m_treeView is an object, defines special styles to be used with list view controls. Table 10.3 lists these special styles.

Table 10.3 Tree View Control Styles

Style Description
TVS_DISABLEDRAGDROP Disables drag and drop operations
TVS_EDITLABELS Enables user to edit labels
TVS_HASBUTTONS Gives each parent item a button.
TVS_HASLINES Adds lines between items in the tree
TVS_LINESATROOT Adds a line between the root and child items
TVS_SHOWSELALWAYS Forces a selected item to stay selected when losing focus

Initializing the Tree View

Like the list view control, a tree view control requires some hefty setup work. As you already know, the tree view control can use an image list for item icons. A tree view control also must contain item objects that your program creates and adds to the control. To initialize a tree view control, you must complete the following steps.

1. Create the tree view control.

2. Associate the control with its image list (optional).

3. Create the root and child items that will be displayed in the control.

Win95 Controls App associates the tree view control with its image list like this:

m_treeView.SetImageList(&m_treeImageList, TVSIL_NORMAL);

The creation of the tree view controls root and child items is covered in the following section.

Creating the Tree View's Items

Creating items for a tree view control is much like doing the same thing for a list view control. As with the list view, Visual C++ defines a structure that you must initialize and pass to the function that creates the items. This structure is called TV_ITEM and is defined as shown in Listing 10.21.

Listing 10.21 LST10_21.CPP The TV_ITEM Structure

typedef struct _TV_ITEM
{
    UINT       mask; 
    HTREEITEM  hItem; 
    UINT       state; 
    UINT       stateMask; 
    LPSTR      pszText; 
    int        cchTextMax; 
    int        iImage; 
    int        iSelectedImage; 
    int        cChildren; 
    LPARAM     lParam; 
} TV_ITEM;

In the TV_ITEM structure, the mask member specifies which other members of the structure are valid. The flags you can use are:

Flags Other Members
TVIF_CHILDREN cChildren is valid
TVIF_HANDLE hItem is valid
TVIF_IMAGE iImage is valid
TVIF_PARAM lParam is valid
TVIF_SELECTEDIMAGE iSelectedImage is valid
TVIF_STATE state and stateMask are valid
TVIF_TEXT pszText and cchTextMax are valid

The hItem member is the handle of the item, whereas the state and stateMask members hold the item's current state and the item's valid states, which can be one or more of

Please check your Visual C++ online documentation for the meanings of these flags.

The pszText member is the address of a string buffer. When you're using the LV_ITEM structure to create an item, the string buffer contains the item's text. When you're obtaining information about the item, pszText is the buffer where the information will be stored, and cchTextMax is the size of the buffer. If pszText is set to LPSTR_TEXTCALLBACK, the item uses the callback mechanism. Finally, the iImage member is the index of the item's icon in the image list. If set to I_IMAGECALLBACK, the iImage member indicates that the item uses the callback mechanism.

The iSelectedImage member is the index of the icon in the image list that represents the item when the item is selected. As with iImage, if this member is set to I_IMAGECALLBACK, the iSelectedImage member indicates that the item uses the callback mechanism. Finally, cChildren specifies whether or not there are child items associated with the item.

Besides the TV_ITEM structure, you must initialize a TV_INSERTSTRUCT structure that holds information about how to insert the new structure into the tree view control. That structure is declared as shown in Listing 10.22.

Listing 10.22 LST10_22.CPP The TV_INSERTSTRUCT Structure.

typedef struct _TV_INSERTSTRUCT
{
    HTREEITEM hParent; 
    HTREEITEM hInsertAfter; 
    TV_ITEM   item; 
} TV_INSERTSTRUCT;

In this structure, hParent is the handle to the parent tree view item. A value of NULL or TVI_ROOT specifies that the item should be placed at the root of the tree. The hInsertAfter member specifies the handle of the item after which this new item should be inserted. It can also be one of the flags TVI_FIRST (beginning of the list), TVI_LAST (end of the list), or TVI_SORT (alphabetical order). Finally, the item member is the TV_ITEM structure containing information about the item to be inserted into the tree.

In Win95 Controls App's CreateTreeView() function, the program initializes the TV_ITEM structure for the root item (the first item in the tree) as shown in Listing 10.23.

Listing 10.23 LST10_23.CPP Creating the Root Item

TV_ITEM tvItem;
tvItem.mask =
   TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
tvItem.pszText = "Root";
tvItem.cchTextMax = 4;
tvItem.iImage = 0;
tvItem.iSelectedImage = 0;
TV_INSERTSTRUCT tvInsert;
tvInsert.hParent = TVI_ROOT;
tvInsert.hInsertAfter = TVI_FIRST;
tvInsert.item = tvItem;
HTREEITEM hRoot = m_treeView.InsertItem(&tvInsert);

As you can see, the CTreeCtrl member function InsertItem() actually inserts the item into the tree view control. Its single argument is the address of the TV_INSERTSTRUCT structure.

The program inserts the remaining items into the tree view control as shown in Listing 10.24.

Listing 10.24 LST10_24.CPP Inserting Child Items into the Tree View Control

// Create the first child item.
tvItem.pszText = "Child Item 1";
tvItem.cchTextMax = 12;
tvItem.iImage = 1;
tvItem.iSelectedImage = 1;
tvInsert.hParent = hRoot;
tvInsert.hInsertAfter = TVI_FIRST;
tvInsert.item = tvItem;
HTREEITEM hChildItem = m_treeView.InsertItem(&tvInsert);
// Create a child of the first child item.
tvItem.pszText = "Child Item 2";
tvItem.cchTextMax = 12;
tvItem.iImage = 2;
tvItem.iSelectedImage = 2;
tvInsert.hParent = hChildItem;
tvInsert.hInsertAfter = TVI_FIRST;
tvInsert.item = tvItem;
m_treeView.InsertItem(&tvInsert);
// Create another child of the root item.
tvItem.pszText = "Child Item 3";
tvItem.cchTextMax = 12;
tvItem.iImage = 1;
tvItem.iSelectedImage = 1;
tvInsert.hParent = hRoot;
tvInsert.hInsertAfter = TVI_LAST;
tvInsert.item = tvItem;
m_treeView.InsertItem(&tvInsert);

Manipulating the Tree View

Just as with the list view control, you can edit the labels of the items in Win95 Controls App's tree view items. Also like the list view control, this process works by the program's capturing and handling (in OnNotify()) the notification messages sent by the tree view control. In the case of a WM_NOTIFY message coming from a tree view control, the WPARAM is the list view control's ID. And, if the WM_NOTIFY message is the TVN_BEGINLABELEDIT or TVN_ENDLABELEDIT notifications, the LPARAM is a pointer to a TV_DISPINFO structure, which itself contains NMHDR and TV_ITEM structures. You use the information in these structures to manipulate the item the user is trying to edit. As you can see in Listing 10.25, OnNotify() handles the tree view notifications almost exactly the same way as the list view notifications. The only difference is the names of the structures used.

Listing 10.25 LST10_25.CPP Handling Tree View Notifications

TV_DISPINFO* tv_dispInfo = (TV_DISPINFO*) lParam;
if (tv_dispInfo->hdr.code == TVN_BEGINLABELEDIT)
{
    CEdit* pEdit = m_treeView.GetEditControl();
    // Manipulate edit control here.
}
else if (tv_dispInfo->hdr.code == TVN_ENDLABELEDIT)
{
    if (tv_dispInfo->item.pszText != NULL)
    {
        m_treeView.SetItemText(tv_dispInfo->item.hItem,
        tv_dispInfo->item.pszText);
    }
}

The tree view control sends a number of different notification messages, including

Now that you know about image list, list , and tree view controls, you're ready to move on to toolbars and status bars, which just happen to be the subject of the next chapter.