ArtsAutosBooksBusinessEducationEntertainmentFamilyFashionFoodGamesGenderHealthHolidaysHomeHubPagesPersonal FinancePetsPoliticsReligionSportsTechnologyTravel

VC++ MFC Example: Owner Draw Menu Using WM_DRAWITEM, WM_MEASUREITEM

Updated on February 20, 2019
sirama profile image

I am a software engineer. I have been working with C++, MFC, and .net technologies for 15 years. I like playing video games & reading books.

1. Introduction

When an owner window of the menu item decides how the menu item should look, then the menu item is known as Owner Drawn Menu. The default windows provided menu has a standard look and feel. We cannot add Red and blue-colored boxes as a menu item through the resource editor. In this article, we will see how can we display only color boxes as a menu item under the color menu.

2. Create MFC SDI Application

First, we will create MFC SDI Application and then we write code for Owner Drawn Menu. To create MFC SDI Application, in the app wizard, we have to select Single Document Interface option and make sure to remove the check mark for the Document/View architecture support check box. This is shown in the below picture.

Create MFC SDI Application without Document/View Support
Create MFC SDI Application without Document/View Support | Source

In the class files provided by the application wizard, we are going to draw the owner-draw menu items through CMainFrame Class. Once the application is created, we have to remove the unwanted Menus and Menu Items from the menu bar of the mainframe window. Then, we will add the color menu at the end of the menu bar. To this color menu, we will add three menu items named Red, Blue, and Green. This is shown in the below video:

Prepare MFC Menu Items (No Audio)

Note that the menu items added in the above video are not owner drawn menu items. We will make these menu items as an owner-draw menu item at run-time. Once the three menu items are added, we name it as ID_COLOR_RED, ID_COLOR_BLUE, and ID_COLOR_GREEN using Property Window. For example, Assigning the ID to the menu item Green is shown in the below picture:

Setting Resource ID for the Menu Item
Setting Resource ID for the Menu Item | Source

At this stage, running the application will display the standard menu items with texts Red, Blue, and Green. Now we will start our coding for this example.

3. Changing the Menu items as Owner Drawn

Video 1 shows that we already added three menu items to the color menu. However, these menu items are not Owner Drawn. To make it Owner Drawn, we should change the menu items by calling the ModifyMenu on the CMenu. We have to Add the below-specified code in the int OnCreate function of the CMainFrame.

//Sample 01: Modify the Menu as OwnerDrawn
CMenu * pMainFrame_menu = this->GetMenu();
pMainFrame_menu->ModifyMenu(ID_COLOR_RED, 
	MF_BYCOMMAND | MF_OWNERDRAW, ID_COLOR_RED);
pMainFrame_menu->ModifyMenu(ID_COLOR_BLUE, 
	MF_BYCOMMAND | MF_OWNERDRAW, ID_COLOR_BLUE);
pMainFrame_menu->ModifyMenu(ID_COLOR_GREEN, 
	MF_BYCOMMAND | MF_OWNERDRAW, ID_COLOR_GREEN);

The code added (Sample 01) above will change three Menu Items of the ‘color’ as Owner Drawn Menu. The first parameter says; for example ID_COLOR_RED specifies the Menu Item that we are modifying. In the second parameter, we specified that the first parameter given to the function is a command id of the Menu Item. This is done by supplying the flag MF_BYCOMMAND. We can also specify a Menu Item by its position in that case a first parameter is a position number like 0,1,2 etc and the second parameter supplies the flag MF_BYPOSITION. In the second parameter, we also supplied one more flag MF_OWNERDRAW which makes the Menu Item as an Owner Drawn Menu Item. We can even change the command id of the Menu Item as part of this modification using the third parameter. In our case, we kept the same ids.

Here, we created the menu first, then used the ModifyMenu Function to change the menu items as Owner Drawn. We can also use the AppendMenu or InsertMenu with the MF_OWNERDRAW flag. In both the cases, we are creating the Menu at run-time.

