Click here to Skip to main content
15,914,013 members
Articles / Programming Languages / C++

Serializing a Collection of Collections (Using CObArray)

Rate me:
Please Sign up or sign in to vote.
2.67/5 (2 votes)
19 Mar 2007CPOL12 min read 28.4K   281   14   1
Serializing objects and their relationships to each other.

Screenshot - collofcolls.jpg

Introduction

This is an article written by a beginner for beginners. Or at least, by a beginner for his own benefit, but he is posting it up on CodeProject because he is hoping that it will be of interest to others too. The style is intended to be useful to someone who is still relatively new (but not completely new) to setting up projects using the Visual Studio 6 IDE. The content of the article may be of interest to someone who is familiar with the technique of serializing an array of simple objects contained in a CObArray container, but who has wondered how this method can be extended to more complex data structures.

Background

I wrote this program to find a way of storing and recalling information about relationships between objects using MFC serialization techniques. From the names of my custom classes I have used (CNode and CSubNode), it may be apparent to the reader that I am interested in investigating ways of modeling networks. The project discussed in this article represents a possible method for storing and recalling various network structures.

Using the code

The zip file that is available with this article contains the entire code for the project described below. The code pertinent to the serialization of a collection of collections is in the Serialize functions of the CSerializeExpDoc, CNode, and CSubNode classes.

In the example project, I have made the first level of objects being collected of type 'CNode' and the objects those objects collect of type 'CSubNode'.

The CNode class inherits from CSubNode which, in turn, inherits from CObject. CNode, therefore, inherits the CObject properties via CSubNode as well as a few other members.

For the serialization to work, the CNode class doesn't have to be derived from CSubNode, but if it wasn't, CNode would explicitly have to inherit from CObject using the following declaration in the file 'Node.h':

C++
class CNode : public CObject

instead of:

C++
class CNode : public CSubNode

Also, the GetString, SetString, and m_strString members would have to be replicated in CNode.

The code for this project has been made available for you to download, peruse, compile, and run as you like.

Here follows a description of creating the project from beginning to end...

Create the skeleton application

Create a Single Document Interface program with Document/View architecture support. The project here is called 'SerializeExp'. De-select ActiveX. You don't specifically need ActiveX here. On stage four of the Application Wizard, click 'Advanced' and choose the file extension to be used by your application's data files. On step 6, change the base class to 'CFormView'. You can now click 'Finish' to build your skeleton project.

Make up the form and prepare the control variables

The necessary controls to be placed on the form can be divided into two groups: those associated with nodes and those associated with sub nodes.

Here are the controls with the View class member variables to be associated with them via the Class Wizard:

Control TypeControl IDView Class Member VariableOther Information
group boxIDC_STATIC-Caption: Node
static textIDC_SNPOSITIONCString m_sNPositionCaption: Node information\n\nRecord 0 of 0
group boxIDC_STATIC--
edit boxIDC_ENODCString m_sNod-
buttonIDC_BNODLEFT-Caption: <
buttonIDC_BNODRIGHT-Caption: >
group boxIDC_STATIC-Caption: Sub Node
static textIDC_SSPOSITIONCString m_sSPositionCaption: Sub Node information\n\nRecord 0 of 0
group boxIDC_STATIC--
edit boxIDC_ESUBNODCString m_sSubNod-
static textIDC_STATIC--
edit boxIDC_ESNWEIGHTshort m_shortSubNodeWeightStyles: Number
buttonIDC_BSUBNODLEFT-Caption: <
buttonIDC_BSUBNODRIGHT-Caption: >

Create the custom classes

Right-click over where it says 'SerializeExp' in the ClassView workspace, and click on 'New Class...'. Change the class type to 'Generic Class', call it 'CSubNode', and derive it from 'CObject' as public.

Repeat the above instructions to create another class called 'CNode', but this time, derive it from 'CSubNode' as public instead of 'CObject'.

Add the class variables and methods for reading and writing variables

To the class CNode, add a public variable called m_oaSubNodes of type CObArray, and a private variable called m_iSubNodePosition of type int.

To the class CSubNode, add a private variable called m_shortWeight of type short, and a protected variable called m_strString of type CString.

