Click here to Skip to main content
15,879,326 members
Articles / Desktop Programming / MFC

Add Serialization Support to CMenu

Rate me:
Please Sign up or sign in to vote.
4.69/5 (6 votes)
4 Dec 20012 min read 72.5K   913   22   4
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.


Written By
Web Developer
Singapore Singapore
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionBut why we need to serialize CMenu? Pin
flyingxu15-Jun-06 15:30
flyingxu15-Jun-06 15:30 
AnswerRe: But why we need to serialize CMenu? Pin
YoSilver30-Jan-07 8:02
YoSilver30-Jan-07 8:02 
GeneralGod Blesses you Pin
5-Dec-01 23:39
suss5-Dec-01 23:39 
GeneralRe: God Blesses you Pin
tim63515-Sep-05 1:27
tim63515-Sep-05 1:27 
It works good,
but for some of my dynamically built menus it doesn't fully work. The menu's are built very basically so it's strange the fillMenuTemplate() doesn't work.

Anyway, good job.

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.