Add Serialization Support to CMenu






4.69/5 (6 votes)
Dec 5, 2001
2 min read

73053

917
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
).
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.