Chapter 18

Using the MFC Collection Classes


MFC includes a lot more than classes for programming Windows' graphical user interface. It also features many collection classes for handling such things as lists, arrays, and maps. By using these classes, you gain extra power over data in your programs, and you simplify many operations involved in using complex data structures such as lists.

For example, because MFC's array classes can change their size dynamically, you are relieved of creating oversized arrays in an attempt to ensure that the arrays are large enough for the application. In this way, you save memory. The other collection classes provide many similar conveniences. In this chapter, you take a close look at MFC's collection classes.

The Array Classes

MFC's array classes enable you to create and manipulate one-dimensional array objects that can hold virtually any type of data. These array objects work much like the standard arrays that you're accustomed to using in your programs, except that MFC can enlarge or shrink an array object dynamically at runtime. This means that you don't have to be concerned with dimensioning your array perfectly when it's declared. Because MFC's arrays can grow dynamically, you can forget about the memory wastage that often occurs with conventional arrays, which must be dimensioned to hold the maximum number of elements that might be needed in the program, whether or not you actually use every element.

The array classes include CByteArray, CDWordArray, CObArray, CPtrArray, CUIntArray, CWordArray, and CStringArray. As you can tell from the class names, each class is designed to hold a specific type of data. For example, the CUIntArray, which is used in this section's examples, is an array class that can hold unsigned integers. The CPtrArray class, on the other hand, represents an array of pointers to void, and the CObArray class represents an array of objects. The array classes are all almost identical, differing only in the type of data that they store. As a result, once you've learned to use one of the array classes, you've learned to use them all. Table 18.1 lists the member functions of the array classes and their descriptions.

Table 18.1 Member Functions of the Array Classes

Function Description
Add() Appends a value to the end of the array, increasing the size of the array as needed.
ElementAt() Gets a reference to an array element's pointer.
FreeExtra() Releases unused array memory.
GetAt() Gets the value at the specified array index.
GetSize() Gets the number of elements in the array.
GetUpperBound() Gets the array's upper bound, which is the highest valid index at which a value can be stored.
InsertAt() Inserts a value at the specified index, shifting existing elements upward as necessary to accommodate the insert.
RemoveAll() Removes all of the array's elements.
RemoveAt() Removes the value at the specified index.
SetAt() Places a value at the specified index. Because this function will not increase the size of the array, the index must be currently valid.
SetAtGrow() Places a value at the specified index, increasing the size of the array as needed.
SetSize() Sets the array's size, which is the number of elements that the array can hold. The array still can grow dynamically beyond this size.

Introducing the Array Demo Application

To illustrate how the array classes work, this chapter includes the Array Demo application, which you can find in the Chap18\array folder of this book's CD-ROM. When you run the program, you see the window shown in Figure 18.1. The window displays the current contents of the array. Because the application's array object (which is an instance of CUIntArray) starts off with ten elements, the values for these elements (indexed as 0 through 9) are displayed on the screen. The application enables you to change, add, or delete elements in the array and see the results.


FIG. 18.1

The Array Demo application lets you experiment with MFC's array classes.

You can add an element to the array in several ways. To see these choices, left-click in the application's window. The dialog box shown in Figure 18.2 appears. Type an array index in the Index box and the new value in the Value box. Then select whether you want to set, insert, or add the element. When you choose Set, the element that you specify in the Index field gets changed to the value in the Value field. The insert operation creates a new array element at the location specified by the index, pushing succeeding elements forward. Finally, the Add operation just tacks the new element onto the end of the array. In this case, the program ignores the Index field of the dialog box.


FIG. 18.2

The Add to Array dialog box lets you add elements to the array.

Suppose, for example, that you enter 3 into the dialog box's Index field and 15 into the Value field, leaving the radio buttons set to Set. Figure 18.3 shows the result, where the program has placed the value 15 into element 3 of the array, overwriting the value that was there previously. Now say that you type 5 into Index, 25 into Value, and click the Insert radio button.


FIG. 18.3

The value of 15 has been placed into array element 3.

In Figure 18.4, you can see that the program stuffs a new element 5 into the array, shoving the other elements forward. The Add radio button tells the program to add a new element to the end of the array.


FIG. 18.4

