MFC includes a lot more than classes for programming 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.
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 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 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 10.1 lists the member functions of the array classes and their descriptions.
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 size, which is the number of elements the array can hold. The array can still grow dynamically beyond this size. |
To illustrate how the array classes work, this chapter includes the Array Demo application, which you can find in the CHAP10\ARRAY folder of this book's CD-ROM. When you run the program, you see the window shown in Figure 10.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.
Figure 10.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, left-click in the application's window. The dialog box shown in Figure 10.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 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.
Figure 10.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 radio buttons set to Set. Figure 10.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 you type 5 into Index, 25 into Value, and click the Insert radio button. In Figure 10.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.
Figure 10.3 : The value of 15 has been placed into array element 3.
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 10.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 10.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!
Figure 10.4 : The screen now shows the new array element 5, giving 11 elements in all.
Figure 10.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 10.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.
Figure 10.6 : The Remove from Array dialog box enables you to delete elements from 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). By giving an initial array size and the amount by which to grow, you can create much more efficient array-handling code.
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 10.1 shows the Array Demo application's OnLButtonDown() function, which handles the left mouse button clicks.
Listing 10.1 LST10_1.TXT-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 in order 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.
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 10.2.
Listing 10.2 LST10_2.TXT-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.
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 10.3.
Listing 10.3 LST10_3.TXT-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.
Lists are like fancy arrays. Because lists (also called linked lists) 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. You'll see these two terms used often as you explore MFC's list classes.
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 10.2 lists and describes the 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. |
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 CHAP10\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 10.7. 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.
Figure 10.7 : A linked list has a head and a tail, with the remaining nodes in between.
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 10.8. 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 into the dialog box, you'd see the display shown in Figure 10.9.
Figure 10.8 : The List Demo application starts off with one node in its list.
Figure 10.9 : A left-click in the window brings up the Add Node dialog box.
You can also delete nodes from the list. To do this, right-click in the window to display the Remove Node dialog box (Figure 10.10). 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.
Figure 10.10 : Each node you add to the list can hold two different values.
NOTE |
If you try to delete nodes from an empty list, the List Demo application will display 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. |
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 10.4.
Listing 10.4 LST10_4.TXT-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 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 10.5.
Listing 10.5 LST10_5.TXT-Creating the First Node
CNode* pNode = new CNode; pNode->value1 = 11; pNode->value2 = 22; list.AddTail(pNode);
In Listing 10.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.
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 10.6.
Listing 10.6 LST10_6.TXT-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 10.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 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, the program enables you to delete nodes only from the head or tail of the list, as shown in Listing 10.7.
Listing 10.7 LST10_7.TXT-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 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!
NOTE |
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. |
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 10.8.
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 = textM6|ric.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 10.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 10.8, this NULL value is the condition that's used to terminate the while loop.
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 10.9.
Listing 10.9 LST10_9.TXT-Deleting the List's Objects
CMyListView::~CMyListView() { // Iterate over the list, deleting each node. while (!list.IsEmpty()) { CNode* pNode = (CNode*)list.RemoveHead(); delete pNode; } }
This destructor in Listing 10.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.
CAUTION |
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 you allocate in memory. |
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, 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 10.3.
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 the map's elements. |
RemoveKey() | Removes an element from the map. |
SetAt() | Adds a map element or replaces an element with a matching key. |
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 CHAP10\MAP folder of this book's CD-ROM. When you run the program, you see the window shown in Figure 10.11.
Figure 10.11 : 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 10.12. 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 10.13. If the key doesn't exist, the program's message box tells you so.
Figure 10.13 : This message box displays the requested map value.
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 10.10.
Listing 10.10 LST10_10.TXT-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.
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 10.11 shows this function.
Listing 10.11 LST10_11.TXT_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 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.
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 10.12.
Listing 10.12 LST10_12.TXT-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.
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 10.4 lists the 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. |
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 so 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();
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);
If you peruse your online documentation, you'll find that most of the other CString member functions are equally easy to use.
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 will get you started with these handy classes. Before you start working with the time classes, however, look over Table 10.5, which lists the member functions of the CTime class, and Table 10.6, which lists the member functions of the CTimeSpan 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. |
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. |
Creating a CTime object for the current time is a simple matter of calling the GetCurrentTime() function, like this:
CTime time = CTime::GetCurrentTime();
Notice that, 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 that you use 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 10.7.
Description | |
Day name, abbreviated (such as Sat for Saturday). | |
Day name, no abbreviation. | |
Month name, abbreviated (such as Mar for March). | |
Month name, no abbreviation. | |
Localized date and time (for the U.S., that would be something like 03/17/96 12:15:34). | |
Day of the month as a number (01Ð31). | |
Hour in the 24-hour format (00Ð23). | |
Hour in the normal 12-hour format (01Ð12). | |
Day of the year as a number (001Ð366). | |
Month as a number (01Ð12). | |
Minute as a number (00Ð59). | |
Localized a.m./p.m. indicator for 12-hour clock. | |
Second as a number (00Ð59). | |
Week of the year as a number (00Ð51, considering Sunday to be the first day of the week). | |
Day of the week as a number (0Ð6, with Sunday being 0). | |
Week of the year as a number (00Ð51, considering Monday to be the first day of the week). | |
Localized date representation. | |
Localized time representation. | |
Year without the century prefix as a number (00Ð99). | |
Year with the century prefix as a decimal number (such as 1996). | |
Name of time zone, abbreviated. | |
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():
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++.) The second line sets the pointer to the tm structure created by the call to GetLocalTm(). This function call essentially retrieves all of the time information at once, organized in the tm structure, which is defined in the header file TIME.H, as shown in Listing 10.13.
Listing 10.13 LST10_13.TXT-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 */ };
NOTE |
The CTime class features a number of overloaded constructors, enabling you to create CTime objects in various ways and using various times. n |
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 10.14 shows how this process works.
Listing 10.14 LST10_14.TXT-Calculating a Time Span
CTime startTime = CTime::GetCurrentTime(); //. //. Time elapses... //. CTime endTime = CTime::GetCurrentTime(); CTimeSpan timeSpan = endTime - startTime;
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 30, "Power-User C++ Features.") 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 10.15.
Listing 10.15 LST10_15.TXT-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, once 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 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 you need for your programs. You've probably used normal C++ arrays and maybe even linked lists in your programs before, but MFC's array and list classes boot those data structures into the 90s, giving them more power than ever. In addition, utility classes such as CTime, CTimeSpan, and CString make common programming tasks easier to implement. For information on related topics, you can refer to these chapters: