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


we implement message handlers to receive system commands, we need to use ON_WM_SYSCOMMAND macro.

Bitmap Menu Item

From the samples in the previous sections, we already know how to customize a menu item: we can set check, remove check, change text dynamically. We can also use bitmaps to represent its checked and unchecked states. Besides above features, a menu item can be modified to display a bitmap instead of a text string. This can make the application more attractive, because sometimes images are more intuitive than text strings.

Sample 5\Menu demonstrates the two techniques described above, it is based on sample 4\Menu. In this sample, one of the system menu items (a separator) is changed to bitmap menu item. If we execute this "bitmap command", a message box will pop up. Also, another new command is added to the system menu, it allows the user to resume the original system menu.

New Functions

Function CWnd::GetSystemMenu(...) has only one Boolean type parameter:

CMenu *CWnd::GetSystemMenu(BOOL bRevert);


Although we can call this function to obtain a pointer to the system menu and manipulate it, the original default system menu can be reverted at any time by calling this function and passing a TRUE value to its bRevert parameter. In this case, function's returned value has no meaning and should not be treated as a pointer to a menu object. We need to pass FALSE to this parameter in order to obtain a valid pointer to the system menu.

Function CMenu::ModifyMenu(...) allows us to change any menu item to a separator, a sub-menu, a bitmap menu item. It can also be used to modify a menu item's text. This member function has two versions:

BOOL CMenu::ModifyMenu
(

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

);

BOOL CMenu::ModifyMenu

(

UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp

);


The first version of this function allows us to change a menu item to a text item, a separator, or a sub-menu. The second version allows us to change a menu item to a bitmap item. For the second version, parameter nIDNewItem specifies the new command ID, and parameter pBmp is a pointer to a CBitmap object, which must contain a valid bitmap resource.

Menu Modification

In the sample application, a bitmap resource ID_BITMAP_QUESTION is prepared for implementing bitmap menu item. This bitmap contains a question mark. There is no restriction on the bitmap size, because the size of menu item will be adjusted automatically to let the image fit in.

To load the image, a new CBitmap type variable m_bmpQuestion is declared in class CMainFrame, and bitmap resource ID_BITMAP_QUESTION is loaded in the constructor of class CMainFrame:

class CMainFrame : public CFrameWnd
{

......

protected:

CStatusBar m_wndStatusBar;

CToolBar m_wndToolBar;

CBitmap m_bmpQuestion;

......

};

CMainFrame::CMainFrame()

{

m_bmpQuestion.LoadBitmap(IDB_BITMAP_QUESTION);

}


The pointer to the system menu is obtained in function CMainFrame::OnCreate(...). First, system menu's fifth item (a separator) is modified to a bitmap menu item, then another new command "Resume standard system menu" is inserted before this bitmap menu item. The IDs of the two commands are ID_QUESTION and ID_RESUME respectively.

Although we can use any numerical values as the command IDs of the newly added menu items, they should not be used by other resources of the application. The best way to prevent this from happening is to generate two new string resources in the application, and use ID_QUESTION and ID_RESUME as their symbolic IDs. Because Developer Studio will always allocate unused values for new resources, we can avoid sharing IDs with other resources by using this method.

The following shows how we access the system menu and make changes to its items:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{

CMenu *ptrMenu;

......

ptrMenu=GetSystemMenu(FALSE);

ptrMenu->ModifyMenu(5, MF_BYPOSITION, ID_QUESTION, &m_bmpQuestion);

ptrMenu->InsertMenu(5, MF_BYPOSITION, ID_RESUME, "Resume standard system menu");

return 0;

}


Message Mapping for System Command

In order to trap events for the system menu, we need to handle message WM_SYSCOMMAND. We can map this message to our member function by using macro ON_WM_SYSCOMMAND. The format of the message handler is:

afx_msg void OnSysCommand(UINT nID, LPARAM lParam);

And the format of mapping macro is:

ON_WM_SYSCOMMAND()

Of course, we can ask Class Wizard to do the mapping for us. Before using it to add the above message handler to CMainFrame class, we need to make following changes to the settings of Class Wizard: first click "Class info" tab of the Class Wizard, then select "Window" from the window "Message filter" (Figure 2-4). The default message filter for CMainFrame frame window is "Topmost frame", and WM_SYSCOMMAND will not be listed in the message list. After this modification, we can go back to "Message Maps" page, and choose "WM_SYSCOMMAND" from messages window. To add the message handler, we simply need to click "Add function" button (make sure the settings in other windows are correct). After this, the new function OnSysCommand(...) will be added to the application.