One of the sources I based this project on (Chapman) uses inline functions for getting and setting class variables. Inline functions are faster to execute than non-inline functions, but can make executable files physically larger, since, when the program is compiled, the entire function is copied to each point in the program where it is called, instead of causing a jump in the execution of the program to a single instance of the code.

An inline function is created simply by putting the implementation of the function immediately after the declaration in the class' header file. So for the subnode class, you should add inline functions for getting and setting m_strString and m_shortWeight, by putting the following lines with the 'public' declarations in the header file.

C++
void SetString(CString inPath) {m_strString = inPath;}
CString GetString() {return m_strString;}
void SetWeight(short inWeight) {m_shortWeight = inWeight;}
short GetWeight() {return m_shortWeight;}

Implement constructors

Here, the initial values are set for the members of the new custom class object members.

For CSubNode, implement the constructor as follows:

C++
CSubNode::CSubNode()
{
    m_shortWeight=1;
    m_strString="";
}

and for CNode...

C++
CNode::CNode()
{
    m_strString = "";
    m_iSubNodePosition = 0;

    // Create a new CSubNode object
    CSubNode *pSubNode = new CSubNode();
    try
    {
        // Set the new position mark
        m_iSubNodePosition = (m_oaSubNodes.GetSize() - 1);

    }
    catch (CMemoryException* perr)
    {}
}

Add the member variables for the document class

Using the ClassView, add the following private variables of type 'int'...

C++
m_iCurPosition
m_iSubNodePosition

Then, add a public variable of type 'CObArray', called m_oaNodes.

Make the custom classes serializable

Making the custom classes serializable involves the following steps:

  1. Add 'DECLARE_SERIAL' to the declaration file macros.
  2. Add 'IMPLEMENT_SERIAL' to the implementation file macros.
  3. Add 'Serialize' functions to the classes to deal with the CArchive object, which is where the C++ save and restore streams are handled.
  1. The DECLARE_SERIAL macro is placed at the beginning of the class declarations of the classes you want to serialize. Use the name of the class as the argument for the macro.
  2. The IMPLEMENT_SERIAL macro goes at the end of the macros at the top of the implementation files for each class you want to make serializable. This macro takes three arguments, namely, the name of the class, its base class, and a version number which should be changed if you change the data structures that you serialize when you update your application.
  3. The contents of SubNode.h should now look something like this...

    C++
    // SubNode.h: interface for the CSubNode class.
    //
    //////////////////////////////////////////////////////////////////////
    
    #if !defined(AFX_SUBNODE_H__3C84564E_BBEE_4B26_99B2_9996E51F4457
    __INCLUDED_)
    #define AFX_SUBNODE_H__3C84564E_BBEE_4B26_99B2_9996E51F4457
    __INCLUDED_
    
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    
    class CSubNode : public CObject
    {
    
        DECLARE_SERIAL (CSubNode)
    public:
        CSubNode();
        virtual ~CSubNode();
    
    private:
        short m_shortWeight;
    protected:
        CString m_strString;
    };
    
    #endif // !defined(AFX_SUBNODE_H__3C84564E_BBEE_4B26_99B2
    _9996E51F4457__INCLUDED_)

    ...and the contents of Node.h should look like this...

    C++
    // Node.h: interface for the CNode class.
    //
    //////////////////////////////////////////////////////////////////////
    
    #if !defined(AFX_NODE_H__A68ACB18_C472_487F_BACF_BADC4304203D 
    __INCLUDED_)
    #define AFX_NODE_H__A68ACB18_C472_487F_BACF_BADC4304203D
    __INCLUDED_
    
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    
    #include "SubNode.h"
    
    class CNode : public CSubNode
    {
    
        DECLARE_SERIAL (CNode)
    public:
        CObArray m_oaSubNodes;
        CNode();
        virtual ~CNode();
    
    
    private:
        int m_iSubNodePosition;
    
    };
    
    #endif // !defined(AFX_NODE_H__A68ACB18_C472_487F_BACF
    _BADC4304203D__INCLUDED_)

    The contents of the CSubNode implementation file should look like this...

    C++
    // SubNode.cpp: implementation of the CSubNode class.
    //
    //////////////////////////////////////////////////////////////////////
    
    #include "stdafx.h"
    #include "SerializeExp.h"
    #include "SubNode.h"
    
    #ifdef _DEBUG
    #undef THIS_FILE
    static char THIS_FILE[]=__FILE__;
    #define new DEBUG_NEW
    #endif
    
    
    IMPLEMENT_SERIAL (CSubNode, CObject, 1)
    //////////////////////////////////////////////////////////////////////
    // Construction/Destruction
    //////////////////////////////////////////////////////////////////////
    
    CSubNode::CSubNode()
    {
        m_shortWeight=1;
        m_strString="";
    }
    
    CSubNode::~CSubNode()
    {
    
    }

    ... and the contents of the CNode implementation file should look like this...

    C++
    // Node.cpp: implementation of the CNode class.
    //
    //////////////////////////////////////////////////////////////////////
    
    #include "stdafx.h"
    #include "SerializeExp.h"
    #include "Node.h"
    
    
    #ifdef _DEBUG
    #undef THIS_FILE
    static char THIS_FILE[]=__FILE__;
    #define new DEBUG_NEW
    #endif
    
    IMPLEMENT_SERIAL (CNode, CSubNode, 1)
    //////////////////////////////////////////////////////////////////////
    // Construction/Destruction
    //////////////////////////////////////////////////////////////////////
    
    CNode::CNode()
    {
        m_iSubNodePosition = 0;
    
        // Create a new CSubNode object
        CSubNode *pSubNode = new CSubNode();
        try
        {
            //  // Mark the document as dirty
            //  SetModifiedFlag();
            // Set the new position mark
            m_iSubNodePosition = (m_oaSubNodes.GetSize() - 1);
    
        }
        catch (CMemoryException* perr)
        {}
    }
    
    CNode::~CNode()
    {
    
    }
  4. Now it is time to create the functionality for saving and restoring data for the custom classes via 'Serialize' functions. Using the ClassView tab in the workspace, add public functions of type 'void' to both CSubNode and CNode, both with the declaration 'Serialize(CArchive &ar)'.

