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


can be activated and tracked by calling function CMenu::TrackPopupMenu(...):

BOOL CMenu::TrackPopupMenu

(

UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect=NULL

);


This function has 5 parameters. The first parameter nFlags lets us set styles of the menu (Where should the menu be put, which mouse button will be tracked). The most commonly used combination is TPM_LEFTALIGN | TPM_RIGHTBUTTON, which aligns menu's left border according to parameter x, and tracks mouse's right button activity (because we are implementing a right-click menu). The second parameter y decides the vertical position of the menu's top border. Please note that when message WM_RBUTTONDOWN is received, position of current mouse cursor will be passed to one of the parameters of function OnRButtonDown(...) as a CPoint type object. To make right-click menu easy to use, we can pass this position to function CMenu::TrackPopupMenu(...), which will create a pop up menu at the position of current mouse cursor. The fourth parameter is a CWnd type pointer, which indicates which window owns the pop up menu. In the sample, because the menu is implemented in the member function of class CMenuView, we can use this pointer to indicate the menu owner. The final parameter discribes a rectangle within which the user can click the mouse without dismissing the pop up menu. We could set it to NULL, in which case the menu will be dismissed if the user clicks outside the pop up menu.

Implementing Right-Click Menu

Now we can implement WM_RBUTTONDOWN message handler, load the menu resource and create right-click menu in the member function:

void CMenuView::OnRButtonDown(UINT nFlags, CPoint point)
{

CMenu menu;

CMenu *ptrMenu;

menu.LoadMenu(IDR_MENU_POPUP);

ptrMenu=menu.GetSubMenu(0);

ptrMenu->EnableMenuItem(ID__POPUPITEM1, MF_GRAYED);

ptrMenu->EnableMenuItem(ID__POPUPITEM2, MF_ENABLED);

ptrMenu->CheckMenuItem(ID__POPUPITEM3, MF_UNCHECKED);

ptrMenu->CheckMenuItem(ID__POPUPITEM4, MF_CHECKED);

ClientToScreen(&point);

ptrMenu->TrackPopupMenu

(

TPM_LEFTALIGN|TPM_RIGHTBUTTON,

point.x,

point.y,

this,

NULL

);

CView::OnRButtonDown(nFlags, point);

}


After implementing the right-click menu, we still need to call function CView::OnRButtonDown(...). This is to make sure that the application does not lose any default property implemented by class CView.

In the above function, before CMenu::TrackPopupMenu(...) is called, function CWnd::ClientToScreen() is used to convert the coordinates of a point from the client window to the desktop window (the whole screen). When point parameter is passed to CMenuView::OnRButtonDown(...), it is assumed to be measured in the coordinates system of the client window, which means (0, 0) is located at the upper-left corner of the client window. When we implement a menu, function CMenu::TrackPopupMenu(...) requires coordinates to be measured in the desktop window system, which means (0, 0) is located at the upper-left corner of the screen. Function CWnd::ClientToScreen(...) can convert the coordinates of a point between the two systems. This function is frequently used when we need to convert coordinates from one window to another.

By compiling and executing the application at this point, we will see that the right-click menu is implemented successfully.

Message Mapping for Right-Click Menu

Although the right-click menu is working now, we still can not use it to execute any command. The reason is simple: we haven't implemented WM_COMMAND type message handlers for the menu commands yet. For right-click menu, we cannot add message handlers using Class Wizard, because the command IDs of the menu items are not listed in "Object IDs" window of the Class Wizard. Thus, we have to do everything manually. Actually, adding message handlers for right-click menu items is the same with adding handlers for a normal menu item: we need to declare afx_msg type functions, use ON_COMMAND macros to do the message mapping, and implement the member functions. In the sample, WM_COMMAND type message handler is added for each menu item contained in the right-click menu. Within each message handler, a message box pops up indicating which menu item it is.

The following portion of code shows the member functions declared in class CMenuDoc:

class CMenuDoc : public CDocument
{

......

//{{AFX_MSG(CMenuDoc)

//}}AFX_MSG

afx_msg void OnPopUpItem1();

afx_msg void OnPopUpItem2();

afx_msg void OnPopUpItem3();

afx_msg void OnPopUpItem4();

DECLARE_MESSAGE_MAP()

......

}


Message mapping macros are implemented as follows:

BEGIN_MESSAGE_MAP(CMenuDoc, CDocument)

//{{AFX_MSG_MAP(CMenuDoc)

//}}AFX_MSG_MAP

ON_COMMAND(ID__POPUPITEM1, OnPopUpItem1)

ON_COMMAND(ID__POPUPITEM2, OnPopUpItem2)

ON_COMMAND(ID__POPUPITEM3, OnPopUpItem3)

