Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Add serialization support to CMenu

0.00/5 (No votes)
4 Dec 2001 1  
The CMenu class is a great help when it comes to manipulating menus, but unfortunately it doesn't implement serialization. CSerializableMenu is a subclass of CMenu that provides serialization support.

Introduction

The CMenu class is a great help when it comes to manipulating menus, but unfortunately it doesn't provide much help where serialization is concerned.

The WIN32 SDK, on the other hand, offers help in the form of the structures MENUITEMTEMPLATEHEADER and MENUITEMTEMPLATE, and the function LoadMenuIndirect(). But these structures are fairly complex and are really cumbersome to use.

CSerializableMenu is a subclass of CMenu that provides serialization support, by encapsulating all the nasty details of the WIN32 implementation, in the usual MFC manner. (i.e. directly by calling the overridden public method Serialize() or indirectly by calling the overloaded operators >> and <<.)

Usage

The CSerializableMenu class can be used as a direct replacement for the CMenu class in your code. To perform serialization, you may call the method Serialize() directly or use the overloaded operators >> and <<.

If you do not wish to replace the CMenu class in your code, you can still use the CSerializableMenu class for its serialization support by doing the following:

 //assuming that you already have a CMenu object called m_menu

 //(or even a raw handle to a HMENU called m_hMenu)

 //and a CArchive object called m_archive

     
 //to store the menu

 CSerializableMenu smenu;
 smenu.Attach(m_menu.GetSafeHmenu()); //or smenu.Attach(m_hMenu);

 smenu.Serialize(m_archive); // or m_archive << smenu;

 smenu.Detach();

 //to restore the menu

 CSerializableMenu smenu;
 smenu.Serialize(m_archive); // or m_archive >> smenu;

 m_menu.Attach(smenu.Detach()); //or m_hMenu = smenu.Detach();

Class Innards Dissected

CSerializableMenu implements serialization through the use of the member function LoadMenuIndirect(), together with the WIN32 structures MENUITEMTEMPLATEHEADER and MENUITEMTEMPLATE. The two structures are defined as follows:

typedef struct {
  WORD versionNumber; //must be 0

  WORD offset;  //byte offset of the first MENUITEMTEMPLATE after this structure

} MENUITEMTEMPLATEHEADER, *PMENUITEMTEMPLATEHEADER; 

typedef struct { 
  WORD mtOption; // OR flags controlling the appearance of the menu item

  WORD mtID; //menu item identifier of a command item

  WCHAR mtString[1]; //null-terminated string for the menu item

} MENUITEMTEMPLATE, *PMENUITEMTEMPLATE; 

The MENUITEMTEMPLATEHEADER structure defines the header for a menu template. A complete menu template consists of a header and one or more menu items (i.e. MENUITEMTEMPLATE).

Sample menu

For example, the popup menu shown above will be serialized into the following structure (where MITH stands for MenuItemTemplateHeader and MIT stands for MenuItemTemplate. Also note that a separator takes up one MIT):

HEADER (MITH) Toolbars (MIT) Address (MIT) Links (MIT) Add Quick Search... (MIT) Desktop (MIT) Quick Launch (MIT) Search (MIT) -SEPARATOR- (MIT) New Toolbar... (MIT) -SEPARATOR- (MIT) Adjust Date/Time (MIT) Cascade Windows (MIT) Tile Windows Horizontally (MIT) Tile Windows Vertically (MIT) -SEPARATOR- (MIT) Minimize All Windows (MIT) -SEPARATOR- (MIT) Task Manager... (MIT) -SEPARATOR- (MIT) Properties (MIT)

Like other serializable classes in the MFC, CSerializableMenu exposes the public method Serialize() and the overloaded operators >> and <<. (Overloaded operators >> and << are defined by the macros DECLARE_SERIAL and IMPLEMENT_SERIAL.)

Class Header File

class CSerializableMenu : public CMenu  
{
public:
    DECLARE_SERIAL(CSerializableMenu)

    CSerializableMenu() {};
    virtual ~CSerializableMenu() {};

//operations

public:

//internal implementations

protected:
    //helper functions for serialization support

    LPBYTE GetMenuTemplate(DWORD* dwLen);
    void FillMenuTemplate(CMemFile* pFile, CMenu* pMenu);

//overrides

public:
    void Serialize(CArchive &ar);


//attributes

protected:
};

Class Source File

#include "stdafx.h"

#include <afxcoll.h>

#include <afxpriv.h>

#include "SerializableMenu.h"


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