In this application, the part of the serialization process we are concerned with starts in the document object. The document class object's Serialize function passes a reference of the archive object to its CObArray collection of CNode objects via CObArray's own serialization function. This causes each CNode object to receive a reference to the archive object in turn, repeating the process internally - passing a reference of the archive object to its collection of CSubNode objects, causing them to become serialized in turn.

Then, for the CSubNode function, it is just a matter of streaming the object's data to or from the archive object, depending on whether or not the data is being saved or retrieved...

To kick off, edit the document class function so that an archive object reference gets passed to its collection of CNode objects...

C++
void CSerializeExpDoc::Serialize(CArchive& ar)
{
    // Pass the serialization on to the object array
    m_oaNodes.Serialize(ar);
}

Now, edit the CNode class Serialize function. This function serializes its own non-collection data before calling the Serialize function of its CSubNode object collection.

The base class for each serializable object must be given the opportunity to serialize its data before the derived class object serializes. Therefore, every 'Serialize' function must start with a call to the Serialize function of the base class. This is 'CObject' for 'CSubNode' objects and 'CSubNode' for 'CNode' objects.

C++
void CNode::Serialize(CArchive &ar)
{
    // Call the ancestor function
    CSubNode::Serialize(ar);

    int iNoSubNodes = 0;

    // Are we writing?
    if (ar.IsStoring())
    {
        // Get the number of sub nodes
        iNoSubNodes = m_oaSubNodes.GetSize();

        // Write variables in order
        ar << m_iSubNodePosition << m_strString << iNoSubNodes;
    }
    else
        // Read variables in order
        ar >> m_iSubNodePosition >> m_strString >> iNoSubNodes;

    // Serialize the array of sub nodes
    m_oaSubNodes.Serialize(ar);
}

At the end of the serialization chain, each CSubNode object performs its own serialization...

