Home

MFC Collections: The CObArray Class

 

An Array ob Objects

 

Introduction

The MFC is a library rich of collection classes that allow you to create and manage any type of list. In this tradition, it is equipped with 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 and 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. If you want to follow this exercise, start Microsoft Visual C++ .NET or Visual Studio .NET
  2. Create a MFC Application
  3. Set the Name to CarRental1
  4. Create it as Dialog Based
  5. Set the Title to Bethesda Car Rental - Cars Inventory
     
  6. Click Finish and design the dialog box box as follows:
     
    Control ID Caption Other Properties
    Static Text   Tag Number:  
    Edit Control IDC_TAG_NUMBER    
    Button IDC_NEW_CAR New Car
    Static Text   Make:  
    Edit Control IDC_MAKE    
    Button IDC_DELETE_CAR Delete Car
    Static Text   Model  
    Edit Control IDC_MODEL    
    Static Control   Year  
    Edit Control IDC_YEAR   Align Text: Right
    Button IDCANCEL Close
    Button IDC_FIRST First  
    Button IDC_PREVIOUS Previous  
    Button IDC_NEXT Next
    Button IDC_LAST Last  
  7. If using MSVC 6, press Ctrl + W to display the ClassWizard. Display the Member Variables property page
    If using MSVC .NET, right-click each edit control and click Add Variable
  8. Create the Value variables as follows:
     
    ID Value Variable Control Variable
    Type Name
    IDC_TAG_NUMBER CString m_TagNumber m_edtTagNumber
    IDC_NEW_CAR     m_BtnNewCar
    IDC_MAKE CString m_Make  
    IDC_MODEL CString m_Model  
    IDC_YEAR int m_Year  
  9. Save all
 

Setting Up an Array of Objects

To create an object that you want to handle using a CObArray list, 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() method. This is necessary because you must specify what member variables would need to be persisted.

 

Practical Learning Practical Learning: Setting Up a List

  1. On the main menu, click Project -> Add Class
  2. In the Template section, select MFC Class and click Open
     
  3. Set the Class Name to CCar and, in the Base Class, select CObject
     
  4. Click Finish and change the files as follows:
     

    Header File: Car.h

    #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);
    };
    Source File: Car.cpp
    // Car.cpp : implementation file
    //
    
    #include "stdafx.h"
    #include "BCRSerial1.h"
    #include "Car.h"
    #include ".\car.h"
    
    IMPLEMENT_SERIAL(CCar, CObject, 1)
    
    // CCar
    
    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;
    	}
    }
  5. Save all

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 method or event, 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 Learning Practical Learning: Setting Up a List

  1. 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 CDialog
    {
    // 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;
    	CObArray ListOfCars;
    
    	// 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;
    };
  2. Save all

List 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() method. When such a class is used as a CObArray item, it can directly benefit from CObArray's own serialization.

To serialize a CObArray list in your project, all of you have to do is to call the CObArray::Serialize() method. Everything else would be handle behind the scenes for you.

 

Practical Learning Practical Learning: Serializing a List

  1. Generate the Serialize() method of the CCarRental1Dlg class (in Class View, click CCarRental1Dlg, then, in the Properties window, click the Overrides button, click Serialize and, in its combo box, select <Add> Serialize)
  2. Implement the method as follows:
     
    void CCarRental1Dlg::Serialize(CArchive& ar)
    {
    	ListOfCars.Serialize(ar);
    }
  3. Save all
 

Saving the List

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. To make our exercise simple, we will let the user create and manage the list while the application is opened. After using the application, when the user decides to close it, then we will automatically save the list.

 

Practical Learning Practical 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
    	char strFilename[] = "Cars.bcr";
    	CFile fleCars;
    
    	fleCars.Open(strFilename, CFile::modeCreate | CFile::modeWrite);
    	CArchive arc(&fleCars, CArchive::store);
    	Serialize(arc);
    
    	arc.Close();
    	fleCars.Close();
    	OnCancel();
    }
  3. Save all

