Home

Microsoft Visual C++/MFC File Processing: Serialization

        

Fundamentals of Serialization

 

Introduction

When performing file processing, the values are typically of primitive types (char, short, int, float, or double). In the same way, we can individually save many values, one at a time. This technique doesn't include an object created from (as a variable of) a class. By contrast, serialization is the ability to save an object, such as a variable of a class, at once. As done for file processing, serialization is used to save an object to a stream, such as a disk.

There are usually two catedories of serialization. One consists of saving one object to a stream. Another type of serialization is used to save a collection or list of objects.

MFC Archiving

As you may be aware already, the primary class used to perform file processing in MFC is called CArchive. On one hand, this parent-less class is used to transmit a value to a medium, such as a disk, where the value would be stored. If the item to be saved is an object made of disparate values, the CArchive object would write each of those values in a consequentive manner for later retrieval. On the other hand, a CArchive class can be used to get a value from a stream. If the value is actually an object made of various values, the CArchive object would get those values and put them together to reconstruct the object that was saved.

To handle a CArchive object, you must create a file that identifies the process by which the object will be saved or retrieved. This is done by declaring a CFile or a CFile-based object. After creating the CFile object, call its Open() member function and initialize it. Pass the first argument as the name of the file and the second argument to specify what operation you want to perform (to read to or write from a stream). Here is an example:

void CHouseDlg::OnFileProcessing()
{
    CFile fleHouse;

    fleHouse.Open(L"House2.hs", CFile::modeCreate | CFile::modeWrite);
}

MFC Support for Serialization

The MFC library has a high level of support for serialization. It starts with the CObject class that is the ancestor to most MFC classes. This CObject class is equipped with a member function named Serialize. Its syntax is:

virtual void Serialize(CArchive& ar);

As you can see, this member function takes a CArchive object as argument. This is the object that holds the value(s) to be be saved. The actual serialization needs more information than that.

The Process of Serializing an Object

 

Introduction

There are a few steps you should (must) follow to implement serialization in an MFC application. As mentioned already, serialization consists of saving an object. This means that you must either get an object or create one. This also means that you can use an existing class to serialize or you must first create a class.

To create a class whose objects would be serialized, derive it from CObject. Here is an example:

class CHouse : public CObject
{

};

Since this is primarily a normal C++ class, you can (in fact should, even must) add an argument-less constructor (the default constructor) and an optional destructor to it. Here is an example:

class CHouse : public CObject
{
public:
    CHouse();
    ~CHouse();
};

Declaring a Serial Macro

In C/C++, a macro is an action that the compiler must perform without checking whether it is right. The MFC provides many types of macros.

One of the macros of the MFC is named DECLARE_SERIAL. This allows you to indicate that a class can be serialized. To use, type it followed by parentheses in the source file of the class and in a public section. The DECLARE_SERIAL macro takes one constructor. In the parentheses of the macro, type the name of the class. This would be done as follows:

class CHouse : public CObject
{
public:
    CHouse(void);
    ~CHouse(void);
	
    DECLARE_SERIAL(CHouse);
};

Implementing a Serial Macro

The counterpart of the DECLARE_SERIAL macro is used in the implementation of the class and it is named IMPLEMENT_SERIAL. To use it, in the body of the implementation of the class, type it followed by parentheses.

The IMPLEMENT_SERIAL macro takes three arguments. In its parentheses, type the first argument as the name of your class and the second argument as its base class. The third argument is called a schema number and it is a positive integer. This means that you can specify it as 0, 1, or up. Here is an example:

#include "House.h"

IMPLEMENT_SERIAL(CHouse, CObject, 0)

CHouse::CHouse(void)
{
}

CHouse::~CHouse(void)
{
}

When an object is being saved, that is, during serialization, the schema number is saved too. When an object is deserialized, the compiler is given a schema number. During that serialization, the compiler checks whether both numbers (the number used to serialize and the number used to deserialize) are the same. There are two primary options and an alternative:

  • If the numbers are the same, the object gets opened and read
  • If the number passed for deserialization is not found (at all) or is different than the number that was used to serialize the same object, the compiler throws a CArchiveException exception
  • As an alternative, you can save an object in more than one version (more than one schema number), in which case each schema number is considered its own version, and pass a different number when it becomes time to deserialize one of them