C++
void CSubNode::Serialize(CArchive &ar)
{
    // Call the ancestor function
    CObject::Serialize(ar);

    // Are we writing?
    if (ar.IsStoring())
        ar << m_shortWeight << m_strString;
    else
        ar >> m_shortWeight >> m_strString;
}

Add the document class navigation and record handler functions

When the application is running, the Document Class navigates through the data as per instructions from the view class, and is responsible for creating new records and deleting old ones.

The code which goes into the project next refers to the CNode and CSubNode classes from within the Document class. In order to get the application to compile, you need to put in 'forward declarations' for the custom classes in the document class header file. This consists of placing the following lines...

C++
class CNode;
class CSubNode;

... under the macros in the header file. The top of the header file for the document class should now look something like this...

C++
// SerializeExpDoc.h : interface of the CSerializeExpDoc class
//
////////////////////////////////////////////////////////////////////////

#if !defined(AFX_SERIALIZEEXPDOC_H__90309A3B_DF70_4EFE_A06A_D15E6DC1FB45
__INCLUDED_)
#define AFX_SERIALIZEEXPDOC_H__90309A3B_DF70_4EFE_A06A_D15E6DC1FB45
__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CNode;
class CSubNode;


class CSerializeExpDoc : public CDocument
{

You will also need to put the following #include lines in the document class implementation file...

C++
#include "Node.h"
#include "SubNode.h"

...so that the top of the implementation file looks something like this...

C++
// SerializeExpDoc.cpp : implementation of the CSerializeExpDoc class
//

#include "stdafx.h"
#include "SerializeExp.h"

#include "Node.h"
#include "SubNode.h"

#include "SerializeExpDoc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//////////////////////////////////////////////////////////////////////
// CSerializeExpDoc

Now, create the following private functions in the document class...