ON_COMMAND(ID__POPUPITEM4, OnPopUpItem4)

END_MESSAGE_MAP()


Four member functions are implemented as follows:

void CMenuDoc::OnPopUpItem1()
{

AfxMessageBox("Pop up menu item 1");

}

void CMenuDoc::OnPopUpItem2()

{

AfxMessageBox("Pop up menu item 2");

}

void CMenuDoc::OnPopUpItem3()

{

AfxMessageBox("Pop up menu item 3");

}

void CMenuDoc::OnPopUpItem4()

{

AfxMessageBox("Pop up menu item 4");

}


With the above implementation, we are able to execute the commands contained in the right-click pop up menu.

3 Updating Menu Dynamically

Sometimes it is desirable to change the contents of a menu dynamically. For example, if we create an application that supports many commands, we may want to organize them into different groups. Sometimes we want to enable a group of commands, sometimes we want to disable them.

Although we can handle UPDATE_COMMAND_UI message to enable or disable commands, sometimes it is more desirable if we can remove the whole sub-menu instead of just graying the menu text. Actually, sub-menu and menu item can all be modified dynamically: we can either add or delete a sub-menu or menu item at any time; we can also change the text of a menu item, move a sub-menu or menu item, or add a separator between two menu items. All these things can be implemented at run-time.

Menu Struture

The structure of menu --> sub menu --> menu item is like a tree. At the topmost level (the root), the menu comprises several sub-menus. Each sub-menu also comprises several items, which could be a normal command or another sub-menu. For example, in application Explorer (file browser in Windows95(), its first level menu comprises five sub-menus: File, Edit, View, Tool, and Help. If we examine File sub-menu, we will see that it comprises eight items: New, separator, Create Shortcut, Delete, Rename, Properties, separator and Close. Here, item New is another sub-menu, which comprises several other menu items. This kind of structure can continue. As long as our program needs, we can organize our menu into many different levels.

In MFC, class CMenu should be used this way. With a CMenu type pointer to a menu object, we have the access to only the menu items at certain level. If we want to access a menu item at a lower level, we first need to access the sub-menu that contains the desired menu item.

This can be explained by the previous "Explorer" example: suppose we have a CMenu type pointer to the main menu, we can use it to access the first level menu items: File, Edit, View, Tool, and Help. This means we can use the pointer to disable, enable or set text for any of the above items, but we can not use it to make change to the items belonging to other levels, for example, New item under File sub-menu. To access this item, we need to first obtain a CMenu type pointer to File sub-menu, then use it to modify item File | New.

Inserting and Removing Menu Item

Class CMenu has certain member functions that allow us to insert or delete a menu item dynamically. We can add either a menu item (including separator), or a whole sub-menu. When we remove a sub-menu, all the lower level items and sub-menus will be removed.

The function that can be used to insert menu items or sub-menus is CMenu::InsertMenu(...), it has the following format:

BOOL CMenu::InsertMenu
(

UINT nPosition, UINT nFlags, UINT nIDNewItem=0, LPCTSTR lpszNewItem=NULL

);


This function has five parameters. The first parameter, nPosition, indicates where we want our new menu item to be inserted. It could be an absolute position, 0, 1, 2..., or a command ID of the menu item. In the former case, MF_BYPOSITION bit of second parameter nFlags must be set. In the latter case, MF_BYCOMMAND bit must be set. Since not all menu items have a command ID (such as a separator), using position to indicate a menu item is sometimes necessary.

Generally, we can insert three types of items: a menu item with specified command ID, a separator or a sub-menu. To insert a menu item, we need to pass the command ID to nIDNewItem parameter, then use the final parameter lpszNewItem to specify the text of this menu item. If we want to insert a separator, we must set MF_SEPARATOR bit of parameter nFlag. In this case the rest two parameters nIDNewItem and lpszNewItem will be ignored, so we can pass any value to them. If we want to insert a sub-menu, we must pass a menu handle to parameter nIDNewItem, and use lpszNewItem to set the text for the menu item.

In Windows( programming, handle is a very important concept. Many types of resources are managed through using handles. A handle is just a unique number that can be used to reference a block of memory. After an object (program, resource, dynamically allocated memory block, etc.) is loaded into the memory, it will be assigned a unique handle that can be used to access this object. As a programmer, we don't need to know the exact value of a handle. When accessing an object, instead of using handle's absolute value, we can just use the variable that stores the handle.

Different handles have different prototypes, for a menu object, its prototype is HMENU.

In MFC, this is further simplified. When we call a member function to load an object into the memory, the handle will be automatically saved to a member variable. Later if we need this handle, we can just call a member function to retrieve it.

In the case of class

Page : << Previous 3  Next >>