Opening the List

Opening a list 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 our example, when the user opens the application, we will automatically open the file that holds the list, retrieve the list and get ready to use it. We will take care of this in the OnCreate() event of the dialog box of our application.

 

Practical Learning Practical Learning: Opening a List

  1. Generate the WM_CREATE message of the CCarRental1Dlg class (in the Class View, click CCarRental1Dlg, then, in the Properties window, click the Messages button; click WM_CREATE, click the arrow of its combo box and select <Add> OnCreate
  2. Implement the event as follows:
     
    int CCarRental1Dlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    	if (CDialog::OnCreate(lpCreateStruct) == -1)
    		return -1;
    
    	// TODO:  Add your specialized creation code here
    	char strFilename[] = "Cars.bcr";
    	CFile fleCars;
    	CFileFind fndFile;
    
    	// 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);
    		CArchive arc(&fleCars, CArchive::load);
    		Serialize(arc);
    
    		arc.Close();
    		fleCars.Close();
    	}
    
    	return 0;
    }
  3. Save all

Introduction to List Management

 

List Emptiness

When you start a list, before adding any item in 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 ann operation on a list, you will need to know whether it contains any item at all. This information can be given to you by calling the CObArray::IsEmpty() method. Its syntax is:

BOOL IsEmpty() const;

This method allows you to find out if the list already contains at least one member.

Practical Learning Practical Learning: Checking List Emptiness

  1. Display the dialog box. 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;
    }
  2. Return to the dialog box. 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;
    }
  3. Return to the dialog box. 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;
    }
  4. Return to the dialog box. 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;
    }
  5. Save all
 

The Index of an Item in the List

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

 

List Creation

 

Item Addition to the List

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

INT_PTR Add(CObject* newElement);

When calling this method, pass the item as argument. If this methods succeeds, which it usually does, it adds the new item to the list. 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 list.

Practical Learning Practical Learning: Starting a List-Based Application

  1. Display the dialog box and double-click the New Car button
  2. Implement its event as follows:
     
    void CCarRental1Dlg::OnBnClickedNewCar()
    {
    	// TODO: Add your control notification handler code here
    	UpdateData();
    	CString strCaption;
    	this->m_BtnNewCar.GetWindowText(strCaption);
    
    	if( strCaption == "New Car" )
    	{
    		this->m_TagNumber = "";
    		this->m_Make  = "";
    		this->m_Model = "";
    		this->m_Year  = 2000;
    		this->m_BtnNewCar.SetWindowText("Add");
    		this->m_edtTagNumber.SetFocus();
    	}
    	else
    	{
    		CCar *car = new CCar(this->m_TagNumber, m_Make, m_Model, m_Year);
    		this->ListOfCars.Add(car);
    
    		this->m_BtnNewCar.SetWindowText("New Car");
    		this->OnBnClickedLastBtn();
    	}
    	
    	UpdateData(FALSE);
    }
  3. Execute the application and create the following list of cars:
     
    Tax Number Make Model Year
    525708 Dodge Neon SE 2004
    DKO-739 Ford Focus SVT 2002
    M682468 Ford Expedition 2004
    DJK822 Daewoo Lanos 2000
    415-048 Chevrolet Monte Carlo LS 2005
  4. Close the dialog box and return to MSVC
 

Item Inserting Within the List

If a list already contains at least one item, when you call the CObArray::Add() method, 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() method 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 add a new item, newElement, at position nIndex. If you want o 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.

 

Item Location

 

Introduction

After creating the list, you can start managing the items. Before taking an action on a member of the list, you may first need to locate it or event to find out if 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() method. Its syntax is:

INT_PTR GetUpperBound() const;

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

 