Preparing for Serialization

Remember that the CObject class is equiped with the Serialize() member function. In your CObject-derived class, you should override that member function. The CObject::Serialize() member function takes a CArchive reference as argument. Here is an example of creating it in a header file:

class CHouse : public CObject
{
public:
    CHouse();
    ~CHouse();
    
public:
    void Serialize(CArchive& ar);
	
    DECLARE_SERIAL(CHouse);
};

When overriding the CObject::Serialize() member function, implement the necessary mechanism that will indicate how an object of that class is serialized. In the MFC library (unlike some libraries, such as the .NET Framework), only one member function is used for both serialization and deserialization. Remember that the CArchive class is equipped with the IsStoring() member function that specifies the serialization and deserialization option. The C++ equivalent operations are << and >> respectively.

When implementing the Serialize() member function, you can use an if conditional statement to find out whether the object is being serialized or deserialized. This can be done as follows:

Header File:

#pragma once
#include "afx.h"
class CHouse : public CObject
{
public:
    CHouse(void);
    ~CHouse(void);

private:
    CString HouseNumber;
    CString HouseType;
    CString Condition;
    double  MarketValue;
	
public:
    void Serialize(CArchive& ar);

    DECLARE_SERIAL(CHouse);
};

Source File:

#include "StdAfx.h"
#include "House.h"

IMPLEMENT_SERIAL(CHouse, CObject, 0)

CHouse::CHouse(void)
{
}

CHouse::~CHouse(void)
{
}

void CHouse::Serialize(CArchive& ar)
{
    CObject::Serialize(ar);
    
    if( ar.IsStoring() )
	ar << HouseNumber << HouseType << Condition << MarketValue;
    else
	ar >> HouseNumber >> HouseType >> Condition >> MarketValue;
}

Serializating an Object

After creating the class, you can serialize. This is done by calling its Serialize() member function. As you may know already, this member function takes a CArchive argument. This means that, first build a CArchive object before calling the Serialize() member function. The CArchive object takes a CFile or CFile-based argument that holds the name of the file to open, and the option that specifies to save the file, such as CArchive::store. Hee is an example of how this can be done.

void CHouseDlg::OnBnClickedSave()
{
	UpdateData(TRUE);

	CFile fleHouse;

	fleHouse.Open(L"House1.hse", CFile::modeCreate | CFile::modeWrite);
	CArchive ar(&fleHouse, CArchive::store);

	house.HouseNumber = "882-100"; 	     // m_HouseNumber;
	house.HouseType   = "Single Family"; // m_HouseType;
	house.Condition   = "Excellent";     // m_Condition;
	house.MarketValue = 685440; 	     // m_MarketValue;

	house.Serialize(ar);
		
	ar.Close();
}

Deserializating an Object

After saving an object, to deserialize it, once again you can call its Serialize member function. First create a CArchive object that holds the name of the file and the type of operation; in this case, this would be CArchive::load. Once the object has been deserialized, it holds the values you can then individually retrieve. Here is an example of how this can be done:

void CHouseDlg::OnBnClickedOpen()
{
	UpdateData(TRUE);

	CFile fleHouse;

	fleHouse.Open(L"House1.hse", CFile::modeRead);
	CArchive ar($fleHouse, CArchive::load);

	house.Serialize(ar);
	
	m_HouseNumber = house.HouseNumber;
	m_HouseType   = house.HouseType;
	m_Condition   = house.Condition;
	m_MarketValue = house.MarketValue;

	ar.Close();
	fleHouse.Close();

	UpdateData(FALSE);
}

Serialization and Collections

 

Introduction

Although serialization can be performed on an object, it becomes redundant if you know the values of the object; you can simply save them individually. The main importance of serialization is in its ability to save a list of objects where each item is made of internal individual values.

As you may know already, the MFC provides two categories of collection classes: non-templates and template-based. In the same way, the MFC provides two broad types of serialization. One is meant for non-template classes and the other for template-based colleciton classes.