  • AddNewRecord() of type CNode *
  • AddNewSubNode() of type CSubNode *

These can then be implemented as follows...

C++
CNode * CSerializeExpDoc::AddNewRecord()
{
    //Create a new CNode object
    CNode *pNode = new CNode();
    CSubNode *pSubNode = new CSubNode();
    try
    {
        // Prime the new node object with a new sub node.
        pNode->m_oaSubNodes.Add(pSubNode);

        // Add the new node to the object array
        m_oaNodes.Add(pNode);

        // Mark the document as dirty
        SetModifiedFlag();
        // Set the new position mark
        m_iCurPosition = (m_oaNodes.GetSize()-1);
    }

    // Did we run into a memory exception?
    catch (CMemoryException* perr)
    {
        // Display a message for the user, giving
        // them the bad news
        AfxMessageBox("Out of Memory",MB_ICONSTOP | MB_OK);
        // Did we create a node object?
        if (pNode)
        {
            // Delete it
            delete pNode;
            pNode = NULL;
        }
        // Did we create a sub node object?
        if (pSubNode)
        {
            // Delete it
            delete pSubNode;
            pSubNode = NULL;
        }

        // Delete the exception object
        perr->Delete ();
    }
    return pNode;

}

and:

C++
CSubNode * CSerializeExpDoc::AddNewSubNode()
{
    // Get pointer to current node
    CNode * curNode = (CNode *) m_oaNodes[m_iCurPosition];
    //Create a new CSubNode object
    CSubNode *pSubNode = new CSubNode();
    try
    {
        // Add a new subnode to the subnode array
        curNode->m_oaSubNodes.Add(pSubNode);
        // Mark the document as dirty
        SetModifiedFlag();
        // Set the new position mark
        m_iSubNodePosition = (curNode->m_oaSubNodes.GetSize()-1);
    }
    // Did we run into a memory exception?
    catch (CMemoryException* perr)
    {
        // Display a message for the user, giving
        // them the bad news
        AfxMessageBox("Out of Memory",MB_ICONSTOP | MB_OK);
        // Did we create a line object?
        if (pSubNode)
        {
            // Delete it
            delete pSubNode;
            pSubNode = NULL;
        }
        // Delete the exception object
        perr->Delete ();
    }
    return pSubNode;
}

To save time, I am going to simply copy all of the functions here. You can add them to your project using the ClassView. They are all public. The type and declaration for each function can be gleaned from its implementation, as written out below. For instance, for the function 'GetCurRecord', the type is 'CNode *' and the declaration is 'GetCurRecord()'.

C++
CNode* CSerializeExpDoc::GetCurRecord()
{
    // Are we editing a valid record number?
    if (m_iCurPosition >=0)
        // Yes, return the current record
        return (CNode*)m_oaNodes[m_iCurPosition];
    else
        // No, return NULL
        return NULL;
}

CNode* CSerializeExpDoc::GetFirstRecord()
{
    // Are there any records in the array?
    if (m_oaNodes.GetSize() > 0)
    {
        // Yes, move to position 0
        m_iCurPosition =0;
        m_iSubNodePosition = 0;
        GetFirstSubNode();
        // Return the record in position 0
        return (CNode*)m_oaNodes[0];
    }
    else
        // No records, return NULL
        return NULL;
}

CNode* CSerializeExpDoc::GetNextRecord()
{
    m_iSubNodePosition = 0;
    // After incrementing the position marker, are
    // we past the end of the array?
    if (++m_iCurPosition < m_oaNodes.GetSize()){
        // No, return the record at the new current position
        return (CNode*)m_oaNodes[m_iCurPosition];
    }
    else
        // Yes, add a new record
        return AddNewRecord();
}

CNode* CSerializeExpDoc::GetPrevRecord()
{
    // Are there any records in the array?
    if (m_oaNodes.GetSize() > 0)
    {
        m_iSubNodePosition = 0;
        // Once we decrement the current position,
        // are we below position 0?
        if (--m_iCurPosition < 0){
            // If so, set the record position to 0
            m_iCurPosition = 0;
        }
        // Return the record at the new current position
        return (CNode*)m_oaNodes[m_iCurPosition];
    }
    else
        // No records, return NULL
        return NULL;
}

CNode * CSerializeExpDoc::GetLastRecord()
{
    // Are there any records in the array?
    if (m_oaNodes.GetSize() > 0)
    {
        m_iSubNodePosition = 0;
        // Move to the last position in the array
        m_iCurPosition = (m_oaNodes.GetSize() - 1);
        m_iSubNodePosition = 0;
        // Return the record in this position
        return (CNode*)m_oaNodes[m_iCurPosition];
    }
    else
        // No records, return NULL
        return NULL;
}

int CSerializeExpDoc::GetCurRecordNbr()
{
    // Return the current position
    return (m_iCurPosition +1);
}

CSubNode* CSerializeExpDoc::GetFirstSubNode()
{
    // Get pointer to current node
    CNode * curNode = (CNode *) m_oaNodes[m_iCurPosition];
    // Does the node have any sub nodes?
    if (curNode->m_oaSubNodes.GetSize() > 0)
    {
        // Yes, move to position 0
        m_iSubNodePosition =0;
        // Return the sub node in position 0
        return (CSubNode*)curNode->m_oaSubNodes[0];
    }
    else
        // No records, return NULL
        return NULL;
}

CSubNode* CSerializeExpDoc::GetNextSubNode()
{
    // Get pointer to current node
    CNode * curNode = (CNode *) m_oaNodes[m_iCurPosition];
    // After incrementing the position marker, are
    // we past the end of the array?
    if (++m_iSubNodePosition < curNode->m_oaSubNodes.GetSize())
        // No, return the record at the new current position
        return (CSubNode*)curNode->m_oaSubNodes[m_iSubNodePosition];
    else
        // Yes, add a new record
        return AddNewSubNode();
}

CSubNode* CSerializeExpDoc::GetPrevSubNode()
{
    // Get pointer to current node
    CNode * curNode = (CNode *) m_oaNodes[m_iCurPosition];
    // Are there any records in the subnode array?
    if (curNode->m_oaSubNodes.GetSize() > 0)
    {
        // Once we decrement the current position,
        // are we below position 0?
        if (--m_iSubNodePosition < 0)
            // If so, set the record position to 0
            m_iSubNodePosition = 0;
        // Return the subnode at the new current position
        return (CSubNode*)curNode->m_oaSubNodes[m_iSubNodePosition];
    }
    else
        // No records, return NULL
        return NULL;
}

int CSerializeExpDoc::GetTotalSubNodes()
{
    // Get pointer to current node
    CNode * curNode = (CNode *) m_oaNodes[m_iCurPosition];
    return curNode->m_oaSubNodes.GetSize();
}

int CSerializeExpDoc::GetCurSubNodeNbr()
{
    return (m_iSubNodePosition + 1);

}

Cleaning up the document

In C++, you need to make sure that all of the objects you create are properly deleted when you don't need them any more, or your application may cause a problem known as 'memory leak'. The following function is an event handler which is called when the document is closed or a new document is opened. It is a pair of nested loops, one for looping through the nodes and deleting them, and an inner loop for looping through the sub nodes contained within each node object and deleting them. You need to use the Class Wizard for creating this function. Click on the 'Message Maps' tab. Make sure the document class' class name is selected in the drop-down list, then double click on 'DeleteContents' in the 'Messages' list to generate the function.

Fill out the body of the function with the following code...

C++
void CSerializeExpDoc::DeleteContents()
{
    // TODO: Add your specialized code here and/or call the base class


    ////
    // My code starts
    // - based on LISTING 13.19 of 'Learn vc++6 in 21 Days'

    // Get the number of nodes in the node array
    int noCount  = m_oaNodes.GetSize();
    int noPos;

    // Are there any nodes in the node array?
    if (noCount)
    {
        // Loop through the nodes
        for (noPos = 0; noPos < noCount; noPos++){

            // Loop through the sub nodes deleting them
            // one by one.
            CNode * pCNode = (CNode *)m_oaNodes[noPos];
            int snCount = pCNode->m_oaSubNodes.GetSize();
            int snPos;

            // Are there any sub nodes in the subnode array?
            if (snCount)
            {
                for (snPos = 0; snPos < snCount; snPos++)
                    delete pCNode->m_oaSubNodes[snPos];

                // Reset the sub node array
                pCNode->m_oaSubNodes.RemoveAll();
            }
            // delete the node object
            delete m_oaNodes[noPos];
        }

        // Reset the node array
        m_oaNodes.RemoveAll();
    }

    // My code ends
    ////

    CDocument::DeleteContents();
}

Before leaving the document class implementation file, scroll up to the top and add a '#include' line for the view class so that the top of the file now looks something like this...

C++
// SerializeExpDoc.cpp : implementation of the CSerializeExpDoc class
//

#include "stdafx.h"
#include "SerializeExp.h"

#include "Node.h"
#include "SubNode.h"

#include "SerializeExpDoc.h"
#include "SerializeExpView.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////
// CSerializeExpDoc

Displaying a new record set

At this point, create a private function in the View class of type void, declared as PopulateView(). We'll put in the code later.

Create two new private member variables for the View class. One of type CNode*, called m_nCurNode, and another of type CSubNode*, called m_snCurSubNode.

Now, make another public function in the View class of type void, declared as ShowFirst(). This function is executed when a document is opened.

Put the following code in it...

C++
void CSerializeExpView::ShowFirst()
{
    // Get a pointer to the current document
    CSerializeExpDoc* pDoc = GetDocument();
    if (pDoc)
    {
        // Get the first record from the document
        m_nCurNode = pDoc->GetFirstRecord();
        m_snCurSubNode = pDoc->GetFirstSubNode();
        if (m_nCurNode)
        {
            // Display the current record
            PopulateView();
        }
    }
}

What to do when a new document is opened

Instead of the document class calling ShowFirst directly, it calls the NewDataSet function in the view class. You need to create this function. It is another public function of type void.

Implement NewDataSet as follows...

C++
void CSerializeExpView::NewDataSet()
{
    // Display the first record in the set
    ShowFirst();
}

The OnNewDocument function creates the new document, creates fresh, empty records ready for new information, and clears the current view. The function should already exist in the document class. You need to add code to it as follows...

C++
BOOL CSerializeExpDoc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
        return FALSE;

    // TODO: add reinitialization code here
    // (SDI documents will reuse this document)

    ////
    //my code starts

    if (!AddNewRecord())
        return FALSE;

    POSITION pos = GetFirstViewPosition();
    CSerializeExpView* pView = (CSerializeExpView*) GetNextView(pos);

    if (pView)
        pView->NewDataSet();

    //my code ends
    ////

    return TRUE;
}

Displaying the current record

This function displays data from the document in the view. Go back to the function CSerializeExpDoc::PopulateView you created earlier, and fill it with the following code...

C++
void CSerializeExpView::PopulateView()
{
    // Get a pointer to the current document
    CSerializeExpDoc* pDoc = GetDocument();
    //
    if (pDoc)
    {
        // Display the current node position in the set
        m_sNPosition.Format("Node information\n\nRecord %d of %d",
            pDoc->GetCurRecordNbr(),pDoc->GetTotalRecords());

        m_sSPosition.Format("Sub Node information\n\nRecord %d of %d",
            pDoc->GetCurSubNodeNbr(),pDoc->GetTotalSubNodes());

        // Do we have a valid record object?
        if (m_nCurNode)
        {
            // Yes, get all of the record values
            m_sNod = m_nCurNode->GetString();

            if (m_snCurSubNode){
                if (pDoc->GetTotalSubNodes()>0){

                        m_sSubNod = m_snCurSubNode->GetString();
                        m_shortSubNodeWeight = 
                m_snCurSubNode->GetWeight();
                }
            }
        }
    }
    // Update the display
    UpdateData (FALSE);
}

