Appendix E

Useful Classes


MFC includes a lot more than classes for programming the Windows graphical user interface. It also features many utility classes for handling such things as lists, arrays, times and dates, and mapped collections. By using these classes, you can gain extra power over data in your programs, as well as 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 other similar conveniences.

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 used 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 just right when it's declared. Because MFC's arrays can grow dynamically, you can forget about the memory waste that often occurs with conventional arrays, which must be dimensioned to hold the maximum number of elements that may 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 will be 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. Once you've learned to use one of the array classes, you've learned to use them all. Table E.1 lists the member functions of the array classes and their descriptions.

Table E.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 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 initial size and the amount by which it grows when needed. By allocating more than one elementís worth of space at a time, you save time but could waste memory.

Introducing the Array Application

To illustrate how the array classes work, this chapter includes the Array application. After a brief tour of the application, youíll see how to build it yourself in the sections that follow. When you run the program, you see the window shown in Figure E.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.

This application is on the CD in the RefE folder.

Fig. E.1 The Array Demo application enables you to experiment with MFC's array classes.

You can add an element to the array in several ways. To see these choices, click in the application's window. The dialog box shown in Figure E.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 value of the element 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. E.2 The Add to Array dialog box enables you to 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 Set radio button selected. Figure E.3 shows the result: the program has placed the value 15 into element 3 of the array, overwriting the value that was there previously. Now type 2 into Index, 25 into Value, select the Insert radio button, and click OK. Figure E.4 shows the result: the program stuffs a new element into the array, shoving the other elements forward.

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

Fig. E.4 The screen now shows the new array element, 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 E.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 E.5. Because there was no element 20, the array class created the new elements that it needed to get to 20. You donít need to keep track of how many elements are in the array. Try that with an old-fashioned array.

Fig. E.5 The array class has added the elements needed in order 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 E.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 Insert command, because the Remove command shortens the array rather than lengthening 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. E.6 The Remove from Array dialog box enables you to 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() in order 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.) If your application doesnít add elements to its arrays often, and you are concerned about memory consumption, donít use SetSize(). If your application repeatedly adds elements, and you have lots of memory available, using SetSize() to arrange for many elements to be allocated at once will reduce the number of allocations performed, giving you a faster application.

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 E.1 shows the Array Demo application's OnLButtonDown() function, which handles the left mouse button clicks.

Chapter 4, ìMessages and Commands,î shows you how to catch mouse clicks and arrange for a message-handler like OnLButtonDown() to be called.

Listing E.1óCArrayView::OnLButtonDown()

void 
CArrayView::OnLButtonDown(UINT nFlags, CPoint point) 
{
    ArrayAddDlg 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);
}

This code starts by creating a dialog object and initializing it, as discussed in Chapter 2, ìDialog Boxes and Controls.î 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 selected, 1 means that the second button (Insert) is selected, and 2 means that the third button (Add) is selected.

Chapter 2, ìDialog Boxes and Controls,î discusses displaying dialog boxes and getting values from them.

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 in order to set the specified array element. Thatís how the extra array elements were added when you chose to set element 20.

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 E.2.

Chapter 6, ìDrawing on the Screen,î shows you how to write an OnDraw() function and how it gets called.

Listing E.2óCArrayView::OnDraw()