The screen now shows the new array element 5, giving 11 elements in all.

An interesting thing to try something that really shows how dynamic MFC's arrays are is to set an array element beyond the end of the array. For example, given the program's state shown in Figure 18.4, if you type 20 in Index and 45 in Value and then choose the Set radio button, you get the results shown in Figure 18.5. Because there was no element 20, the array class created the new elements that it needed to get to 20. Try that with an old-fashioned array!


FIG. 18.5

The array class has added the elements needed to set element 20.

Besides adding new elements to the array, you can also delete elements in one of two ways. To do this, first right-click in the window. When you do, you see the dialog box shown in Figure 18.6. If you type an index into the Remove field and then click OK, the program deletes the selected element from the array. This is the opposite of the effect of the Insert command, because the Remove command shortens the array rather than lengthens it. If you want, you can select the Remove All option in the dialog box. Then the program deletes all elements from the array, leaving it empty.


FIG. 18.6

The Remove from Array dialog box lets you delete elements from the array.

Declaring and Initializing the Array

Now you'd probably like to see how all this array trickery works. It's really pretty simple. First, the program declares the array object as a data member of the view class, like this:

CUIntArray array;

Then, in the view class's constructor, the program initializes the array to ten elements:

array.SetSize(10, 5);

The SetSize() function takes as parameters the number of elements to give the array initially and the number of elements by which the array should grow whenever it needs to. You don't need to call SetSize()to use the array class. However, if you fail to do so, MFC adds elements to the array one at a time, as needed, which is a slow process (although, unless you're doing some heavy processing, you're not likely to notice any difference in speed). By giving an initial array size and the amount by which to grow, you can create much more efficient array-handling code.

Adding Elements to the Array

After setting the array size, the program waits for the user to click the left or right mouse buttons in the window. When the user does, the program springs into action, displaying the appropriate dialog box and processing the values entered into the dialog box. Listing 18.1 shows the Array Demo application's OnLButtonDown() function, which handles the left mouse button clicks.

Listing 18.1 LST18_01.cpp The OnLButtonDown() Function

void CArrayView::OnLButtonDown(UINT nFlags, CPoint point) 
{
    // TODO: Add your message handler code here and/or call default
    
    ArrayDlg dialog(this);
    dialog.m_index = 0;
    dialog.m_value = 0;
    dialog.m_radio = 0;
    int result = dialog.DoModal();
    if (result == IDOK)
    {
        if (dialog.m_radio == 0)
            array.SetAtGrow(dialog.m_index, dialog.m_value);
        else if (dialog.m_radio == 1)
            array.InsertAt(dialog.m_index, dialog.m_value, 1);
        else
            array.Add(dialog.m_value);
        Invalidate();
    }
    CView::OnLButtonDown(nFlags, point);
}

If the user exits the dialog box by clicking the OK button, the OnLButtonDown() function checks the value of the dialog box's m_radio data member. A value of 0 means that the first radio button (Set) is set; 1 means that the second button (Insert) is set; and 2 means that the third button (Add) is set.

If the user wants to set an array element, the program calls SetAtGrow(), giving the array index and the new value as arguments. Unlike the regular SetAt() function, which you can use only with a currently valid index number, SetAtGrow() will enlarge the array as necessary to set the specified array element.

When the user has selected the Insert radio button, the program calls the InsertAt() function, giving the array index and new value as arguments. This causes MFC to create a new array element at the index specified, shoving the other array elements forward. Finally, when the user has selected the Add option, the program calls the Add() function, which adds a new element to the end of the array. This function's single argument is the new value to place in the added element. The call to Invalidate() forces the window to redraw the data display with the new information.

Reading Through the Array

So that you can see what's happening as you add, change, and delete array elements, the Array Demo application's OnDraw() function reads through the array, displaying the values that it finds in each element. The code for this function is shown in Listing 18.2.

Listing 18.2 LST18_02.cpp Array Demo's OnDraw() Function

void CArrayView::OnDraw(CDC* pDC)
{
    CArrayDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    // TODO: add draw code for native data here
    
    // Get the current font's height.
    TEXTMETRIC textMetric;
    pDC->GetTextMetrics(&textMetric);
    int fontHeight = textMetric.tmHeight;
    // Get the size of the array.
    int count = array.GetSize();
    int displayPos = 10;
    // Display the array data.
    for (int x=0; x<count; ++x)
    {
        UINT value = array.GetAt(x);
        char s[81];
        wsprintf(s, "Element %d contains the value %u.", x, value);
        pDC->TextOut(10, displayPos, s);
        displayPos += fontHeight;
    }
}

Here, the program first gets the height of the current font so that it can properly space the lines of text that it displays in the window. It then gets the number of elements in the array by calling the array object's GetSize() function. Finally, the program uses the element count to control a for loop, which calls the array object's GetAt() member function to get the value of the currently indexed array element. The program converts this value to a string for display purposes.

Removing Elements from the Array

Because it is a right button click in the window that brings up the Remove from Array dialog box, it is the program's OnRButtonDown() function that handles the element-deletion duties. That function is shown in Listing 18.3.

Listing 18.3 LST18_03.cpp The OnRButtonDown() Function

void CArrayView::OnRButtonDown(UINT nFlags, CPoint point) 
{
    // TODO: Add your message handler code here and/or call default
    
    ArrayDlg2 dialog(this);
    dialog.m_remove = 0;
    dialog.m_removeAll = FALSE;
    int result = dialog.DoModal();
    if (result == IDOK)
    {
        if (dialog.m_removeAll)
            array.RemoveAll();
        else
            array.RemoveAt(dialog.m_remove);
        Invalidate();
    }
    
    CView::OnRButtonDown(nFlags, point);
}

In this function, after displaying the dialog box, the program checks the value of the dialog box's m_removeAll data member. A value of TRUE means that the user has checked this option and wants to delete all elements from the array. In this case, the program calls the array object's RemoveAll() member function. Otherwise, the program calls RemoveAt(), whose single argument specifies the index of the element to delete. The call to Invalidate() forces the window to redraw the data display with the new information.

The List Classes

Lists are like fancy arrays. Because lists (also called linked lists) use pointers to link their elements (called nodes) rather than depend upon contiguous memory locations to order values, lists are a better data structure to use when you need to insert and delete items quickly. However, finding items in a list can be slower than finding items in an array because a list often needs to be traversed sequentially to follow the pointers from one item to the next.

When using lists, you need to know some new vocabulary. Specifically, you need to know that the head of a list is the first node in the list, and the tail of the list is the last node in the list (Figure 18.7). You'll see these two terms used often as you explore MFC's list classes.


FIG. 18.7

A linked list has a head and a tail, with the remaining nodes in between.

MFC provides three list classes that you can use to create your lists. These classes are CObList (which represents a list of objects), CPtrList (which represents a list of pointers), and CStringList (which represents a list of strings). Each of these classes has similar member functions, and the classes differ in the type of data that they can hold in their lists. Table 18.2 lists and describes the member functions of the list classes.

Table 18.2 Member Functions of the List Classes

Function Description
AddHead() Adds a node to the head of the list, making the node the new head.
AddTail() Adds a node to the tail of the list, making the node the new tail.
Find() Searches the list sequentially to find the given object pointer. Returns a POSITION value.
FindIndex() Scans the list sequentially, stopping at the node indicated by the given index. Returns a POSITION value for the node.
GetAt() Gets the node at the specified position.
GetCount() Gets the number of nodes in the list.
GetHead() Gets the list's head node.
GetHeadPosition() Gets the head node's position.
GetNext() When iterating over a list, gets the next node in the list.
GetPrev() When iterating over a list, gets the previous node in the list.
GetTail() Gets the list's tail node.
GetTailPosition() Gets the tail node's position.
InsertAfter() Inserts a new node after the specified position.
InsertBefore() Inserts a new node before the specified position.
IsEmpty() Returns TRUE if the list is empty and returns FALSE otherwise.
RemoveAll() Removes all of a list's nodes.
RemoveAt() Removes a single node from a list.
RemoveHead() Removes the list's head node.
RemoveTail() Removes the list's tail node.
SetAt() Sets the node at the specified position.

Introducing the List Demo Application

As you've no doubt guessed, now that you know a little about list classes and their member functions, you're going to get a chance to see lists in action. In the Chap18\list folder of this book's CD-ROM, you'll find the List Demo application. When you run the application, you see the window shown in Figure 18.8. The window displays the values of the single node with which the list begins. Each node in the list can hold two different values, both of which are integers.


FIG. 18.8

The List Demo application starts off with one node in its list.

Using the List Demo application, you can experiment with adding and removing nodes from a list. To add a node, left-click the application's window. You then see the dialog box shown in Figure 18.9. Enter the two values that you want the new node to hold and then click OK. When you do, the program adds the new node to the tail of the list and displays the new list in the window. For example, if you were to enter the values 55 and 65 to the dialog box, you'd see the display shown in Figure 18.10.


FIG. 18.9

A left-click in the window brings up the Add Node dialog box.


FIG. 18.10

Each node that you add to the list can hold two different values.

You can also delete nodes from the list. To do this, right-click in the window to display the Remove Node dialog box (Figure 18.11). Using this dialog box, you can choose to remove the head or tail node. If you exit the dialog box by clicking OK, the program deletes the specified node and displays the resulting list in the window.

If you try to delete nodes from an empty list, the List Demo application displays a message box warning you of your error. If the application didn't catch this possible error, the program could crash when it tries to delete a nonexistent node.


FIG. 18.11

Right-click in the window to delete a node.

Declaring and Initializing the List

Declaring a list is as easy as declaring any other data type. Just include the name of the class that you're using, followed by the name of the object. For example, the List Demo application declares its list like this:

CPtrList list;

Here, the program is declaring an object of the CPtrList class. This class holds a linked list of pointers, which means that the list can reference just about any type of information.

Although there's not much that you need to do to initialize an empty list, you do need to decide what type of information will be pointed to by the pointers in the list. That is, you need to declare exactly what a node in the list will look like. The List Demo application declares a node as shown in Listing 18.4.

Listing 18.4 LST18_04.cpp The CNode Structure

struct CNode
{
    int value1;
    int value2;
};

Here, a node is defined as a structure holding two integer values. However, you can create any type of data structure that you like for your nodes. To add a node to a list, you use the new operator to create a node structure in memory, and then you add the returned pointer to the pointer list. The List Demo application begins its list with a single node, which is created in the view class's constructor, as shown in Listing 18.5.

Listing 18.5 LST18_05.cpp Creating the First Node

CNode* pNode = new CNode;
pNode->value1 = 11;
pNode->value2 = 22;
list.AddTail(pNode);

In Listing 18.5, the program first creates a new CNode structure on the heap and then sets the node's two members. After initializing the new node, a quick call to the list's AddTail() member function adds the node to the list. Because the list was empty, adding a node to the tail of the list is the same as adding the node to the head of the list. That is, the program also could have called AddHead() to add the node. In either case, the new single node is now both the head and tail of the list.

Adding a Node to the List

Although you can insert nodes into a list at any position, the easiest way to add to a list is to add a node to the head or tail, making the node the new head or tail. In the List Demo application, you left-click in the window to bring up the Add Node dialog box, so you'll want to examine the OnLButtonDown() function, which looks like Listing 18.6.

Listing 18.6 LST18_06.cpp List Demo's OnLButtonDown() Function

void CMyListView::OnLButtonDown(UINT nFlags, CPoint point) 
{
    // TODO: Add your message handler code here and/or call default
    
    // Create and initialize the dialog box.
    CAddDlg dialog;
    dialog.m_value1 = 0;
    dialog.m_value2 = 0;
    // Display the dialog box.
    int result = dialog.DoModal();
    // If the user clicked the OK button...
    if (result == IDOK)
    {
        // Create and initialize the new node.
        CNode* pNode = new CNode;
        pNode->value1 = dialog.m_value1;
        pNode->value2 = dialog.m_value2;
        // Add the node to the list.
        list.AddTail(pNode);
        // Repaint the window.
        Invalidate();
    }
    CView::OnLButtonDown(nFlags, point);
}

In Listing 18.6, after displaying the dialog box, the program checks whether the user exited the dialog box with the OK button. If so, the user wants to add a new node to the list. In this case, the program creates and initializes the new node, just as it did previously for the first node that it added in the view class's constructor. The program adds the node in the same way, too, by calling AddTail(). If you want to modify the List Demo application, one thing that you could try is giving the user a choice between adding the node at the head or the tail of the list, instead of just at the tail.