Before the project will compile, you need to add:

  1. the GetTotalRecords() function to the document class, and
  2. #include "Node.h" to the view class implementation file

Add the function GetTotalRecords() to the document class. It is of type int and is public. Implement it as follows...

C++
int CSerializeExpDoc::GetTotalRecords()
{
    // Return the array count
    return m_oaNodes.GetSize();
}

Add the line #include "Node.h" near the top of the view class implementation file so that the include section now looks like...

C++
// SerializeExpView.cpp : 
//    implementation of the CSerializeExpView class
//

#include "stdafx.h"
#include "SerializeExp.h"

#include "Node.h"

#include "SerializeExpDoc.h"
#include "SerializeExpView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

Navigating the record set

The application needs to have functions added which respond to user input in the View class. These are called 'event handlers', and are most easily created using the ClassWizard. Open the ClassWizard, click on the Message Maps tab, make sure the View class is selected in the 'Class name:' drop down list, and then for each of the Object IDs IDC_BNODLEFT, IDC_BNODRIGHT, IDC_BSUBNODLEFT, and IDC_BSUBNODRIGHT, double click on the 'BN_CLICKED' message and change the suggested member function name to the following in turn...

  • OnBNodeLeft
  • OnBNodeRight
  • OnBSubNodeLeft
  • OnBSubNodeRight

Now implement the functions as follows...

C++
void CSerializeExpView::OnBNodeLeft()
{
    // TODO: Add your control notification handler code here

    // Get a pointer to the current document
    CSerializeExpDoc * pDoc = GetDocument();
    if (pDoc)
    {
        // Get the previous record from the document
        m_nCurNode = pDoc->GetPrevRecord();

        pDoc->GetFirstSubNode();
        OnBSubNodeLeft();

        if (m_nCurNode)
        {
            // Display the current record
            PopulateView();
        }
    }
}

void CSerializeExpView::OnBNodeRight()
{
    // TODO: Add your control notification handler code here

    // Get a pointer to the current document
    CSerializeExpDoc * pDoc = GetDocument();
    if (pDoc)
    {
        // Get the next record from the document
        m_nCurNode = pDoc->GetNextRecord();

        pDoc->GetFirstSubNode();
        OnBSubNodeLeft();


        if (m_nCurNode)
        {
            // Display the current record
            PopulateView();
        }
    }
}

void CSerializeExpView::OnBSubNodeLeft()
{
    // TODO: Add your control notification handler code here
    // Get a pointer to the current document
    CSerializeExpDoc * pDoc = GetDocument();
    if (pDoc)
    {
        // Get the previous record from the document
        m_snCurSubNode = pDoc->GetPrevSubNode();
        if (m_snCurSubNode)
        {
            // Display the current record
            PopulateView();
        }
    }
}