Non-Template Serialization

The MFC library provides the following classes that don't use templates: CObArray, CByteArray, CDWordArray, CPtrArray, CStringArray, CWordArray, CUIntArray, CObList, CPtrList, CStringList, CMapPtrToWord, CMapPtrToPtr, CMapStringToOb, CMapStringToPtr, CMapStringToString, CMapWordToOb, and CMapWordToPtr.

We start with serialization of non-template collection classes because their objects are really easy to do.

Introduction to an Array of Objects

One of the collection classes of the MFC library is the CObArray class. The particularity of this class is that it can be used to create a list of CObject values. This doesn't mean that the objects have to fit in any particular MFC scenario. Of course, there are a few rules you must follow but the main theory is that the class that would constitute the object must be derived from CObject. Normally, this is an advantage because such a class would be ready for serialization and MFC's exception handling.

The CObArray class is part of MFC's collection classes defined in the afxcoll.h library. Therefore, if you plan to use it to create and manage your list, make sure you include this header file where necessary.

Practical LearningPractical Learning: Introducing the List of Objects

  1. Start Microsoft Visual Studio
  2. To create a new application, on the main menu, click File -> New Project...
  3. In the middle list, click MFC Application
  4. Set the Name to CarRental1
  5. Click OK
  6. In the second page of the wizard, click Next
  7. In the second page, click Dialog Based and click Next
  8. In the third page, set the Title to Bethesda Car Rental - Cars Inventory
  9. Click Finish
  10. Design the dialog box box as follows:
     
    Bethesda Car Rental - Cars Inventory
    Control ID Caption Other Properties
    Static Text Static Text   Tag Number:  
    Edit Control Edit Control IDC_TAG_NUMBER    
    Static Text Static Text   Make:  
    Edit Control Edit Control IDC_MAKE    
    Static Text Static Text   Model  
    Edit Control Edit Control IDC_MODEL    
    Static Control Static Text   Year  
    Edit Control Edit Control IDC_YEAR   Align Text: Right
    Button Button IDC_FIRST First  
    Button Button IDC_PREVIOUS Previous  
    Button Button IDC_NEXT Next  
    Button Button IDC_LAST Last  
    Button Button IDC_NEW_CAR New Car  
    Button Button IDC_DELETE_CAR Delete Car  
    Button Button IDCANCEL Close  
  11. Right-click the dialog box and click Class Wizard...
  12. In the Class Name, make sure CCarRental1Dlg is selected.
    Click the Member Variables tab
  13. Double-click each edit control and click Add Variable
  14. Create the Value variables as follows:
     
    ID
    Category Type Name
    IDC_MAKE Value CString m_Make
    IDC_MODEL Value CString m_Model
    IDC_TAG_NUMBER Value CString m_TagNumber
    IDC_YEAR Value int m_Year
  15. Click OK to close the MFC Class Wizard

Setting Up an Array of Objects

To create an object that you want to handle using a CObArray collection, the first rule you must follow is that your class must be based on CObject. If you only plan to create an object and use it temporarily, this is all you need to do. If you want to be able to save the list, there are a few more steps you must follow. For example, you must include the DECLARE_SERIAL macro in the header file of your class and you must include the IMPLEMENT_SERIAL macro in the source file of your class. Also, if you plan to save your class, you must override the derived Serialize() member function. This is necessary because you must specify what member variable(s) would need to be persisted.