4. One Handler - Three menu items

We will add single handler function for all three changed Menu Items. First, we must ensure that Resource IDs for the Menu Items are in sequential order. In other words, the IDS of the Menu Items should be a continuous numbers. In our case, ID_COLOR_RED is lower id and ID_COLOR_GREEN is higher id. We can check these IDS from Resource.h as shown in the below picture.

Menu Item Resource IDS in Sequential Order
Menu Item Resource IDS in Sequential Order | Source

Once we have the ID sequence, make an entry in the message map as shown below:

//Sample 02: Hanlder for the Owner drawn menu
ON_COMMAND_RANGE(ID_COLOR_RED, ID_COLOR_GREEN, 
OnOwnerDMenuClick ) 

In ON_COMMAND_RANGE Macro, we link handler function OnOwnerDMenuClick to all our three Menu Items. The IDS passed to the Macro defines the Lower and Upper boundary. This way, when we click any Menu Item under the color Menu of our example, the handler function receives the call.

In the MainFrame.h header file, we add the handler function declaration as shown below:

//Sample 03: Declare the Handler function
void OnOwnerDMenuClick(UINT cid);

And, Below is the implementation for the handler function in the MainFrame.cpp:

//Sample 04: Handler for the Menu Items
void CMainFrame::OnOwnerDMenuClick(UINT cid)
{
	if (cid == ID_COLOR_RED)
	{
		AfxMessageBox(_T("Red Button Clicked"));
	}
	if (cid == ID_COLOR_BLUE)
	{
		AfxMessageBox(_T("Blue Button Clicked"));
	}
	if (cid == ID_COLOR_GREEN)
	{
		AfxMessageBox(_T("Green Button Clicked"));
	}
}

The above handler function displays the message boxes, which corresponds to the clicked menu item. In the above function we check the 'cid' which carries the command id of the menu item clicked.

5. WM_MEASUREITEM and WM_DRAWITEM

When a Menu Item is specified as Owner Drawn Menu, the owner, the CMainFrame window will receive WM_DRAWITEM and WM_MEASUREITEM window messages. The WM_MEASUREITEM message will be sent only once for each Owner Drawn Menu Item. This means, MFC sends the message when the Menu containing the Menu Items is opened for the first time. The handler for the measure item will set the dimensions required for drawing those menus.

The window message WM_DRAWITEM will be sent for every Owner Drawn Menu Item whenever the menu is opened by the mouse click. Therefore, the owner draws each Menu Item in the WM_DRAWITEM handler by using the measurements taken from the handler function of the WM_MEASUREITEM.

For example, let us consider that user clicks the color Menu three times. For the first time the CFrameWnd will hear both the window messages. For second and third clicks, it will get only the WM_DRAWITEM message.

The Below video shows providing the handler function in CFrameWnd for both the window messages:

Providing the Handler Functions (No Audio)

6. Implementing OnMeasureItem Handler

In the previous video, we saw the Handler Function for both the window messages discussed in the previous section. Now we will add the below piece of code in the OnMeasureItem Handler.

void CMainFrame::OnMeasureItem(int nIDCtl,
		LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
	//Sample 05: Provide Height and widths
	UINT height = 20;
	UINT width = 40;
	lpMeasureItemStruct->itemHeight = height;
	lpMeasureItemStruct->itemWidth = width;
	CFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}

In the above handler function, we specified the width and height of our Owner Drawn Menu Item with a hard-coded value through LPMEASUREITEMSTRUCT. Therefore, in our case, the three Menu Items will have same width and height. Sometimes, the width and height changes for each Menu Item. For Example, displaying the name of the Font in the Menu using the same Font Style. This changes the width and height of the Menu Items based on the displayed Font’s characteristics even if we display same text.

7. Implementing OnDrawItem Handler

Look at the handler function signature of the OnDrawItem below:

void CMainFrame::OnDrawItem(int nIDCtl, 
		LPDRAWITEMSTRUCT lpDrawItemStruct)