Deleting a Node from the List

Deleting a node from a list can be easy or complicated, depending on where in the list you want to delete the node. As with adding a node, dealing with nodes other than the head or tail requires that you first locate the node that you want and then get its position in the list. You learn about node positions in the next section, which demonstrates how to iterate over a list. To keep things simple, however, the program enables you to delete nodes only from the head or tail of the list, as shown in Listing 18.7.

Listing 18.7 LST18_07.cpp The OnRButtonDown() Function

void CMyListView::OnRButtonDown(UINT nFlags, CPoint point) 
{
    // TODO: Add your message handler code here and/or call default
    
    // Create and initialize the dialog box.
    CRemoveDlg dialog;
    dialog.m_radio = 0;
    // Display the dialog box.
    int result = dialog.DoModal();
    // If the user clicked the OK button...
    if (result == IDOK)
    {
        CNode* pNode;
        // Make sure the list isn't empty.
        if (list.IsEmpty())
            MessageBox("No nodes to delete.");
        else
        {
            // Remove the specified node.
            if (dialog.m_radio == 0)
                pNode = (CNode*)list.RemoveHead();
            else
                pNode = (CNode*)list.RemoveTail();
            // Delete the node object and repaint the window.
            delete pNode;
            Invalidate();
        }
    }
    CView::OnRButtonDown(nFlags, point);
}

Here, after displaying the dialog box, the program checks whether the user exited the dialog box via the OK button. If so, the program must then check whether the user wants to delete a node from the head or tail of the list. If the Remove Head radio button were checked, the dialog box's m_radio data member would be 0. In this case, the program calls the list class's RemoveHead() member function. Otherwise, the program calls RemoveTail(). Both of these functions return a pointer to the object that was removed from the list. Before calling either of these member functions, however, notice how the program calls IsEmpty()to determine whether the list contains any nodes. You can't delete a node from an empty list!

Notice that, when removing a node from the list, the List Demo application calls delete on the pointer returned by the list. It's important to remember that, when you remove a node from a list, the node's pointer is removed from the list, but the object to which the pointer points is still in memory, where it stays until you delete it.

Iterating Over the List

Often, you'll want to iterate over (read through) a list. You might, for example, as is the case with List Demo, want to display the values in each node of the list, starting from the head of the list and working your way to the tail. The List Demo application does exactly this in its OnDraw() function, as shown in Listing 18.8.

Listing 18.8 LST18_08.cpp The List Demo Application's OnDraw() Function

void CMyListView::OnDraw(CDC* pDC)
{
    CListDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    // TODO: add draw code for native data here
    // Get the current font's height.
    TEXTMETRIC textMetric;
    pDC->GetTextMetrics(&textMetric);
    int fontHeight = textMetric.tmHeight;
    // Initialize values used in the loop.
    POSITION pos = list.GetHeadPosition();
    int displayPosition = 10;
    int index = 0;
    // Iterate over the list, displaying each node's values.
    while (pos != NULL)
    {
        CNode* pNode = (CNode*)list.GetNext(pos);
        char s[81];
        wsprintf(s, "Node %d contains %d and %d.",
            index, pNode->value1, pNode->value2);
        pDC->TextOut(10, displayPosition, s);
        displayPosition += fontHeight;
        ++index;
    }
}

In Listing 18.8, the program gets the position of the head node by calling the GetHeadPosition() member function. The position is a value that many of the list class's member functions use to quickly locate nodes in the list. You must have this starting-position value to iterate over the list.

In the while loop, the iteration actually takes place. The program calls the list object's GetNext() member function, which requires as its single argument the position of the node to retrieve. The function returns a pointer to the node and sets the position to the next node in the list. When the position is NULL, the program has reached the end of the list. In Listing 18.8, this NULL value is the condition that's used to terminate the while loop.

Cleaning Up the List

There's one other time when you need to iterate over a list. That's when the program is about to terminate and you need to delete all of the objects pointed to by the pointers in the list. The List Demo application performs this task in the view class's destructor, as shown in Listing 18.9.

Listing 18.9 LST18_09.cpp Deleting the List's Objects