Practical LearningPractical Learning: Setting Up a List

  1. In the Class View, right-click the name of the project -> Add -> Class
  2. In the middle section, click MFC Class
  3.  Click Add
  4. Set the Class Name to CCar and, in the Base Class, select CObject
     
    MFC Add Class Wizard
  5. Click Finish
  6. Change the header file as follows:
    #pragma once
    
    // CCar command target
    
    class CCar : public CObject
    {
    	DECLARE_SERIAL(CCar)
    	
    public:
    	CCar();
    	CCar(CString tagnbr, CString mk, CString mdl, int yr);
    	virtual ~CCar();
    	
    private:
    	CString TagNumber;
    	CString Make;
    	CString Model;
    	int Year;
    public:
    	CString getTagNumber(void);
    	void setTagNumber(CString tag);
    	CString getMake(void);
    	void setMake(CString mk);
    	CString getModel(void);
    	void setModel(CString mdl);
    	int getYear(void);
    	void setYear(int yr);
    	virtual void Serialize(CArchive& ar);
    };
  7. Access the Car.cpp source file and change it as follows:
    // Car.cpp : implementation file
    //
    
    #include "stdafx.h"
    #include "CarRental1.h"
    #include "Car.h"
    
    // CCar
    
    IMPLEMENT_SERIAL(CCar, CObject, 1)
    
    CCar::CCar()
    	: TagNumber(_T("")), Make(_T("")), Model(_T("")), Year(0)
    {
    }
    
    CCar::CCar(CString tagnbr, CString mk, CString mdl, int yr)
    	: TagNumber(tagnbr), Make(mk), Model(mdl), Year(yr)
    {
    }
    
    CCar::~CCar()
    {
    }
    
    // CCar member functions
    
    CString CCar::getTagNumber(void)
    {
    	return TagNumber;
    }
    
    void CCar::setTagNumber(CString tag)
    {
    	TagNumber = tag;
    }
    
    CString CCar::getMake(void)
    {
    	return Make;
    }
    
    void CCar::setMake(CString mk)
    {
    	Make = mk;
    }
    
    CString CCar::getModel(void)
    {
    	return Model;
    }
    
    void CCar::setModel(CString mdl)
    {
    	Model = mdl;
    }
    
    int CCar::getYear(void)
    {
    	return Year;
    }
    
    void CCar::setYear(int yr)
    {
    	Year = yr;
    }
    
    void CCar::Serialize(CArchive& ar)
    {
    	CObject::Serialize(ar);
    	
    	if (ar.IsStoring())
    	{
    		ar << TagNumber << Make << Model << Year;
    	}
    	else
    	{
    		ar >> TagNumber >> Make >> Model >> Year;
    	}
    }
    

List Setup

When you are ready to start the list, like any other class, you must first declare a variable of type CObArray. To support this, the CObArray class is equipped with a simple default constructor. If you are planning to create and manage the list from one member function or message, you can declare the variable locally. On the other hand, if you are planning to access the list from various sections of your code, you should declare the variable globally, for example, in the header file.

Practical LearningPractical Learning: Setting Up a List

  • Access the CarRental1Dlg.h header file. Include the afxtempl.h file and declare a CObArray variable named ListOfCars:
     
    // CarRental1Dlg.h : header file
    //
    
    #pragma once
    
    #include "afxcoll.h"
    
    // CCarRental1Dlg dialog
    class CCarRental1Dlg : public CDialogEx
    {
    // Construction
    public:
    	CCarRental1Dlg(CWnd* pParent = NULL);	// standard constructor
    
    // Dialog Data
    	enum { IDD = IDD_CARRENTAL1_DIALOG };
    
    	protected:
    	virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV support
    
    
    // Implementation
    protected:
    	HICON m_hIcon;
    
    	// Generated message map functions
    	virtual BOOL OnInitDialog();
    	afx_msg void OnPaint();
    	afx_msg HCURSOR OnQueryDragIcon();
    	DECLARE_MESSAGE_MAP()
    public:
    	CString m_TagNumber;
    	CString m_Make;
    	CString m_Model;
    	int m_Year;
    
    private:
    	CObArray ListOfCars;
    };
 
 
 

Collection Serialization

 

Introduction

One of the advantages of using the CObArray class to create and manage a list is that it has a built-in mechanism for serialization. This allows you to easily save a list to a medium and be able to open it when necessary. As mentioned earlier, to prepare a class for serialization, you must make sure you override its Serialize() member function. When such a class is used as a CObArray item, it can directly benefit from CObArray's own serialization.

To serialize a CObArray collection in your project, all of you have to do is to call the CObArray::Serialize() member function. Everything else would be handled behind the scenes.

Practical LearningPractical Learning: Serializing a List

  1. On the main menu, click Project -> Class Wizard...
  2. In the Class Name, make sure CCarRental1Dlg is selected.
    Click the Virtual Functions tab
  3. In the Virtual Functions list, scroll down and double-click Serialize
  4. Click Edit Code and implement the member function as follows:
    void CCarRental1Dlg::Serialize(CArchive& ar)
    {
        if (ar.IsStoring())
        {	// storing code
        }
        else
        {	// loading code
        }
    
        ListOfCars.Serialize(ar);
    }
 

Saving the Collection

The actions we have taken so far with regards to serialization allow our class to know how to save the list and what to save. The next action is to tell the project when to save the list. This is traditionally done by using the common Windows Save dialog box. Another way is to let the user create and manage the list while the application is opened. After using the application, when the user decides to close it, you can automatically save the list.

Practical LearningPractical Learning: Saving a List

  1. Display the dialog box and double-click the Close button
  2. Implement its event as follows: 
    void CCarRental1Dlg::OnBnClickedCancel()
    {
    	// TODO: Add your control notification handler code here
    	CFile fleCars;
    	CString strFilename = _T("Cars.bcr");
    
    	fleCars.Open(strFilename, CFile::modeCreate | CFile::modeWrite);
    	CArchive arc(&fleCars, CArchive::store);
    	Serialize(arc);
    
    	arc.Close();
    	fleCars.Close();
    	
    	CDialogEx::OnCancel();
    }

Opening the Collection

Opening a collection consists of retrieving it from an archive and making it available to an application. As done when saving, it is up to you to decide when this action must occur. For example, when the user opens the application, you can automatically open the file that holds the collection, retrieve the collection and get ready to use it. This can be done in the OnCreate() event of the dialog box of the application.

Practical LearningPractical Learning: Opening a List

  1. On the manu menu, click Project -> Class Wizard...
  2. In the Class Name, make sure CCarRental1Dlg is selected.
    Click the Virtual Functions tab
  3. In the Overridden Virtual Functions list, click OnInitDialog
  4. Click Edit Code and implement the event as follows:
    BOOL CCarRental1Dlg::OnInitDialog()
    {
        CDialogEx::OnInitDialog();
    
        // Set the icon for this dialog.  The framework does this automatically
        //  when the application's main window is not a dialog
        SetIcon(m_hIcon, TRUE);			// Set big icon
        SetIcon(m_hIcon, FALSE);		// Set small icon
    
        // TODO: Add extra initialization here
        CFile fleCars;
        CFileFind fndFile;
        CString strFilename = _T("Cars.bcr");
    
        // Look for the Cars.bcr file
        BOOL exists = fndFile.FindFile(strFilename);
    
        if( exists == TRUE )
        {
    	// If the file exists, open it and fill the controls with it
    	fleCars.Open(strFilename, CFile::modeRead);
    	// Create a CArchive object to receive the collection
    	CArchive arc(&fleCars, CArchive::load);
    	// Pass the CArchive object to the Serialize() function of this application
    	Serialize(arc);
    
    	// Close the archive
    	arc.Close();
    	// Close the file stream
    	fleCars.Close();
    
    	// Behave as if the user had just clicked the First button
    	OnBnClickedFirst();
        }
    
        return TRUE;  // return TRUE  unless you set the focus to a control
    }

Introduction to Collection Management

 

An Empty Collection

When you start a collection, before adding any item to it, you would know that it is empty. If someone else had created it, you would not have this information. In some cases, before performing an operation on a collection, you will need to know whether it contains any item at all. This information can be given to you by calling the CObArray::IsEmpty() member function. Its syntax is:

BOOL IsEmpty() const;

This member function allows you to find out if the collection already contains at least one member.

Practical LearningPractical Learning: Checking List Emptiness

  1. Display the dialog box
  2. Double-click the First button and implement its event as follows: 
    void CCarRental1Dlg::OnBnClickedFirst()
    {
    	// TODO: Add your control notification handler code here
    	if( this->ListOfCars.IsEmpty() == TRUE )
    		return;
    }
  3. Return to the dialog box
  4. Double-click the Previous button and implement its event as follows:
    void CCarRental1Dlg::OnBnClickedPrevious()
    {
    	// TODO: Add your control notification handler code here
    	if( this->ListOfCars.IsEmpty() == TRUE )
    		return;
    }
  5. Return to the dialog box
  6. Double-click the Next button and implement its event as follows:
    void CCarRental1Dlg::OnBnClickedNext()
    {
    	// TODO: Add your control notification handler code here
    	if( this->ListOfCars.IsEmpty() == TRUE )
    		return;
    }
  7. Return to the dialog box
  8. Double-click the Last button and implement the events as follows:
    void CCarRental1Dlg::OnBnClickedLast()
    {
    	// TODO: Add your control notification handler code here
    	if( this->ListOfCars.IsEmpty() == TRUE )
    		return;
    }

The Index of an Item in the Collection

As we will see in the next sections, there are various operations you can perform on a collection, such as adding or inserting a new item. When an item is part of the collection, that item occupies a specific position so you can locate it easily. The members of a CObArray collection are stored at positions represented by the INT_PTR data type.

Creating a Collection

 

Additing a New Item

Probably the first action to take with regards to a collection consists of populating it, since an empty list may not be very useful. After creating an item, to add it to a CObArray collection, you can call the Add() member function. Its syntax is:

INT_PTR Add(CObject* newElement);

When calling this member function, pass the item as argument. If this member function succeeds, which it usually does, it adds the new item to the collection. If the list was empty, it would be started with this item. If the list already contained at least one item, the new item would be added to the end of the collection.

Practical LearningPractical Learning: Starting a List-Based Application

  1. In the Resource View, right-click the name of the project -> Add -> Resource...
  2. In the Add Resource dialog box, click Dialog and click New
  3. In the Properties window, change its ID to IDD_CAR_EDITOR_DLG
  4. Design the dialog box as follows:
     
    Bethesda Car Rental - Car Editor
    Control ID Caption Other Properties
    Static Text   Tag Number:  
    Edit Control IDC_TAG_NUMBER    
    Static Text   Make:  
    Edit Control IDC_MAKE    
    Static Text   Model:  
    Edit Control IDC_MODEL    
    Static Control   Year:  
    Edit Control IDC_YEAR   Align Text: Right
  5. Right-click an unoccupied area of the dialog box and click Class Wizard...
  6. On the right side of the MFC Class Wizard dialog box, click Add Class
  7. In the Class Name, type CCarEditorDlg
  8. In the Base Class, select CDialogEx
  9. In the Dialog ID, select IDD_CAR_EDITOR_DLG
     
    MFC Add Class Wizard
  10. Click Finish
  11. In the Class Name, make sure CCarEditorDlg is selected.
    Click the Member Variables tab
  12. In the Member Variables, click IDC_MAKE
  13. Click Add Variable...
  14. Set the Member Variable Name to m_Make
  15. Set the Category to Value and make sure the Variable Type is set to CString
  16. Click OK
  17. In the same way, using the IDs in the Member Variable, add the member variables as follows:
     
    ID Value Variable Variable Type
    IDC_MAKE m_Make CString
    IDC_MODEL m_Model CString
    IDC_TAG_NUMBER m_TagNumber CString
    IDC_YEAR m_Year int

    MFC Class Wizard
  18. Click OK
  19. Display the first dialog box
  20. Double-click the New Car button
  21. In the top section of the file, include the Car.h header file
    // CarRental1Dlg.cpp : implementation file
    //
    
    #include "stdafx.h"
    #include "CarRental1.h"
    #include "CarRental1Dlg.h"
    #include "afxdialogex.h"
    #include "Car.h"
    #include "CarEditorDlg.h"
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
  22. Scroll down and implement the new event as follows:
    void CCarRental1Dlg::OnBnClickedNewCar()
    {
        // TODO: Add your control notification handler code hereUpdateData();
        // Initialize the Car Editor dialog box
        CCarEditorDlg dlg;
    
        // Display the Car Editor dialog box and find out if the user clicked OK
        if( dlg.DoModal() == IDOK )
        {
    	// If so, get the values the user entered in the dialog box
    	// and create a CCar object from them
    	CCar *car = new CCar(dlg.m_TagNumber, dlg.m_Make, dlg.m_Model, dlg.m_Year);
    	// Add that new car to the collection
    	ListOfCars.Add(car);
    
    	// Display the beginning of the collection, if there is any CCar object
    	OnBnClickedFirst();
        }
    }
  23. To execute the application, press F5
  24. Click the New Car button and enter the values for a car, then click Add:
     
    Tax Number Make Model Year
    374-957 Dodge Ram 1500 Laramie 2010
    790974 Ford Fusion 2011
    M038241 Mercury Mountaineer 2008
    797495 Lincoln MKS 2011
    M797305 Dodge Ram 2500 2010
  25. Close the dialog box and return to your programming environment

Inserting an Item

If a collection already contains at least one item, when you call the CObArray::Add() member function, the new item would added to the end of the list. In some cases, you can want to insert the new item somewhere within the list. To support this operation, the CObArray class provides the InsertAt() member function which is overloaded with two versions. Their syntaxes are:

void InsertAt(INT_PTR nIndex, CObject* newElement, INT_PTR nCount = 1);
void InsertAt(INT_PTR nStartIndex, CObArray* pNewArray);

The first version allows you to insert a new item, newElement, at position nIndex. If you want to insert more than one sample of the same item, specify the number as the nCount argument.

The second argument allows you to insert another CObArray list to this one, at position nStartIndex.

Locating an Item

After creating the collection, you can start managing the items. Before taking an action on a member of the collection, you may first need to locate it or event to find out whether it exists. We previously mentioned that the index of each member is represented by an INT_PTR value. To allow you to scan the list and locate each item by its index using the same concept of the C++' arrays, the [] operator was overloaded in two versions that use the following syntaxes:

CObject*& operator [](INT_PTR nIndex);
CObject* operator [](INT_PTR nIndex) const;

This operator allows you to locate an item by specifying its index between the square brackets of the CObArray variable.

When a list is not empty, the index of the last item can be gotten by calling the CObArray::GetUpperBound() member function. Its syntax is:

INT_PTR GetUpperBound() const;

This member function returns the index of the last member of the array.

Practical LearningPractical Learning: Locating an Item by its Index

  1. Display the CCarRental1Dlg.h header file
  2. Declare an INT_PTR variable named CurrentPosition:
    private:
        CObArray ListOfCars;
        INT_PTR CurrentPosition;

Accessing the Members as an Array

To store all the members of the list in one pointer, you can call the CObArray::GetData() member function that is overloaded with two versions whose syntaxes are:

const CObject** GetData( ) const; 
CObject** GetData( );

This member function can help you access all members of the list as one.

The Size and Capacity of a Collection

 

The Number of Items in the Collection

To know the number of items in the list, you can call the CObArray::GetCount() member function:

INT_PTR GetCount() const;

Since the list is zero-based, the actual number of items is the total - 1. Besides GetCount(), you can call the CArray::GetSize() member function to know the size of the list. The syntax of the GetSize() member function is:

INT_PTR GetSize() const;

This member function returns the size of the list. Like GetCount(), the GetSize() member function returns the total number of items - 1. 

