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:
CSerializableMenu smenu;
smenu.Attach(m_menu.GetSafeHmenu());
smenu.Serialize(m_archive);
smenu.Detach();
CSerializableMenu smenu;
smenu.Serialize(m_archive);
m_menu.Attach(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;
WORD offset;
} MENUITEMTEMPLATEHEADER, *PMENUITEMTEMPLATEHEADER;
typedef struct {
WORD mtOption;
WORD mtID;
WCHAR mtString[1];
} 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() {};
public:
protected:
LPBYTE GetMenuTemplate(DWORD* dwLen);
void FillMenuTemplate(CMemFile* pFile, CMenu* pMenu);
public:
void Serialize(CArchive &ar);
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)
LPBYTE CSerializableMenu::GetMenuTemplate(DWORD* dwLen)
{
CMemFile memFile;
MENUITEMTEMPLATEHEADER mitHeader;
mitHeader.versionNumber = 0;
mitHeader.offset = 0;
memFile.Write(&mitHeader, sizeof(MENUITEMTEMPLATEHEADER));
FillMenuTemplate(&memFile, this);
*dwLen = memFile.GetLength();
return memFile.Detach();
}
void CSerializableMenu::FillMenuTemplate(CMemFile* pFile, CMenu* pMenu)
{
USES_CONVERSION;
_ASSERTE(pMenu != NULL);
CString tmpStr;
LPCWSTR wszTmp = NULL;
WORD mt;
int nSize = pMenu->GetMenuItemCount();
for (int i=0; i<nSize; i++)
{
mt = (WORD) pMenu->GetMenuState(i, MF_BYPOSITION);
if (mt & MF_POPUP)
mt &= ~0xFF00;
if (i == nSize-1)
mt |= MF_END;
pFile->Write(&mt, sizeof(WORD));
if (!(mt & MF_POPUP))
{
WORD cmdID = (WORD) pMenu->GetMenuItemID(i);
pFile->Write(&cmdID, sizeof(WORD));
}
pMenu->GetMenuString(i, tmpStr, MF_BYPOSITION);
wszTmp = T2CW(tmpStr);
pFile->Write(wszTmp, (tmpStr.GetLength()+1)*sizeof(WCHAR));
if (mt & MF_POPUP)
FillMenuTemplate(pFile, pMenu->GetSubMenu(i));
}
}
void CSerializableMenu::Serialize(CArchive &ar)
{
CMenu::Serialize(ar);
if (ar.IsLoading())
{
DestroyMenu();
DWORD dwSize;
ar.Read(&dwSize, sizeof(DWORD));
LPBYTE pBuf = new BYTE [dwSize];
ar.Read(pBuf, dwSize);
LoadMenuIndirect(pBuf);
delete [] pBuf;
}
else
{
DWORD dwSize;
LPBYTE pBuf = GetMenuTemplate(&dwSize);
ar.Write(&dwSize, sizeof(DWORD));
ar.Write(pBuf, dwSize);
free(pBuf);
}
}