Practical Learning Practical Learning: Locating an Item by its Index

  1. Access the OnCreate event of the CCarRental1Dlg class and change it as follows:
     
    int CCarRental1Dlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    	if (CDialog::OnCreate(lpCreateStruct) == -1)
    		return -1;
    
    	// TODO:  Add your specialized creation code here
    	char strFilename[] = "Cars.bcr";
    	CFile fleCars;
    	CFileFind fndFile;
    
    	// 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);
    		CArchive arc(&fleCars, CArchive::load);
    		Serialize(arc);
    
    		arc.Close();
    		fleCars.Close();
    
    		CCar *car = reinterpret_cast<CCar *>(this->ListOfCars[0]);
    
    		this->m_TagNumber = car->getTagNumber();
    		this->m_Make = car->getMake();
    		this->m_Model = car->getModel();
    		this->m_Year  = car->getYear();
    	}
    
    	return 0;
    }
  2. Save all
  3. Display the CCarRental1Dlg.h header file and declare an INT_PTR variable named CurrentPosition
     
    // Implementation
    protected:
    	HICON m_hIcon;
    	CObArray ListOfCars;
    	INT_PTR CurrentPosition;
  4. Initialize the variable in the OnInitDialog event at 0
     
    BOOL CCarRental1Dlg::OnInitDialog()
    {
    	CDialog::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
    	this->CurrentPosition = 0;
    
    	return TRUE;  // return TRUE  unless you set the focus to a control
    }
  5. Save all

Accessing the Members of an Array

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

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

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

 

The Size and Capacity of a List

 

The Number of Items in the List

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

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() method to know the size of the list. The syntax of the GetSize() method is:

INT_PTR GetSize() const;

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

 

Practical Learning Practical Learning: Accessing the Members of an Array

  1. In the Code Editor, make sure the left combo box displays CCarRental1Dlg. In the Members combo box, select OnBnClickedFirstBtn 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]);
    
    	this->m_TagNumber = car->getTagNumber();
    	this->m_Make = car->getMake();
    	this->m_Model = car->getModel();
    	this->m_Year  = car->getYear();
    
    	UpdateData(FALSE);
    }
  2. In the Members combo box, select OnBnClickedPreviousBtn 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]);
    
    	this->m_TagNumber = car->getTagNumber();
    	this->m_Make = car->getMake();
    	this->m_Model = car->getModel();
    	this->m_Year  = car->getYear();
    
    	UpdateData(FALSE);
    }
  3. In the Code Editor, make sure the left combo box displays CCarRental1Dlg. In the Members combo box, select OnBnClickedNextBtn 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]);
    
    	this->m_TagNumber = car->getTagNumber();
    	this->m_Make = car->getMake();
    	this->m_Model = car->getModel();
    	this->m_Year  = car->getYear();
    
    	UpdateData(FALSE);
    }
  4. In the Code Editor, make sure the left combo box displays CCarRental1Dlg. In the Members combo box, select OnBnClickedLastBtn 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]);
    
    	this->m_TagNumber = car->getTagNumber();
    	this->m_Make = car->getMake();
    	this->m_Model = car->getModel();
    	this->m_Year  = car->getYear();
    
    	UpdateData(FALSE);
    }
  5. Close the form

The Extra Memory Unused by Items

When a new item is being added to the list, the compiler increases the amount of memory allocated to the list. Some times, 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() method. Its syntax is:

void FreeExtra();

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

Deleting Items From a List

 

Deleting Items

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

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

The first to this method is the index of the item you want to delete from the list. The value of this argument must be between the bounds of the list. If you pass only one argument, only 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.

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

void RemoveAll( );

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

Practical Learning Practical Learning: Deleting a Member of an Array

  1. Display the dialog box and double-click the Delete Car button
  2. Implement its event as follows:
     
    void CDepartmentStore1Dlg::OnBnClickedDeleteBtn()
    {
    	// TODO: Add your control notification handler code here
    	if( CurrentPosition >= 0 )
    	{
    		this->StoreItems.RemoveAt(CurrentPosition);
    		this->OnBnClickedLastBtn();
    	}
    }
  3. Save all
 

Item Replacement

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

void SetAt(INT_PTR nIndex, CObject* newElement);

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

 

Home Copyright © 2005-2010 FunctionX, Inc.