CMyListView::~CMyListView()
{
    // Iterate over the list, deleting each node.
    while (!list.IsEmpty())
    {
        CNode* pNode = (CNode*)list.RemoveHead();
        delete pNode;
    }
}

The destructor in Listing 18.9 iterates over the list in a while loop until the IsEmpty() member function returns TRUE. Inside the loop, the program removes the head node from the list (which makes the next node in the list the new head) and deletes the node from memory. When the list is empty, all the nodes that the program allocated have been deleted.

Although the List program uses RemoveHead() to remove nodes from the list, the RemoveTail() member function, which removes the last node, is a bit more efficient. In large lists, you'd probably want to use RemoveTail() to empty the list.

Don't forget that you're responsible for deleting every node that you create with the new operator. If you fail to delete nodes, you could leave memory allocated after your program terminates. This isn't a major problem under Windows 95 because the system cleans up memory after an application exits. However, it's always good programming practice to delete any objects that you allocate in memory.

The Map Classes

You can use MFC's mapped collection classes for creating lookup tables. For example, you might want to convert digits into the words that represent the numbers. That is, you might want to use the digit 1 as a key to find the word one. A mapped collection is perfect for this sort of task. Thanks to the many MFC map classes, you can use various types of data for keys and values.

The MFC map classes are CMapPtrToPtr, CMapPtrToWord, CMapStringToOb, CMapStringToPtr, CMapStringToString, CMapWordToOb, and CMapWordToPtr. The first data type in the name is the key, and the second is the value type. So, for example, CMapStringToOb uses strings as keys and objects as values, whereas CMapStringToString which this section uses in its examples uses strings as both keys and values. All of the map classes are similar and so have similar member functions, which are listed and described in Table 18.3.

Table 18.3 Functions of the Map Classes

Function Description
GetCount() Gets the number of map elements
GetNextAssoc() When iterating over the map, gets the next element
GetStartPosition() Gets the first element's position
IsEmpty() Returns TRUE if the map is empty and returns FALSE otherwise
Lookup() Finds the value associated with a key
RemoveAll() Removes all of the map's elements
RemoveKey() Removes an element from the map
SetAt() Adds a map element or replaces an element with a matching key

Introducing the Map Demo Application

This section's example program, Map Demo, displays the contents of a map and enables you to retrieve values from the map by giving the program the appropriate key. You can find the program in the Chap18\map folder of this book's CD-ROM. When you run the program, you see the window shown in Figure 18.12.


FIG. 18.12

The Map Demo application displays the contents of a map object.

The window displays the contents of the application's map object, in which digits are used as keys to access the words that represent the numbers. To retrieve a value from the map, click in the window. You then see the dialog box shown in Figure 18.13. Type the digit that you want to use for a key and then click OK. The program finds the matching value in the map and displays it in another message box. For example, if you type 8 as the key, you see the message box shown in Figure 18.14. If the key doesn't exist, the program's message box tells you so.


FIG. 18.13

The Get Map Value dialog box enables you to match a key with the key's value in the map.


FIG. 18.14

This message box displays the requested map value.

Creating and Initializing the Map

The Map Demo application starts off with a ten-element map. The map object is declared as a data member of the view class, like this:

CMapStringToString map;

As you can see from the declaration, this application's map is an object of the CMapStringToString class, which means that the map uses strings as keys and strings as values.

Declaring the map object doesn't, of course, fill it with values. You have to do that on your own, which the Map Demo application does in its view class's constructor, as shown in Listing 18.10.

Listing 18.10 LST18_10.cpp Initializing the Map Object

map.SetAt("1", "One");
map.SetAt("2", "Two");
map.SetAt("3", "Three");
map.SetAt("4", "Four");
map.SetAt("5", "Five");
map.SetAt("6", "Six");
map.SetAt("7", "Seven");
map.SetAt("8", "Eight");
map.SetAt("9", "Nine");
map.SetAt("10", "Ten");

The SetAt() function takes as parameters the key and the value to associate with the key in the map. If the key already exists, the function replaces the value associated with the key with the new value given as the second argument.

Retrieving a Value from the Map

