Topic : Menus
Author : Unknown
Page : << Previous 4  Next >>
Go to page :


CMenu, after calling function CMenu::LoadMenu(...), we can obtain the handle of the menu resource by calling function CMenu::GetSafeHmenu().

For example, in sample 2\Menu, after menu resource IDR_MENU_POUP is loaded into the memory, we could obtain the handle of its first sub-menu and store it to an HMENU type variable as follows:

CMenu menu;

CMenu *ptrMenu;

HMENU hMenu;

menu.LoadMenu(IDR_MENU_POPUP);

ptrMenu=menu.GetSubMenu(0);

hMenu=ptrMenu->GetSafeHmenu();


To remove a menu item, we need to use another member function of CMenu:

BOOL CMenu::RemoveMenu(UINT nPosition, UINT nFlags);

The meanings of nPosition and nFlags parameters are similar to those of function CMenu:: InsertMenu(...).

There is another similar function: CMenu::DeleteMenu(...), which can also remove a menu item or sub- menu. However, if we use this function to delete a sub-menu, the menu resource will be released from the memory. In this case, if we wand to use the sub-menu again, we need to reload the menu resource.

Sample Implementation

Sample 3\Menu demonstrates how to add and delete menu items dynamically. It is a standard SDI application generated by Application Wizard, with all the default settings. In this sample, there are two commands Edit | Insert Dynamic Menu and Edit | Delete Dynamic Menu. If we execute the first command, a new sub-menu will be added between File and Edit sub-menus. We can use the second command to remove this dynamically added sub-menu.

The first step is to add two menu items to IDR_MAINFRAME menu resource. In the sample, two commands are added to Edit sub-menu, their description text are "Insert Dynamic Menu" and "Delete Dynamic Menu" respectively, and their command IDs are ID_EDIT_INSERTDYNAMICMENU and ID_EDIT_DELETEDYNAMICMENU. Both of them have WM_COMMAND and UPDATE_COMMAND_UI message handlers in class CMenuDoc, whose function names are OnEditInsertDynamicMenu, OnUpdateEditInsertDynamicMenu, OnEditDeleteDynamicMenu and OnUpdateEditDeleteDynamicMenu.

Because we want to disable command ID_EDIT_DELETEDYNAMICMENU and enable command ID_EDIT_INSERTDYNAMICMENU before the sub-menu is inserted, and reverse this after the menu is inserted, another Boolean type variable m_bSubMenuOn is declared in class CMenuDoc, which will be used to indicate the state of the inserted menu. It is initialized to FALSE in the constructor.

Preparing the menu resource that will be used to implement dynamic sub-menu is the same with what we did in the previous sample. Here a resource IDR_MENU_POPUP is added to the application, whose content is the same with the resource created in sample 2\Menu.

In this case, we could not use a local variable to load the menu, because once the menu is inserted, it may exist for a while before the user removes it. If we still use a local variable, it will go out of scope after the messagae hander returns. In the sample, a CMenu type variable is declared in class CMenuDoc, which is used to load the menu resource in the constructor.

The following shows the modified class CMenuDoc:

class CMenuDoc : public CDocument
{

protected: // create from serialization only

CMenu m_menuSub;

BOOL m_bSubMenuOn;

......

protected:

//{{AFX_MSG(CMenuDoc)

afx_msg void OnEditInsertDynamicMenu();

afx_msg void OnUpdateEditInsertDynamicMenu(CCmdUI* pCmdUI);

afx_msg void OnEditDeleteDynamicMenu();

afx_msg void OnUpdateEditDeleteDynamicMenu(CCmdUI* pCmdUI);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};


The following is the constructor within which the menu resource is loaded and m_bSubMenuOn is initialized:

CMenuDoc::CMenuDoc()
{

m_menuSub.LoadMenu(IDR_MENU_POPUP);

m_bSubMenuOn=FALSE;

}


The following shows two UPDATE_COMMAND_UI message handlers where two menu commands are enabled or disabled:

void CMenuDoc::OnUpdateEditInsertDynamicMenu(CCmdUI* pCmdUI)
{

pCmdUI->Enable(m_bSubMenuOn == FALSE);

}

void CMenuDoc::OnUpdateEditDeleteDynamicMenu(CCmdUI* pCmdUI)

{

pCmdUI->Enable(m_bSubMenuOn == TRUE);

}


At last, we must implement two WM_COMMAND message handlers. First, we need to find a way of accessing mainframe menu IDR_MAINFRAME of the application. In MFC, a menu associated with a window can be accessed by calling function CWnd::GetMenu(), which will return a CMenu type pointer. Once we get this pointer, we can use it to access any of its sub-menus.

The mainframe window pointer can be obtained by calling function AfxGetMainWnd() anywhere in the program. An alternate way is to call AfxGetApp() to obtain a CWinApp type pointer, then access its public member m_pMainWnd. We could use CMenu type pointer to insert or remove a sub-menu dynamically.

The following shows two message handlers that are used to insert or remove the sub-menu:

void CMenuDoc::OnEditInsertDynamicMenu()
{

CMenu *pTopMenu=AfxGetMainWnd()->GetMenu();

CMenu *ptrMenu=m_menuSub.GetSubMenu(0);

pTopMenu->InsertMenu

(

1, MF_BYPOSITION | MF_POPUP, (UINT)ptrMenu->GetSafeHmenu(), "&Dynamic Menu"

);

AfxGetMainWnd()->DrawMenuBar();

m_bSubMenuOn=TRUE;

}

void CMenuDoc::OnEditDeleteDynamicMenu()

{

CMenu *pTopMenu=AfxGetMainWnd()->GetMenu();

pTopMenu->RemoveMenu(1, MF_BYPOSITION);

AfxGetMainWnd()->DrawMenuBar();

m_bSubMenuOn=FALSE;

}


When inserting sub-menu, flag MF_BYPOSITION is used. This is because the first level menu items do not have command IDs.

After the menu is inserted or removed, we must call function CWnd::DrawMenuBar() to let the menu be updated. Otherwise although the content of the menu is actually changed, it will not be reflected to the user interface until the update is triggered by some other reasons.

4 Bitmap Check

The default menu check provided by MFC is a tick mark, and nothing is displayed when the check is removed. With a little effort, we can prepare our own bitmaps and use them to implement the checked and unchecked state (Figure 2-3).

To implement the checked and unchecked states of menu items using bitmaps, we need to call the following member function of CMenu:

BOOL CMenu::SetMenuItemBitmaps
(

UINT nPosition, UINT nFlags, const CBitmap* pBmpUnchecked,

const CBitmap* pBmpChecked

);


The first two parameters of this function indicate which menu item we are working with. Their meanings are the same with that of functions such as CMenu::EnableMenuItem(...). When calling this function, we can use either a command ID or an absolute position to identify a menu item. The third and fourth parameters are pointers to bitmaps (CBitmap type objects), one for checked state, one for unchecked state.

Like menu, bitmap can also be prepared as resource then be loaded at program's runtime. We can edit a bitmap in Developer Studio, and save it as application's resource. Adding a bitmap resource is the same with adding other types of resources: we can execute Insert | Resource... command, then select Bitmap from the popped up dialog box. The newly added resource will be assigned a default ID, it could also be changed by the programmer.

To load a bitmap resource into the memory, we need to use class CBitmap. This procedure is similar to loading a menu resource: first we need to use CBitmap to declare a variable, then call function CBitmap::LoadBitmap(...) to load the resource. For example, if we have a CBitmap type variable bmp, and our bitmap resource's ID is IDB_BITMAP, we can load the bitmap as follows:

bmp.LoadBitmap(IDB_BITMAP);

When calling function CMenu::SetMenuItemBitmaps(...), we can pass the pointers of CBitmap type variables to its parameters.