Here is how this function is declared in class CMainFrame:

class CMainFrame
{

......

afx_msg void OnSysCommand(UINT nID, LPARAM lParam);

......

};

The message mapping is as follows:

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

......

ON_WM_SYSCOMMAND()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()


Message handler CMainFrame::OnSysCommand(...) has two parameters, first of which is the command ID, and the second is LPARAM message parameter. In our case, we only need to use the first parameter, because it tells us which command is being executed. If the command is ID_RESUME, we need to call function CMenu::GetSystemMenu(...) to resume the default system menu; if the command is ID_QUESTION, we pop up a message box:

void CMainFrame::OnSysCommand(UINT nID, LPARAM lParam)
{

if(nID == ID_RESUME)GetSystemMenu(TRUE);

if(nID == ID_QUESTION)AfxMessageBox("Question");

CFrameWnd::OnSysCommand(nID, lParam);

}


6 Owner-Draw Menu

When we highlight a bitmap menu item by selecting it using mouse, the bitmap will be inversed. We have no way of modifying this property because function CMenu::ModifyMenu(...) requires only one bitmap, which will be used to implement menu item's normal state. The other states of a bitmap menu item will be drawn using the default implementations. Normally this is good enough. However, sometimes we may want to use different bitmaps to represent a menu item's different states: selected, unselected, checked, unchecked, grayed or enabled.

Also, sometimes we may want to paint a menu item dynamically. Suppose we need to create a "Color" menu item: the menu item represents a color that the user can use, and this color can be modified to represent any available color in the system. To implement this type of menu item, we can paint it using the currently selected color. In this case it is not appropriate to create the menu item using bitmap resources: there may exist thousands of available colors in the system, and it is just too inconvenient to prepare a bitmap resource for each possible color.

The owner-draw menu can help us build more powerful user interface. With this type of menu, we can draw the menu items dynamically, using different bitmaps to represent different states of the menu item. We can also change the associated bitmaps at any time.

Overriding Two Functions

The implementation of owner draw menu is not very difficult, all we need is to override two member functions of class CMenu: CMenu::MeasureItem(...) and CMenu::DrawItem(...).

By default, the menu is drawn by the system. We can change this attribute by specifying MF_OWNERDRAW style for a menu. Any menu with this style will be drawn by its owner. Since a menu's owner is usually the mainframe window (in SDI and MDI applications), we can add code to class CMainFrame to implement dynamic menu drawing. Actually, the menu drawing has two associated messages: WM_MEASUREITEM and WM_DRAWITEM. When a menu item with MF_OWNERDRAW style needs to be drawn, the menu sends out the above two messages to the mainframe window. The mainframe window finds out the pointer to the corresponding menu and calls functions CMenu::MeasureItem(...) and CMenu::DrawItem(...). Here, the first function is used to retrieve the dimension of the menu item, which can be used to calculate its layout. The second function implements menu drawing. We need to override it in order to implement custom interface.

One simple and most commonly used way to provide graphic interface is to prepare a bitmap resource then load and draw it at run time. Please note that this is different from preparing a bitmap resource and calling CMenu::ModifyMenu(...) to associate the bitmap with a menu item. If we implement drawing by ourselves, we can manipulate drawing details. For example, when drawing the bitmap, we can change the size of the image, add a text over it. If we assign the bitmap to the menu item, we lose the control over the details of painting the bitmap.

Drawing a Bitmap

To draw a bitmap, we need to understand some basics on graphic device interface (GDI). This topic will be thoroughly discussed from chapter 8 through 12, here is just a simple discussion on bitmap drawing. In Windows( operating system, when we want to output objects (a pixel, a line or a bitmap) to the screen, we can not write directly to the screen. Instead, we must write to the device context (DC). A device context is a data structure containing information about the drawing attributes of a device (typically a display or a printer). We can use DC to write text, draw pixels, lines and bitmaps to the devices. In MFC the device context is supported by class CDC, which has many functions that can let us draw

Page : << Previous 5  Next >>