When you click in Map Demo's window, the Get Map Value dialog box appears, so you must suspect that the view class's OnLButtonDown() member function comes into play somewhere. And you'd be correct. Listing 18.11 shows this function.

Listing 18.11 LST18_11.cpp The Map Demo Application's OnLButtonDown() Function

void CMapView::OnLButtonDown(UINT nFlags, CPoint point) 
{
    // TODO: Add your message handler code here and/or call default
    // Initialize the dialog box.    
    CGetDlg dialog(this);
    dialog.m_key = "";
    // Display the dialog box.
    int result = dialog.DoModal();
    // If the user exits with the OK button...
    if (result == IDOK)
    {
        // Look for the requested value.
        CString value;
        BOOL found = map.Lookup(dialog.m_key, value);
        if (found)
            MessageBox(value);
        else
            MessageBox("No matching value.");
    }
    CView::OnLButtonDown(nFlags, point);
}

In OnLButtonDown(), the program displays the dialog box in the usual way, checking to see whether the user exited the dialog box by clicking the OK button. If the user did, the program calls the map object's Lookup() member function, using the key that the user entered into the dialog box as the first argument. The second argument is a reference to the string into which the function can store the value that it retrieves from the map. If the key can't be found, the Lookup() function returns FALSE; otherwise, it returns TRUE. The program uses this return value to determine whether it should display the string value retrieved from the map or a message box indicating an error.

Iterating Over the Map

To display the keys and values used in the map, the program must iterate over the map, moving from one entry to the next, retrieving and displaying the information for each map element. As with the array and list examples, the Map Demo application accomplishes this in its OnDraw() function, which is shown in Listing 18.12.

Listing 18.12 LST18_12.cpp The Map Demo Application's OnDraw() Function

void CMapView::OnDraw(CDC* pDC)
{
    CMapDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    // TODO: add draw code for native data here
    TEXTMETRIC textMetric;
    pDC->GetTextMetrics(&textMetric);
    int fontHeight = textMetric.tmHeight;
    int displayPosition = 10;
    POSITION pos = map.GetStartPosition();
    CString key;
    CString value;
    while (pos != NULL)
    {
        map.GetNextAssoc(pos, key, value);
        CString str = "Key '" + key + 
            "' is associated with the value '" +
            value + "'";
        pDC->TextOut(10, displayPosition, str);
        displayPosition += fontHeight;
    }
}

Much of this OnDraw() function is similar to other versions that you've seen in this chapter. The map iteration, however, begins when the program calls the map object's GetStartPosition() member function, which returns a position value for the first entry in the map (not necessarily the first entry that you added to the map). Inside a while loop, the program calls the map object's GetNextAssoc() member function, giving the position returned from GetStartPosition() as the single argument. GetNextAssoc() retrieves the key and value at the given position and then updates the position to the next element in the map. When the position value becomes NULL, the program has reached the end of the map.

Collection Class Templates

MFC includes class templates that you can use to create your own special types of collection classes. Although the subject of templates can be complex, using the collection class templates is easy enough. For example, suppose that you want to create an array class that can hold structures of the type shown in Listing 18.13.

Listing 18.13 LST18_13.cpp A Sample Structure

struct MyValues
{
    int value1;
    int value2;
    int value3;
};

The first step is to use the template to create your class, like this:

CArray<MyValues, MyValues&> myValueArray;

Here, CArray is the template that you use for creating your own array classes. The template's two arguments are the type of data to store in the array and the type of data that the new array class's member functions should use as arguments where appropriate. In this case, the type of data to store in the array is structures of the MyValues type. The second argument specifies that class member functions should expect references to MyValues structures as arguments where needed.

To build your array, you first set the array's initial size:

myValueArray.SetSize(10, 5);

Then you can start adding elements to the array, like this:

MyValues myValues;
myValueArray.Add(myValues);

As you can see, after you have created your array class from the template, you use the array just as you do any of MFC's array classes, as described earlier in this chapter. Other collection class templates that you can use are CList and CMap.

MFC's collection classes provide you with a way to better organize and manipulate data in your programs. Moreover, thanks to the collection class templates, you can easily create collections of any type that you need for your programs. You've probably used normal C++ arrays and maybe even linked lists in your programs, but MFC's array and list classes boot those data structures into the 90s, giving them more power than ever.