IMPLEMENT_SERIAL( CSerializableMenu, CMenu, 1)

//////////////////////////////////////////////////////////////////////

// Internal helper mtds

//////////////////////////////////////////////////////////////////////


/**
 * This method will create the memory structure that represents
 * the menu wrapped by this class.
 * @param dwLen [in/out] pointer to a DWORD that will receive the size
 *                       of the structure allocated.
 * @return a BYTE array containing the created structure.
 **/
LPBYTE CSerializableMenu::GetMenuTemplate(DWORD* dwLen)
{
    //since we have no idea whats the total size of

    //the structure will be, lets use a memory file instead

    CMemFile memFile;
    //create and initialize the header structure

    MENUITEMTEMPLATEHEADER mitHeader;
    mitHeader.versionNumber = 0; //as required by SDK

    mitHeader.offset = 0; //implies that the menu items come next

    //write it to our memory file

    memFile.Write(&mitHeader, sizeof(MENUITEMTEMPLATEHEADER));
    //lets get a helper function to fill up the menu items

    FillMenuTemplate(&memFile, this);
    //update the length variable

    *dwLen = memFile.GetLength();
    //and return the BYTE array

    return memFile.Detach();
}

/**
 * This is a recursive method that will populate the given memory file
 * with the menu items (including all submenus).
 * @param pFile [in] pointer to the memory file
 * @param pMenu [in] pointer to the menu
 **/
void CSerializableMenu::FillMenuTemplate(CMemFile* pFile, CMenu* pMenu)
{
    USES_CONVERSION; //need this for the conversion macro to work


    _ASSERTE(pMenu != NULL);

    CString tmpStr;
    LPCWSTR wszTmp = NULL;
    WORD mt;
    int nSize = pMenu->GetMenuItemCount();
    //loop thru all the menu items in this level

    for (int i=0; i<nSize; i++)
    {
        //first, get the menu state and store it

        mt = (WORD) pMenu->GetMenuState(i, MF_BYPOSITION);
        if (mt & MF_POPUP) 
            //need to mask out the high order byte if its a popup

            //cuz its contains the number of items in the popup

            //which we are not interested in

            mt &= ~0xFF00;
        if (i == nSize-1) //is last item, so add the flag MF_END

            mt |= MF_END;
        pFile->Write(&mt, sizeof(WORD));
        //if its NOT a popup, we should store the command ID as well

        if (!(mt & MF_POPUP))
        {
            WORD cmdID = (WORD) pMenu->GetMenuItemID(i);
            pFile->Write(&cmdID, sizeof(WORD));
        }
        //now, lets get the menu string and store it

        pMenu->GetMenuString(i, tmpStr, MF_BYPOSITION);
        wszTmp = T2CW(tmpStr);
        //+1 to include the null terminator

        pFile->Write(wszTmp, (tmpStr.GetLength()+1)*sizeof(WCHAR));

        if (mt & MF_POPUP) //is a popup, so add in the submenus

            FillMenuTemplate(pFile, pMenu->GetSubMenu(i));
    }
}

//////////////////////////////////////////////////////////////////////

// overrides

//////////////////////////////////////////////////////////////////////

/**
 * This is THE method that does serialization.
 * @param ar [in/out] a reference to the CArchive object that will
 *                    store/load the menu
 **/
void CSerializableMenu::Serialize(CArchive &ar)
{
    //get the base class to do its thing first

    CMenu::Serialize(ar);

    if (ar.IsLoading()) //loading the menu from storage

    {
        //destroy (any) old menu first

        DestroyMenu();

        DWORD dwSize;
        //first, lets read the size of the structure

        ar.Read(&dwSize, sizeof(DWORD));
        //next, we allocate a space in memory that is large enough

        //to hold the structure

        LPBYTE pBuf = new BYTE [dwSize];
        //lets read the structure proper

        ar.Read(pBuf, dwSize);
        //get the member function to create/load the menu

        LoadMenuIndirect(pBuf);
        //cleanup

        delete [] pBuf;
    } 
    else //storing the menu into storage

    { 
        DWORD dwSize;
        //lets get our helper function to create the BYTE array for us

        LPBYTE pBuf = GetMenuTemplate(&dwSize);
        //first, write the size of the buffer into the archive

        ar.Write(&dwSize, sizeof(DWORD)); 
        //next, write the structure into the archive

        ar.Write(pBuf, dwSize);
        //cleanup

        free(pBuf); //CMemFile uses malloc so we have to use free here

    }
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here