void CSerializeExpView::OnBSubNodeRight()
{
    // TODO: Add your control notification handler code here

    // Get a pointer to the current document
    CSerializeExpDoc * pDoc = GetDocument();
    if (pDoc)
    {
        // Get the next record from the document
        m_snCurSubNode = pDoc->GetNextSubNode();
        if (m_snCurSubNode)
        {
            // Display the current record
            PopulateView();
        }
    }
}

What to do when opening a saved document

You need to create an event handler function for the OnOpenDocument message in the Document class. Implement this function as follows...

C++
BOOL CSerializeExpDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
    if (!CDocument::OnOpenDocument(lpszPathName))
        return FALSE;

    // TODO: Add your specialized creation code here


    ////
    //my code starts

    POSITION pos = GetFirstViewPosition();
    CSerializeExpView* pView = 
        (CSerializeExpView*) GetNextView(pos);

    if (pView)
        pView->NewDataSet();

    //my code ends
    ////

    return TRUE;
}

Saving edits and changes

All that is left to do now is to place event handlers for the edit box controls. Using the ClassWizard for the View class, select the corresponding Object IDs for each of the edit boxes, then double click on the EN_CHANGE message for each of the edit boxes. The ClassWizard then suggests a function name for each event handler. You can change this into something more meaningful if need be.

For IDC_ENOD, the ClassWizard suggests OnChangeEnod as an event handler name. I suggest using OnChangeENode instead. For IDC_ESNWEIGHT, instead of using OnChangeWsnweight, I suggest using OnChangeWSNWeight, and for IDC_ESUBNOD, instead of using OnChangeEsubnod, I suggest using OnChangeESubNode.

Implement these functions as follows...

C++
void CSerializeExpView::OnChangeENode()
{
    // TODO: If this is a RICHEDIT control, the control will not
    // send this notification unless you override the 
    //CFormView::OnInitDialog()
    // function and call CRichEditCtrl().SetEventMask()
    // with the ENM_CHANGE flag ORed into the mask.

    // TODO: Add your control notification handler code here

    // Sync the data in the form with the variables
    UpdateData(TRUE);
    // If we have a valid node object, pass the data changes to it
    if (m_nCurNode)
        m_nCurNode->SetString(m_sNod);
}

void CSerializeExpView::OnChangeESNWeight()
{
    // TODO: If this is a RICHEDIT control, the control will not
    // send this notification unless you override the 
    //CFormView::OnInitDialog()
    // function and call CRichEditCtrl().SetEventMask()
    // with the ENM_CHANGE flag ORed into the mask.

    // TODO: Add your control notification handler code here

    // Sync the data in the form with the variables
    UpdateData(TRUE);
    // If we have a valid node object, pass the data changes to it
    if (m_snCurSubNode)
        m_snCurSubNode->SetWeight(m_shortSubNodeWeight);
}

void CSerializeExpView::OnChangeESubNode()
{
    // TODO: If this is a RICHEDIT control, the control will not
    // send this notification unless you override the 
    //CFormView::OnInitDialog()
    // function and call CRichEditCtrl().SetEventMask()
    // with the ENM_CHANGE flag ORed into the mask.

    // TODO: Add your control notification handler code here

    // Sync the data in the form with the variables
    UpdateData(TRUE);
    // If we have a valid node object, pass the data changes to it
    if (m_snCurSubNode)
        m_snCurSubNode->SetString(m_sSubNod);
}

Memory management note...

For better memory management and efficiency, use CObArray's SetSize function. This can prevent fragmentation and unnecessary copying.

References

  1. Much of this article is inspired by and based on 'Teach Yourself Visual C++ 6 in 21 Days', Chapter 13: 'Saving and Restoring Work--File Access' by Davis Chapman.
  2. I got plenty more inspiration and pleasure reading A serialization primer - Part 3, by Ravi Bhavnani.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom

Comments and Discussions

 
GeneralA more elegant solution... Pin
Ben Aldhouse3-Aug-08 0:27
Ben Aldhouse3-Aug-08 0:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.