Sample 4\Menu demonstrates bitmap check implementation. It is based on sample 3\Menu, which adds check bitmaps to menu item ID__POPUPITEM1 and ID__POPUPITEM2. Two bitmap resources IDB_BITMAP_CHECK and IDB_BITMAP_UNCHECK are used to indicate menu item's checked and unchecked states respectively. Both bitmaps have a size of 15(15, which is a suitable size for normal menu items. If we use bigger bitmaps, they might be chopped to fit into the area of menu item.

In the sample, two new CBitmap type variables m_bmpCheck and m_bmpUnCheck are declared in class CMenuDoc, which are used to load the bitmap resources:

class CMenuDoc : public CDocument
{
protected:

CMenu m_menuSub;

CBitmap m_bmpCheck;

CBitmap m_bmpUnCheck;

BOOL m_bSubMenuOn;

......

}


In the constructor of CMenuDoc, bitmap resources IDB_BITMAP_CHECK and IDB_BITMAP_UNCHECK are loaded using two variables. Also, after we load the pop up menu resource, function CMenu:: SetMenuItemBitmap(...) is called for both ID__POPUPITEM1 and ID__POPITEM2. We use bitmaps to indicate both checked and unchecked states. The following code fragment shows how it is implemented:

CMenuDoc::CMenuDoc()
{

CMenu *ptrMenu;

m_menuSub.LoadMenu(IDR_MENU_POPUP);

m_bmpCheck.LoadBitmap(IDB_BITMAP_CHECK);

m_bmpUnCheck.LoadBitmap(IDB_BITMAP_UNCHECK);

ptrMenu=m_menuSub.GetSubMenu(0);

ptrMenu->SetMenuItemBitmaps(0, MF_BYPOSITION, &m_bmpUnCheck, &m_bmpCheck);

ptrMenu->SetMenuItemBitmaps(1, MF_BYPOSITION, &m_bmpUnCheck, &m_bmpCheck);

m_bSubMenuOn=FALSE;

}


When calling function CMenu::SetMenuItemBitmap(...), we use absolute position instead of command ID to identify a menu item. So the second parameter passed to the function is MF_BYPOSITION.

Besides these, two UPDATE_COMMAND_UI message handlers are also added to CMenuDoc to set item ID__POPUPITEM1 to checked state and set ID_POPUPITEM2 to unchecked state permenently. The following two functions show the implementation of the messages handlers:

void CMenuDoc::OnUpdatePopUpItem1(CCmdUI *pCmdUI)
{

if(m_bSubMenuOn)pCmdUI->SetCheck(TRUE);

}

void CMenuDoc::OnUpdatePopUpItem2(CCmdUI *pCmdUI)

{

if(m_bSubMenuOn)pCmdUI->SetCheck(FALSE);

}


In the sample, bitmaps are prepared for both checked and unchecked states for a menu item. When calling function CMenu::SetMenuItemBitmaps(...), if either of the bitmaps is not provided (the corresponding parameter is NULL), nothing will be displayed for that state. If both parameters are NULL, the default tick mark will be used for the checked state.

5 System Menu and Bitmap Menu Item

System Menu

By default, every application has a system menu, which is accessible through left clicking on the small icon located at the left side of application's caption bar, or right clicking on the application when it is in icon state. The system menu can be customized to meet special requirement. Especially, we can add and delete menu items dynamically just like a normal menu.

We already know how to access an application's standard menu. Once we obtained a CMenu type pointer to the application's standard menu, we can feel free to add new menu items, remove menu items, and change their attributes dynamically.

System menu is different from a standard menu. We need to call another function to obtain a pointer to it. In MFC, the function that can be used to access system menu is CWnd::GetSystemMenu(...). Please note that we must call this function for a window that has an attached system menu. For an SDI or MDI application, system menu is attached to the mainframe window. For a dialog box based application, the system menu is attached to the dialog window.

Unlike user implemented commands, system commands (commands on the system menu) are sent through WM_SYSCOMMAND rather than WM_COMMAND message. If

Page : << Previous 4  Next >>