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


different types of objects.

We can implement bitmap drawing by obtaining a target DC and calling member functions of CDC. The target DC is usually obtained from a window.

A DC can select a lot of GDI objects, such pen, brush, font and bitmap. Pen can be different type of pens, and brush can be different types of brushes. A DC can select any pen or brush as its current tool, but at any time, a DC can select only one pen and one brush. If we want to draw a pixel or a line, we can select the appropriate pen into the target DC and use it to implement drawing. A DC can also select bitmap for drawing. However, to paint a bitmap, we can not select it into the target DC and draw it directly. The normal way of painting a bitmap is to prepare a compatible memory DC (which is a block of memory with the same attributes of the target DC), select the bitmap into the memory DC, and copy the bitmap from the memory DC to the target DC.

We will learn more about DC and bitmap drawing in later chapters. For the time being, we can neglect the drawing details.

Deriving a New Class from CMenu

Sample 6\Menu demonstrates owner-draw menu implementation. It is base on sample 5\Menu. In this sample menu items ID__POPUPITEM1 and ID__POPUPITEM2 of the dynamic menu are implemented as owner-draw menu, their menu items are painted using different bitmaps.

Like sample 5\Menu, some new images are prepared as bitmap resources. In the sample, the newly added bitmaps are IDB_BITMAP_QUESTIONSEL, IDB_BITMAP_SMILE and IDB_BITMAP_SMILESEL. We will use IDB_BITMAP_SMILE and IDB_BITMAP_SMILESEL to implement owner-draw menu item ID__POPITEM1, and use IDB_BITMAP_QUESTION to implement ID__POPITEM2.

First, we need to override class CMenu. In the sample, a new class MCMenu is derived from CMenu, in this class, we declare four CBitmap type variables that will be used to load bitmaps for menu drawing. Also, functions CMenu::MeasureItem(...) and CMenu::DrawItem(...) are overridden:

class MCMenu : public CMenu
{

protected:

CBitmap m_bmpQuestion;

CBitmap m_bmpQuestionSel;

CBitmap m_bmpSmile;

CBitmap m_bmpSmileSel;

public:

MCMenu();

virtual ~MCMenu();

virtual void MeasureItem(LPMEASUREITEMSTRUCT);

virtual void DrawItem(LPDRAWITEMSTRUCT);

};


In the constructor of class MCMenu, four bitmaps are loaded:

MCMenu::MCMenu() : CMenu()

{

m_bmpQuestion.LoadBitmap(IDB_BITMAP_QUESTION);

m_bmpQuestionSel.LoadBitmap(IDB_BITMAP_QUESTIONSEL);

m_bmpSmile.LoadBitmap(IDB_BITMAP_SMILE);

m_bmpSmileSel.LoadBitmap(IDB_BITMAP_SMILESEL);

}


Overriding Function CMenu::MeasureItem(...)

Next we need to override function CMenu::MeasureItem(...). It has only one parameter, which is a pointer to MEASUREITEMSTRUCT type object. Structure MEASUREITEMSTRUCT is used to inform system the dimension of the owner-draw menu and other controls. There are three important members that will be used: itemWidth and itemHeight, which represent width and height of the menu item; itemData, from which we know the type of the menu item that is being inquired.

The program can assign special data to an owner-draw menu item when it is being created. When the mainframe window calls the overridden functions to inquire the item's attributes or paint the menu, the data will be passed through itemData member of structure MEASUREITEMSTRUCT or DRAWITEMSTRUCT (DRAWITEMSTRUCT will be passed to function CMenu::DrawItem(...)). By examining this member in the overriden functions, we know what type of menu item we are working with.

Class CBitmap has a member function that can be used to obtain the size of a bitmap: CBitmap::GetBitmap(...). We need to prepare a BITMAP type object and pass its pointer to this function. After calling this function, the structure will be filled with a lot of information about the bitmap (not only the image dimension). In this sample, we need to use only two members of BITMAP structure: bmWidth and bmHeight, which represent a bitmap's width and height.

The following is the overridden function of CMenu::MeasureItem(...):

void MCMenu::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{

BITMAP bm;

switch(lpMeasureItemStruct->itemData)

{

case MENUTYPE_SMILE:

{

m_bmpSmile.GetBitmap(&bm);

break;

}

case MENUTYPE_QUESTION:

{

m_bmpQuestion.GetBitmap(&bm);

break;

}

}

lpMeasureItemStruct->itemWidth=bm.bmWidth;

lpMeasureItemStruct->itemHeight=bm.bmHeight;

}


In this function, MENUTYPE_SMILE and MENUTYPE_QUESTION are user-defined macros that represent the type of menu items. First we examine member itemData and decide the type of the menu item. For different types of menu items, the corresponding bitmap sizes are retrieved and set to members itemWidth and itemHeight of structure MEASUREITEMSTRUCT. This size will be sent to the system and be used to calculate the layout of the whole sub-menu.

Overriding Function CMenu::DrawItem(...)

ow we need to override another member function of CMenu: CMenu::DrawItem(...). This function uses a different structure, DRAWITEMSTRUCT. It is used to inform us the state of the menu item, pass the target DC, and tell us the position where we can draw the customized menu. The following table lists some of its important members:

(Table omitted)

The following is the overridden function:

void MCMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{

CDC *ptrDC;

CDC dcMem;

CBitmap *ptrBmpOld;

CBitmap *ptrBmp;

CRect rect;

if(!(lpDrawItemStruct->CtlType & ODT_MENU))

{

CMenu::DrawItem(lpDrawItemStruct);

return;

}

ptrDC=CDC::FromHandle(lpDrawItemStruct->hDC);

dcMem.CreateCompatibleDC(ptrDC);

if(lpDrawItemStruct->itemState & ODS_SELECTED)

{

switch(lpDrawItemStruct->itemData)

{

case MENUTYPE_SMILE:

{

ptrBmp=&m_bmpSmileSel;

break;

}

case MENUTYPE_QUESTION:

{

ptrBmp=&m_bmpQuestionSel;

break;

}

}

}

else

{

switch(lpDrawItemStruct->itemData)

{

case MENUTYPE_SMILE:

{

ptrBmp=&m_bmpSmile;

break;

}

case MENUTYPE_QUESTION:

{

ptrBmp=&m_bmpQuestion;

break;

}

}

}

ptrBmpOld=dcMem.SelectObject(ptrBmp);

rect=lpDrawItemStruct->rcItem;

ptrDC->BitBlt

(

rect.left,

rect.top,

rect.Width(),

rect.Height(),

&dcMem,

0,

0,

SRCCOPY

);

dcMem.SelectObject(ptrBmpOld);

}


First, we check if the item is a menu. If not, we call the same function implemented by the base class and return. If so, first we obtain a CDC type pointer to the target device by calling function CDC::FromHandle(...). Then, we create a compatible memory DC with target DC, which will be used to draw the bitmap. Next, we check the menu item's state and type by looking at itemState and itemData members of structure DRAWITEMSTRUCT, and choose different bitmaps according to different situations. At last we select the appropriate bitmap into the memory DC, copy the bitmap to target device using function CDC::BitBlt(...). This function has many parameters: the first four are position and size on the target device; the fifth parameter is the pointer to the memory DC; the last parameter specifies drawing mode (SRCCOPY will copy the source bitmap to the target device). Finally, we must select the bitmap out of the memory DC, and resume its original state.

Using the New Class

Now we can use this class to implement owner-draw menu. In the sample application, the first two items of dynamic menu IDR_MENU_POPUP are implemented with this style.

First a new MCMenu type variable m_menuModified is declared in class CMenuDoc (the original m_menuSub variable remain unchanged):

class CMenuDoc : public CDocument
{

protected:

CMenu m_menuSub;

MCMenu m_menuModified;

......

}


The original variable m_menuSub is used to load the menu resource, whose first sub-menu is obtained by calling function CMenu::GetSubMenu(...) and attached to variable m_menuModified. By doing this, the system will call the member functions of class MCMenu instead of CMenu when the owner-draw menu needs to be painted. To change a menu item's default style, function CMenu::ModifyMenu(...) is called and MF_OWNERDRAW flag is specified:

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

m_menuModified.Attach(ptrMenu->GetSafeHmenu());

ptrMenu->ModifyMenu

(

0,

MF_BYPOSITION | MF_ENABLED | MF_OWNERDRAW, ID__POPUPITEM1,

(LPCTSTR)MENUTYPE_SMILE

);

ptrMenu->ModifyMenu

(

1,

MF_BYPOSITION | MF_ENABLED | MF_OWNERDRAW, ID__POPUPITEM2,

(LPCTSTR)MENUTYPE_QUESTION

);

m_bSubMenuOn=FALSE;

}


In the above code, menu IDR_MENU_POPUP is loaded into m_menuSub, then the first sub-menu is obtained and attached to variable m_menuModified. Here, function CMenu::Attach(...) requires a HMENU type parameter, which can be obtained by calling function CMenu::GetSafeHmenu(). When calling function CMenu::ModifyMenu(...), we pass integer instead of string pointer to its last parameter. This does not matter because the integer provided here will not be treated as memory address, instead, it will be passed to itemData member of structure MEASUREITEMSTRUCT (and DRAWITEMSTRUCT) to indicate the type of the owner-drawn menu items.

Because the popup menu is attached to variable CMenuDoc::m_menuModified, we need to detach it before application exits. The best place of implementing this is in class MCMenu's destructor, when the menu object is about to be destroyed:

MCMenu::~MCMenu()
{

Detach();

}


Now we can compile the sample project and execute it. By executing command Edit | Insert Dynamic Menu and expanding Dynamic Menu then selecting the first two menu items, we will see that both selected and unselected states of them will be implemented by our own bitmaps.

Bitmap is not restricted to only indicating selected and normal menu states. With a little effort, we could also use bitmap to implement other menu states: grayed, checked, and unchecked. This will make our menu completely different from a menu implemented by plain text.

7 Changing the Whole Menu Dynamically

If we write MDI application, we can see that the main menu may change with different type of views. Generally, when there is no client window opened, the mainframe menu will have only three sub-menus (File, View, Help). After the user opens a client window, the mainframe menu will be replaced with a new menu that has more sub-menus. Although we could call CMenu::ModifyMenu(...) to implement this, it is not efficient because with this method, we have to the change menu items one by one. Actually, there is another easy way to change the whole menu dynamically: we can prepare a new menu resource, use it to replace the menu currently associated with the window at run time.

Class CWnd has a member function that can be used to set a window's menu dynamically: CWnd::SetMenu(...). The function has only one parameter, which is a CMenu type pointer. By using this function, we can change an application's mainframe menu at any time we want. Sample 7\Menu demonstrates this technique. It is a standard SDI application generated by Application Wizard with all default settings. In the sample, besides mainframe menu IDR_MAINFRAME, a new menu resource IDR_MAINFRAME_ACTIVE is prepared in the application. This menu has some new sub-menus (Options, Properties, Settings). For the purpose of demonstration, none of these sub-menus contains menu items. The default menu will be changed to this menu after we execute File | New or File | Open command. If we execute File | Close command from menu IDR_MAINFRAME_ACTIVE,

Page : << Previous 6  Next >>