The measurement done for each menu item in the OnMeasureItem() can be retrieved here using the lpDrawItemStruct. Before we write the code inside the OnDrawItem(), we explore some important members of the LPDRAWITEMSTRUCT.

  • The 'itemId member of this structure holds menu item id. Using this, we can know what menu item we are drawing now. If we want to draw special effect for a particular menu item, this member will be useful.
  • The rcItem member provides the rectangular structure, which can be used as the dimension required for drawing the Menu Item.
  • The 'hDC' member is used for Drawing the shapes using the GDI objects.
  • The itemState member informs the state of the menu item. We can check the state using the bitwise & operator with constants ODS_CHECKED, ODS_DISABLED, ODS_GRAYED and ODS_SELECTED.

1) First thing we will do is creating a device context for the drawing. Once the device context is created in the stack, we attach this MFC object to the 'hDC’ win32 handle of the lpDrawItemStruct.

//6.1: Get the device context from the Draw 
//Item structure and attach that to a dc object
CDC menu_dc;
menu_dc.Attach(lpDrawItemStruct->hDC);

2) When the Menu Item is selected, we will draw a border in black color around it. In addition, we will draw the border around Non-selected Menu items in the background color of the Menu. This will differentiate the selected Menu Item from the normal one. The Win32 API call, GetSysColor(COLOR_MENU) will give us the background Menu color. Drawing around the Non-selected Menu Item is required to clear the already drawn black rectangle. For example, when the user moves the mouse cursor from Red Menu Item to Blue Menu Item, we should clear the Border Rectangle around the red and draw a new border around the blue one. To draw the rectangle, we call FrameRect function on the device context. The ODS_SELECTED constant is checked against the item state of the Menu Item to decide the color of the brush.

//6.2: Test item state. When the item is selected, 
//draw a black border over it. When item is 
//not selected draw the border in menu's background 
//color (Clears previous drawn border)	
CBrush * brush;
RECT menu_item_rct = lpDrawItemStruct->rcItem ;
if ( lpDrawItemStruct->itemState & ODS_SELECTED )
	brush = new CBrush(RGB(0,0,0));
else
{
	DWORD color_index = ::GetSysColor(COLOR_MENU);
	brush = new CBrush(color_index);
}
menu_dc.FrameRect(&menu_item_rct, brush);
delete brush;

3) We check the itemID member with the menu item id constant for creating the color brushes matching the selected menu item. Once the required color brush is created, we fill the diminished rectangle measured in the OnMeasureItem. The DeflateRect function will reduce the dimension of the rectangle and FillRect paints the rectangle using the brush specified. Once we are done with the drawing, we detach the Win32 handle from CDC object. Below is the code:

//6.3: Create the Item color and draw it
if (lpDrawItemStruct->itemID == ID_COLOR_RED)
	brush = new CBrush(RGB(255,0,0));
if (lpDrawItemStruct->itemID == ID_COLOR_BLUE)
	brush = new CBrush(RGB(0,0,255));
if (lpDrawItemStruct->itemID == ID_COLOR_GREEN)
	brush = new CBrush(RGB(0,255,0));
CRect menu_rct(menu_item_rct);
menu_rct.DeflateRect(1,2);
menu_dc.FillRect( menu_rct, brush );
delete brush;

//6.4: Detach win32 handle
menu_dc.Detach();
CFrameWnd::OnDrawItem(nIDCtl, lpDrawItemStruct);

Running the Example - MFC Owner Drawn Menu for Colors (No Audio)

Source Code : Download

© 2019 sirama