void CArrayView::OnDraw(CDC* pDC)
{
    CArrayDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    // 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 E.3.

Listing E.3óCArrayView::OnRButtonDown()

void CArrayView::OnRButtonDown(UINT nFlags, CPoint point) 
{
    ArrayRemoveDlg 

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. The MFC list classes implement linked lists, which use pointers to link their elements (called nodes) rather than depending upon contiguous memory locations to order values, lists are a better data structure to use when you need to be able 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 in order 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 E.7.) Each node knows how to reach the next node, the one after it in the list. You'll see these terms used often as you explore MFC's list classes.

Fig. E.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 E.2 lists and describes the member functions of the list classes.

Table E.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()
Gets the next node in the list when iterating over a list.
GetPrev()
Gets the previous node in the list when iterating over a 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 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 List Application. When you run the application, you see the window shown in Figure E.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.

This application is on the CD in the RefE folder.

Fig. E.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 in the application's window. You then see the dialog box shown in Figure E.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 enter the values 55 and 65 into the dialog box, yousee the display shown in Figure E.10.

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

Fig. E.10 Each node 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 E.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. E.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 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 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 E.4.

Listing E.4 ó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 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 E.5.

Listing E.5óCMyListView constructor

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

In Listing E.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 could have also 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 E.6.

Listing E.6óCMyListView::OnLButtonDown()

void CMyListView::OnLButtonDown(UINT nFlags, CPoint point) 
{
    // Create and initialize the 
dialog box.
    AddNodeDlg 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 E.6, after displaying the dialog box, the program checks whether the user exited the dialog 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 the AddTail(). If you want to modify the List Demo application, one thing 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 more 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'll learn about node positions in the next section, which demonstrates how to iterate over a list. To keep things simple, however, this program enables you to delete nodes only from the head or tail of the list, as shown in Listing E.7.

Listing E.7óCMyListView::OnRButtonDown()

void 
CMyListView::OnRButtonDown(UINT nFlags, CPoint point) 
{
    // Create and initialize the dialog box.
    RemoveNodeDlg 
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 was checked, the dialog box's m_radio data member will 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() in order 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, 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 application does exactly this in its OnDraw() function, as shown in Listing E.8.

Listing E.8óCMyListView::OnDraw()

void CMyListView::OnDraw(CDC* pDC)
{
    CListDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
// 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 E.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 in order 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 E.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 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 E.9.

Listing E.9óCMyListView destructor

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

The destructor in Listing E.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.

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 cause a memory leak. In a small program like this, a few wasted bytes donít matter, but in a long-running program adding and deleting hundreds or thousands of list nodes, you could create serious errors in your program. It's always good programming practice to delete any objects you allocate in memory.

Chapter 24, ìImproving your Applicationís Performance,î discusses memory management and preventing memory leaks.

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 in order 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 the map classes are similar, and so have similar member functions, which are listed and described in Table E.3.

Table E.3óFunctions of the Map Classes

Function Description
GetCount()
Gets the number of map elements.
GetNextAssoc()
Gets the next element when iterating over the map.
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 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 Application

This section's example program, Map, displays the contents of a map and enables you to retrieve values from the map by giving the program the appropriate key. When you run the program, you see the window shown in Figure E.12.

This application is on the CD in the RefE folder.

Fig. E.12 The Map 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 E.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 E.14. If the key doesn't exist, the program's message box tells you so.

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

Fig. E.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;

This 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 constructor, shown in Listing E.10.

Listing E.10óCMapView constructor

CMapView::CMapView()
{
     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's window, the Get Map Value dialog box appears, so itís probably not surprising that the view class OnLButtonDown() member function comes into play somewhere. Listing E.11 shows this function.

Listing E.11óCMapView::OnLButtonDown()

void CMapView::OnLButtonDown(UINT 
nFlags, CPoint point) 
{
// Initialize the dialog box.    
    
GetMapDlg 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 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 in order to determine whether it should display the string value retrieved from the map or a message box indicating an error.

Iterating Over the Map

In order 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 E.12.

Listing E.12óCMapView::OnDraw()

void CMapView::OnDraw(CDC* pDC)
{
    CMapDoc* 
pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    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. (For more information on templates, please refer to the section "Templates" in Chapter 26, " Exceptions, Templates, and the Latest Additions to C++.") 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 E.13.

Listing E.13ó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 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 optionally 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);

Once you create 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 you can use are CList and Cmap. This means you can take advantage of all the design work put in by the MFC team to create an array of Employee objects, or a linked list of Order objects, or a map linking names to Customer objects.

The String Class

There are few programs that don't have to deal with text strings of one sort or another. Unfortunately, C++ is infamous for its weak string-handling capabilities, while languages like BASIC and Pascal have always enjoyed superior power when it came to these ubiquitous data types. MFC's CString class addresses C++'s string problems by providing member functions that are as handy to use as those found in other languages. Table E.4 lists the commonly used member functions of the CString class.

Table E.4óCommonly Used Member Functions of the CString Class

Function Description
Compare()
Case-sensitive compare of two strings.
CompareNoCase()
Case-insensitive compare of two strings.
Empty()
Clears a string.
Find()
Locates a substring.
Format() ìPrintî variables into a CString much like the C sprintf function.
GetAt()
Gets a character at a specified position in the string.
GetBuffer()
Gets a pointer to the string's contents.
GetLength()
Gets the number of characters in the string.
IsEmpty()
Returns TRUE if the string holds no characters.
Left()
Gets the left segment of a string.
MakeLower()
Lowercases a string.
MakeReverse()
Reverses the contents of a string.
MakeUpper()
Uppercases a string.
Mid()
Gets the middle segment of a string.
Right()
Gets the right segment of a string.
SetAt()
Sets a character at a specified position in the string.
TrimLeft()
Removes leading white-space characters from a string.
TrimRight()
Removes trailing white-space characters from a string.

Besides the functions listed in the table, the CString class also defines a full set of operators for dealing with strings. Using these operators, you can do things like concatenate (join together) strings with the plus sign (+), assign values to a string object with the equal sign (=), access the string as a C-style string with the LPCTSTR operator, and more.

Creating a string object is quick and easy, like this:

CString str = "This is a test string";

Of course, there are lots of ways to construct your string object. The previous example is only one possibility. You can create an empty string object and assign characters to it later, you can create a string object from an existing string object, and you can even create a string from a repeating character.

Once you have the string object created, you can call its member functions and manipulate the string in a number of ways. For example, to convert all the characters in the string to uppercase, you'd make a function call like this:

str.MakeUpper();

To lengthen a string, use the + or += operators, like this:

CString sentence = "hello " + 
str;
sentence += " there."

Or, to compare two strings, you'd make a function call something like this:

str.Compare("Test String");

You can also compare two CString objects:

CString testStr = "Test String";
str.Compare(testStr);

Or neater still:

if (testStr == str)

If you peruse your online documentation, you'll find that most of the other CString member functions are equally easy to use.

The Time Classes

If you've ever tried to manipulate time values returned from a computer, you'll be pleased to learn about MFC's CTime and CTimeSpan classes, which represent absolute times and elapsed times, respectively. The use of these classes is pretty straightforward, so there's no sample program for this section. However, the following sections get you started with these handy classes. Before you start working with the time classes, however, look over Table E.5, which lists the member functions of the CTime class, and Table E.6, which lists the member functions of the CTimeSpan class..

Table E.5óMember Functions of the CTime Class

Function Description
Format()
Constructs a string representing the time object's time.
FormatGmt()
Constructs a string representing the time object's GMT (or UTC) time. This is the Greenwich mean time.
GetCurrentTime()
Creates a CTime object for the current time.
GetDay()
Gets the time object's day as an integer.
GetDayOfWeek()
Gets the time object's day of the week, starting with 1 for Sunday.
GetGmtTm()
Gets a time object's second, minute, hour, day, month, year, day of the week, and day of the year as a tm structure.
GetHour()
Gets the time object's hour as an integer.
GetLocalTm()
Gets a time object's local time, returning the second, minute, hour, day, month, year, day of the week, and day of the year in a tm structure.
GetMinute()
Gets the time object's minutes as an integer.
GetMonth()
Gets the time object's month as an integer.
GetSecond()
Gets the time object's second as an integer.
GetTime()
Gets the time object's time as a time_t value.
GetYear()
Gets the time object's year as an integer.

Table E.6óMember Functions of the CTimeSpan Class

Function Description
Format()
Constructs a string representing the time-span object's time.
GetDays()
Gets the time-span object's days.
GetHours()
Gets the time-span object's hours for the current day.
GetMinutes()
Gets the time-span object's minutes for the current hour.
GetSeconds()
Gets the time-span object's seconds for the current minute.
GetTotalHours()
Gets the time-span objects total hours.
GetTotalMinutes()
Gets the time-span object's total minutes.
GetTotalSeconds()
Gets the time-span object's total seconds.

Using a CTime Object

Creating a CTime object for the current time is a simple matter of calling the GetCurrentTime() function, like this:

CTime time = CTime::GetCurrentTime();

Because GetCurrentTime() is a static member function of the CTime class, you can call it without actually creating a CTime object. You do, however, have to include the class's name as part of the function call, as shown in the preceding code. As you can see, the function returns a CTime object. This object represents the current time. If you wanted to display this time, you could call upon the Format() member function, like this:

CString str = time.Format("DATE: %A, %B %d, %Y");

The Format() function takes as its single argument a format string that tells the function how to create the string representing the time. The previous example creates a string that looks something like this:

DATE: Saturday, April 20, 1996

The format string used with Format() is not unlike the format string used with functions like the old DOS favorite printf() or the Windows conversion function wsprintf(). That is, you specify the string's format by including literal characters along with control characters. The literal characters, such as the "DATE:" and the commas in the previous string example, are added to the string exactly as you type them, whereas the format codes are replaced with the appropriate values. For example, the %A in the previous code example will be replaced by the name of the day and the %B will be replaced by the name of the month. Although the format-string concept is the same as that used with printf(), the Format() function has its own set of format codes, which are listed in Table E.7.

Table E.7óFormat Codes for the Format() Function

Code Description
%a
Day name, abbreviated (such as Sat for Saturday).
%A
Day name, no abbreviation.
%b
Month name, abbreviated (such as Mar for March).
%B
Month name, no abbreviation.
%c
Localized date and time (for the U.S., that would be something like 03/17/96 12:15:34).
%d
Day of the month as a number (01-31).
%H
Hour in the 24-hour format (00-23).
%I
Hour in the normal 12-hour format (01-12).
%j
Day of the year as a number (001-366).
%m
Month as a number (01-12).
%M
Minute as a number (00-59).
%p
Localized a.m./p.m. indicator for 12-hour clock.
%S
Second as a number (00-59).
%U
Week of the year as a number (00-51, considering Sunday to be the first day of the week).
%w
Day of the week as a number (0-6, with Sunday being 0).
%W
Week of the year as a number (00-51, considering Monday to be the first day of the week).
%x
Localized date representation.
%X
Localized time representation.
%y
Year without the century prefix as a number (00-99).
%Y
Year with the century prefix as a decimal number (such as 1996).
%z
Name of time zone, abbreviated.
%Z
Name of time zone, no abbreviation.
%%
Percent sign.

Other CTime member functions like GetMinute(), GetYear(), and GetMonth() are obvious in their usage. However, you may like an example of using a function like GetLocalTm(), which is what the following shows:

struct tm* timeStruct;
timeStruct = time.GetLocalTm();

The first line of the previous code declares a pointer to a tm structure. (The tm structure is defined by Visual C++ and shown in Listing E.14.) The second line sets the pointer to the tm structure created by the call to GetLocalTm().

Listing E.14óThe tm Structure

struct tm {
        int 
tm_sec;     /* seconds after the minute - [0,59] 
*/
        int tm_min;     /* minutes after the hour - [0,59] */
        int tm_hour;    /* hours since midnight - [0,23] 
*/
        int 
tm_mday;    /* day of the month - [1,31] */
        int tm_mon;     /* months since January - [0,11] */
        int 
tm_year;    /* years since 1900 */
        int tm_wday;    /* days since Sunday - [0,6] */
        int tm_yday;    /* days since January 1 - [0,365] */
        int tm_isdst;   /* daylight savings 
time flag */
        };

The CTime class features a number of overloaded constructors, enabling you to create CTime objects in various ways and using various times.

Using a CTimeSpan Object

A CTimeSpan object is nothing more complex than the difference between two times. You can use CTime objects in conjunction with CTimeSpan objects to easily determine the amount of time that's elapsed between two absolute times. To do this, first create a CTime object for the current time. Then, when the time you're measuring has elapsed, create a second CTime object for the current time. Subtracting the old time object from the new one gives you a CTimeSpan object representing the amount of time that has elapsed. The example in Listing E.15 shows how this process works.

Listing E.15óCalculating a Time Span

CTime startTime = 
CTime::GetCurrentTime();
    //.
    //. Time elapses...
    //.
CTime endTime = 
CTime::GetCurrentTime();
CTimeSpan timeSpan = endTime - startTime;


© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.