65.9K
CodeProject 正在变化。 阅读更多。
Home

为CMenu添加序列化支持

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (6投票s)

2001年12月5日

2分钟阅读

viewsIcon

73716

downloadIcon

939

CMenu 类在操作菜单时非常有用,但不幸的是它没有实现序列化。CSerializableMenu 是 CMenu 的一个子类,它提供了序列化支持。

引言

`CMenu` 类在操作菜单时非常有用,但不幸的是它在序列化方面并没有提供太多帮助。

另一方面,WIN32 SDK 以结构体 `MENUITEMTEMPLATEHEADER``MENUITEMTEMPLATE`,以及函数 `LoadMenuIndirect()` 的形式提供了帮助。但是这些结构体相当复杂,使用起来非常麻烦。

`CSerializableMenu` 是 `CMenu` 的一个子类,它提供了序列化支持,通过以通常的 MFC 方式封装 WIN32 实现的所有麻烦细节(即,直接通过调用重写的 `public` 方法 `Serialize()` 或间接通过调用重载的运算符 `>>` 和 `<<`)。

用法

`CSerializableMenu` 类可以用作代码中 `CMenu` 类的直接替代品。要执行序列化,您可以直接调用方法 `Serialize()` 或使用重载的运算符 `>>` 和 `<<`。

如果您不想替换代码中的 `CMenu` 类,您仍然可以使用 `CSerializableMenu` 类来实现其序列化支持,方法如下

 //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();

类内部剖析

`CSerializableMenu` 通过使用成员函数 `LoadMenuIndirect()` 以及 WIN32 结构体 `MENUITEMTEMPLATEHEADER``MENUITEMTEMPLATE` 来实现序列化。这两个结构体的定义如下

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; 

`MENUITEMTEMPLATEHEADER` 结构体定义了菜单模板的标头。一个完整的菜单模板由一个标头和一个或多个菜单项(即,`MENUITEMTEMPLATE`)组成。

Sample menu

例如,上面显示的弹出菜单将被序列化为以下结构(其中 MITH 代表 `MenuItemTemplateHeader`,MIT 代表 `MenuItemTemplate`。另请注意,分隔符占用一个 MIT)

HEADER (MITH) 工具栏 (MIT) 地址 (MIT) 链接 (MIT) 添加快速搜索... (MIT) 桌面 (MIT) 快速启动 (MIT) 搜索 (MIT) -分隔符- (MIT) 新工具栏... (MIT) -分隔符- (MIT) 调整日期/时间 (MIT) 级联窗口 (MIT) 水平平铺窗口 (MIT) 垂直平铺窗口 (MIT) -分隔符- (MIT) 最小化所有窗口 (MIT) -分隔符- (MIT) 任务管理器... (MIT) -分隔符- (MIT) 属性 (MIT)

与 MFC 中的其他可序列化类一样,`CSerializableMenu` 公开了公共方法 `Serialize()` 和重载的运算符 `>>` 和 `<<`。(重载运算符 `>>` 和 `<<` 由宏 `DECLARE_SERIAL` 和 `IMPLEMENT_SERIAL` 定义。)

类头文件

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:
};

类源文件

#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
    }
}

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.