Comments

    0 of 8192 characters used
    Post Comment

    No comments yet.

    working

    This website uses cookies

    As a user in the EEA, your approval is needed on a few things. To provide a better website experience, hubpages.com uses cookies (and other similar technologies) and may collect, process, and share personal data. Please choose which areas of our service you consent to our doing so.

    For more information on managing or withdrawing consents and how we handle data, visit our Privacy Policy at: https://hubpages.com/privacy-policy#gdpr

    Show Details
    Necessary
    HubPages Device IDThis is used to identify particular browsers or devices when the access the service, and is used for security reasons.
    LoginThis is necessary to sign in to the HubPages Service.
    Google RecaptchaThis is used to prevent bots and spam. (Privacy Policy)
    AkismetThis is used to detect comment spam. (Privacy Policy)
    HubPages Google AnalyticsThis is used to provide data on traffic to our website, all personally identifyable data is anonymized. (Privacy Policy)
    HubPages Traffic PixelThis is used to collect data on traffic to articles and other pages on our site. Unless you are signed in to a HubPages account, all personally identifiable information is anonymized.
    Amazon Web ServicesThis is a cloud services platform that we used to host our service. (Privacy Policy)
    CloudflareThis is a cloud CDN service that we use to efficiently deliver files required for our service to operate such as javascript, cascading style sheets, images, and videos. (Privacy Policy)
    Google Hosted LibrariesJavascript software libraries such as jQuery are loaded at endpoints on the googleapis.com or gstatic.com domains, for performance and efficiency reasons. (Privacy Policy)
    Features
    Google Custom SearchThis is feature allows you to search the site. (Privacy Policy)
    Google MapsSome articles have Google Maps embedded in them. (Privacy Policy)
    Google ChartsThis is used to display charts and graphs on articles and the author center. (Privacy Policy)
    Google AdSense Host APIThis service allows you to sign up for or associate a Google AdSense account with HubPages, so that you can earn money from ads on your articles. No data is shared unless you engage with this feature. (Privacy Policy)
    Google YouTubeSome articles have YouTube videos embedded in them. (Privacy Policy)
    VimeoSome articles have Vimeo videos embedded in them. (Privacy Policy)
    PaypalThis is used for a registered author who enrolls in the HubPages Earnings program and requests to be paid via PayPal. No data is shared with Paypal unless you engage with this feature. (Privacy Policy)
    Facebook LoginYou can use this to streamline signing up for, or signing in to your Hubpages account. No data is shared with Facebook unless you engage with this feature. (Privacy Policy)
    MavenThis supports the Maven widget and search functionality. (Privacy Policy)
    Marketing
    Google AdSenseThis is an ad network. (Privacy Policy)
    Google DoubleClickGoogle provides ad serving technology and runs an ad network. (Privacy Policy)
    Index ExchangeThis is an ad network. (Privacy Policy)
    SovrnThis is an ad network. (Privacy Policy)
    Facebook AdsThis is an ad network. (Privacy Policy)
    Amazon Unified Ad MarketplaceThis is an ad network. (Privacy Policy)
    AppNexusThis is an ad network. (Privacy Policy)
    OpenxThis is an ad network. (Privacy Policy)
    Rubicon ProjectThis is an ad network. (Privacy Policy)
    TripleLiftThis is an ad network. (Privacy Policy)
    Say MediaWe partner with Say Media to deliver ad campaigns on our sites. (Privacy Policy)
    Remarketing PixelsWe may use remarketing pixels from advertising networks such as Google AdWords, Bing Ads, and Facebook in order to advertise the HubPages Service to people that have visited our sites.
    Conversion Tracking PixelsWe may use conversion tracking pixels from advertising networks such as Google AdWords, Bing Ads, and Facebook in order to identify when an advertisement has successfully resulted in the desired action, such as signing up for the HubPages Service or publishing an article on the HubPages Service.
    Statistics
    Author Google AnalyticsThis is used to provide traffic data and reports to the authors of articles on the HubPages Service. (Privacy Policy)
    ComscoreComScore is a media measurement and analytics company providing marketing data and analytics to enterprises, media and advertising agencies, and publishers. Non-consent will result in ComScore only processing obfuscated personal data. (Privacy Policy)
    Amazon Tracking PixelSome articles display amazon products as part of the Amazon Affiliate program, this pixel provides traffic statistics for those products (Privacy Policy)