Practical LearningPractical Learning: Accessing the Members of an Array

  1. Access the source file of the first dialog box
  2. In the Code Editor, make sure the left combo box displays CCarRental1Dlg.
    In the Members combo box, select OnBnClickedFirst and change its events as follows:
    void CCarRental1Dlg::OnBnClickedFirst()
    {
    	// TODO: Add your control notification handler code here
    	if( this->ListOfCars.IsEmpty() == TRUE )
    		return;
    
    	CurrentPosition = 0;
    	CCar *car = reinterpret_cast<CCar *>(this->ListOfCars[CurrentPosition]);
    
    	m_TagNumber = car->getTagNumber();
    	m_Make = car->getMake();
    	m_Model = car->getModel();
    	m_Year  = car->getYear();
    
    	UpdateData(FALSE);
    }
  3. In the Members combo box, select OnBnClickedPrevious and change its events as follows:
    void CCarRental1Dlg::OnBnClickedPrevious()
    {
    	// TODO: Add your control notification handler code here
    	if( this->ListOfCars.IsEmpty() == TRUE )
    		return;
    
    	if( CurrentPosition != 0 )
    		CurrentPosition--;
    	CCar *car = reinterpret_cast<CCar *>(this->ListOfCars[CurrentPosition]);
    
    	m_TagNumber = car->getTagNumber();
    	m_Make = car->getMake();
    	m_Model = car->getModel();
    	m_Year  = car->getYear();
    
    	UpdateData(FALSE);
    }
  4. In the Members combo box, select OnBnClickedNext and change its events as follows:
    void CCarRental1Dlg::OnBnClickedNext()
    {
    	// TODO: Add your control notification handler code here
    	if( this->ListOfCars.IsEmpty() == TRUE )
    		return;
    
    	if( CurrentPosition != (this->ListOfCars.GetSize() - 1) )
    		CurrentPosition++;
    	CCar *car = reinterpret_cast<CCar *>(this->ListOfCars[CurrentPosition]);
    
    	m_TagNumber = car->getTagNumber();
    	m_Make = car->getMake();
    	m_Model = car->getModel();
    	m_Year  = car->getYear();
    
    	UpdateData(FALSE);
    }
  5. In the Members combo box, select OnBnClickedLast and change its events as follows:
    void CCarRental1Dlg::OnBnClickedLast()
    {
    	// TODO: Add your control notification handler code here
    	if( this->ListOfCars.IsEmpty() == TRUE )
    		return;
    	
    	CurrentPosition = this->ListOfCars.GetSize() - 1;
    	CCar *car = reinterpret_cast<CCar *>(this->ListOfCars[CurrentPosition]);
    
    	m_TagNumber = car->getTagNumber();
    	m_Make = car->getMake();
    	m_Model = car->getModel();
    	m_Year  = car->getYear();
    
    	UpdateData(FALSE);
    }
  6. To execute the application, press F5
  7. Close the dialog box and return to your programming environment

The Extra Memory Unused by Items

When a new item is being added to a CObArray collection, the compiler increases the amount of memory allocated to the list. Sometimes, the amount increased may be more than was necessary. If you want to intervene in releasing any extra memory that is not used, you can call the CObArray::FreeExtra() member function. Its syntax is:

void FreeExtra();

This member function can be used to release unused memory that was allocated when the list of items was increased.

Deleting Items From a Collection

 

Deleting Items

If your CObArray collection contains an item you don't need anymore, you can remove it. To delete an item, you can call the CObArray:

void RemoveAt(INT_PTR nIndex, INT_PTR nCount = 1);

The first argument to this member function is the index of the item you want to delete. The value of this argument must be between the bounds of the collection. The second argument is optional. If you pass only the first argument, the item at that position would be deleted. If you want to delete more than one argument, pass the desired number as the second argument.

Clearing the Collection

To delete all items from a CObArray list, call its RemoveAll() member function. Its syntax is:

void RemoveAll( );

When called, this member function will clear the list of all members.

Practical LearningPractical Learning: Deleting a Member of an Array

  1. Display the first dialog box
  2. Double-click the Delete Car button
  3. Implement its event as follows:
    void CCarRental1Dlg::OnBnClickedDeleteCar()
    {
        // TODO: Add your control notification handler code here
        if( CurrentPosition >= 0 )
        {
    	ListOfCars.RemoveAt(CurrentPosition);
    	OnBnClickedFirst();
        }
    }
  4. To execute, press F5
  5. Using the Next button, navigate to the Lincoln MKS
  6. Click the Delete Car button
  7. Close the dialog box and return to your programming environment

Replacing an Item

Instead of simply deleting an item, you may want to replace it with another. This operation is supported by the CObArray::SetAt() member function. Its syntax is:

void SetAt(INT_PTR nIndex, CObject* newElement);

The first argument of this member function is the index that the new item will occupy in the list.

 
 
   
 

Home Copyright © 2010 FunctionX, Inc.