MFC extension library - A plugin to handle owner drawn menus






4.41/5 (7 votes)
Continuation of the Plugin Architecture series
Table of content
- Introduction
- Initial work
- What messages do we need to implement
- Setting up the menu's image lists
- System menu icons
- Problems encountered
- What item am I measuring?
- All menu items are always enabled
- System menu(s) do not display correctly
- Default items not measured correctly
- Conclusion
- References
- Version history
Introduction
This article is a follow on to the article An MFC extension library to enable DLL plug-in technology for your application using MESSAGE_MAPs. If you are not familiar with the library itself I would recommend that you look at the main article first.
When I first published the MFC plug-in architecture I always intended to write a series of standard plug-ins which add functionality to all applications. This is the second in that series, the first being the example plug-in with the main article.
This article introduces a new plug-in that handles owner drawn menus using a modified version of Brent Corcums Cool Owner Drawn Menus with Bitmaps - Version 3.03. I will cover in this article how I developed the plug-in and problems that were encountered while doing so.

Initial work
When I first started to develop this plug-in I realized that there was a problem in the actual plug-in library. This was that you had to provide the name of the class(es) that the plug-in was used in. In the case of owner-drawn menus, we need to be a plug-in for many classes, except I do not know the names of all of these in advance.
// old method LPCTSTR CMFPlugIn::GetClass() { // return the name of the class which this is // a plug in map for, e.g. "CMyApp" return L"CMainFrame"; }
I needed to switch around the logic such that the plug-in gets asked "Are you a plug-in for this class?" and
returns true
/false
itself instead of supplying a
class name. So in our case, we just return true
for all objects which are plug-in enabled
so that we can handle making any menus they show ownerdrawn.
// new method bool CMFPlugIn::IsPlugInFor(CRuntimeClass *pClass) { return (_tcscmp(pClass->m_lpszClassName, "CMainFrame") == 0); }
This also forced me to re-work the method of suppressing message processing back in the main application, as
previously you had to cast the m_pPlugInFor
pointer to the right object and set a member variable.
Which is not really possible when you do not know what class you are a plug in for!
Since this plug-in only works with V1.2 or later of the library (V1.2 has the required modifications to the library to properly support this plug-in), existing users of the library should visit the main article and upgrade to the latest source versions. You will also have to update any of your own existing plug-ins to conform with the changes made. Instructions for this procedure are present in the main article.
What messages do we need to implement
So we want to do an owner drawn menu plug-in, which messages should this plug-in handle to implement its behaviour? Well it comes down to just 3, these are:
WM_INITMENUPOPUP
This message is sent when a menu is about to be displayed, in fact just before any popup menu or when
the user switches between multiple top level menus (e.g. between File and Edit). In this message handler,
we need to set the ownerdrawn style bit MF_OWNERDRAW
on every menu item in the menu about to be
displayed.
// early version of the OnInitMenuPopup() function void CODMenu::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) { if (IsPostCall()) { // iterate any menu about to be displayed and make sure // all the items have the ownerdrawn style set // We receive a WM_INITMENUPOPUP as each menu // is displayed, even if the user // switches menus or brings up a sub menu. This means we only need to // set the style at the current popup level. // we also set the user item data to the HMENU handle to allow // us to measure/draw the item correctly later if (pPopupMenu != NULL) { int itemCount = pPopupMenu->GetMenuItemCount(); for(int item = 0; item < itemCount; item++) { pPopupMenu->ModifyMenu(item, MF_BYPOSITION | MF_OWNERDRAW, itemID, (LPCTSTR)pPopupMenu->m_hMenu); } } } }
Because we do the MF_OWNERDRAW
style setting on demand, we do not need to subclass or replace
any of the existing CMenu
member functions. The programmer does not need to worry about keeping the
menu(s) they modify in the correct ownerdrawn state.
As the library supports Pre and Post calls on plug-in messages and we do not want to do this flag setting multiple times, we need to choose whether we do it before (pre) or after (post) the standard MFC processing of the message. I chose after for these reasons:
- MFC calls the
ON_UPDATE_COMMAND_UI
handlers for the menu items - MFC expands some menu options (e.g. recent file list, open documents in Window) with extra menu options, and we need all items in the menu to be ownerdrawn.
WM_MEASUREITEM
Once a menu item has the MF_OWNERDRAW
style bit set for it, the application is going to be receiving
WM_MEASUREITEM
messages so that the OS knows how big each display element is when a menu item is about
to be displayed. This message is a Pre message handler as we need to suppress the message being processed in the
regular MFC call list. Suppressing this message stops MFC TRACE
ing out a warning message when in debug
as it thinks the application is not handling the message (which it is not, as our plug-in is).
In this message, we also have to return the size of the menu option being measured. A modification of Brents BCMenu
code supplies the required functionality. That code is not covered here, examine the source fo further information.
WM_DRAWITEM
Once a menu item has the MF_OWNERDRAW
style bit set for it, the application is going to be receiving
these messages so that the items get drawn when required. This is usually the complicated bit of implementing
owner drawn menus. Once again, a modified version of the BCMenu::DrawItem
code comes to my rescue!
I did initially try and implement my own owner drawn menu code, but it was too much work. :(
These messages should only be processed when the object type is ODT_MENU
. As WM_MEASUREITEM
and WM_DRAWITEM
can be received for many different control types.
void CODMenu::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) { // check that the call is for an owner drawn menu if (IsPreCall() && lpMeasureItemStruct->CtlType == ODT_MENU) { MeasureItem(lpMeasureItemStruct); // we have handled it, stop it flowing through regular MFC SuppressThisMessage(); } } void CODMenu::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) { if (IsPreCall() && lpDrawItemStruct->CtlType == ODT_MENU) { DrawItem(lpDrawItemStruct); // we have handled it, stop it flowing through regular MFC SuppressThisMessage(); } }
Setting up the menu's image lists
The BCMenu
class would normally have calls made to LoadToolbar
during application
initialization.
This is something this plug-in cannot have done for it, so it needs to find and use all the toolbar resources in the application
and other plug-ins automatically. So how do we do this?
Well, when the library has been fully initialized and all plug-in DLLs loaded, each plug-in DLL receives a call to its
IntialiseDLL()
function (if it has one implemented). We need to put some code in this function to scan the
exe/dlls for any toolbar resources, a quick search through the MSDN
turned up the function
EnumResourceNames
, which looks an ideal candidate to do this.
extern "C" void InitialiseDLL() { CODMenu::EnumerateAndLoadToolbars(); }
If you look at the EnumResourceNames
function prototype:
// ANSI WINBASEAPI BOOL WINAPI EnumResourceNamesA( HMODULE hModule, LPCSTR lpType, ENUMRESNAMEPROC lpEnumFunc, LONG lParam); // UNICODE WINBASEAPI BOOL WINAPI EnumResourceNamesW( HMODULE hModule, LPCWSTR lpType, ENUMRESNAMEPROC lpEnumFunc, LONG lParam);
You can see that it requires an HMODULE
for each call. That will be the HINSTANCE
of the application
or the HINSTANCE
of each loaded plug-in DLL.
But how do we get the HINSTANCE
s of the loaded DLLs? This was another problem in the library,
which I solved by adding 2 extra functions to the main CPlugInApp
class:
int CPlugInApp::GetPlugInDLLCount()
Returns the number of plug-in DLLs.CDLLWrapper* CPlugInApp::GetDLL(int index)
returns a pointer to the object wrapping the plug-in DLLs exposed
The class CDLLWrapper
also had one extra interface function added:
HINSTANCE CDLLWrapper::GetHInstance()
returns theHINSTANCE
of the plug-in DLL.
If we have the HINSTANCE
of each module we need to search, we can use the system function
EnumResourceNames()
and pass through RT_TOOLBAR
as the type of resource we want to enumerate.
The system then calls a static
member CALLBACK
procedure and we get the information there on
every toolbar found in every module!
void CODMenu::EnumerateAndLoadToolbars() { // This procedure is called by the InitialiseDLL function // When we intialise, we need to enumerate all the toolbar // resources in all the plug-in DLLs and the application // this allows us to generate a map of menu items which have toolbar // images available for them // First enumerate and use the appplications toolbar(s) CPlugInApp *pApp = static_cast<CPlugInApp*>(AfxGetApp()); if (pApp) { TRACE("Enumerating application\n"); EnumResourceNames(pApp->m_hInstance, RT_TOOLBAR, (ENUMRESNAMEPROC)EnumResNameProc, 0); // now enumerate all the plug-in DLL toolbars int dllCount = pApp->GetPlugInDLLCount(); for(int dll = 0 ; dll < dllCount; dll++) { TRACE("Enumerating DLL %1d\n", dll); EnumResourceNames(pApp->GetDLL(dll)->GetHInstance(), RT_TOOLBAR, (ENUMRESNAMEPROC)EnumResNameProc, 0); } }
In the callback we load the toolbar resource entry which specifies the WM_COMMAND
id's for each
toolbar button. We are also able to load the bitmap resource with the same name and add this on-mass to our imagelist
which is used to plot the actual toolbar images used when the menu is displayed.
But how do we get the button ID's from the toolbar resource? We do not know its structure!
Toolbar resource structure
The standard MFC MDI toolbar is defined in the .rc file like this:
IDR_MAINFRAME TOOLBAR DISCARDABLE 16, 15 BEGIN BUTTON ID_FILE_NEW BUTTON ID_FILE_OPEN BUTTON ID_FILE_SAVE SEPARATOR BUTTON ID_EDIT_CUT BUTTON ID_EDIT_COPY BUTTON ID_EDIT_PASTE SEPARATOR BUTTON ID_FILE_PRINT SEPARATOR BUTTON ID_APP_ABOUT BUTTON ID_CONTEXT_HELP END
When we enumerate each toolbar resource and load it, we gets a pointer to the resource like this:
BOOL CALLBACK CODMenu::EnumResNameProc(HMODULE hModule, LPCTSTR lpszType, LPTSTR lpszName, LONG lParam) { TRACE("Toolbar found, module %x, Type %1d, Name %1d\n", hModule, lpszType, lpszName); // There should be 2 resources,one of type RT_TOOLBAR, // which enumerates the command IDs // the other of type RT_BITMAP, which are the images for each button // load in the RT_TOOLBAR button indexes HRSRC hrsrcToolbar = ::FindResource(hModule, lpszName, lpszType); HGLOBAL hToolbar = ::LoadResource(hModule, hrsrcToolbar); int size = ::SizeofResource(hModule, hrsrcToolbar); WORD* pToolbarData = (WORD*)::LockResource(hToolbar);
This gives us a WORD*
pointer to the toolbar resource, but what does the data mean? After a little investigation what
we have is a pointer to a structure like this:
struct ToolbarResource { int version; // version of the resource (always 1) int imageSizeX; // the X size of the toolbar images int imageSizeY; // the Y size of the toolbar images int tableEntries; // the number of toolbar button entries int buttons[tableEntries]; // button indexes (0 is separator) };
So having learnt this we load and construct the map as follows:
// from examination of a toolbar resource, the layout it: // WORD : Version number(usually 1) // WORD : X_SIZE of image(16) // WORD : Y_SIZE of image(15) // WORD : NUMBER OF TOOLBAR BUTTONS // x * WORD : TOOLBAR BUTTON ID's // NOTE THAT A TOOLBAR BUTTON ID OF 0 IS A SEPARATOR if (pToolbarData[0] == 0x0001) { // we recognise this toolbar version, go ahead if (pToolbarData[1] == m_iconX && pToolbarData[2] == m_iconY) { // buttons are the correct size for a menu, // lets load these images CBitmap toolbarBitmap; // make sure we load the bitmap image from the correct module HINSTANCE old = AfxGetResourceHandle(); AfxSetResourceHandle(hModule); toolbarBitmap.LoadBitmap(lpszName); AfxSetResourceHandle(old); // extract the IDs of each toolbar button for(int buttonIndex = 0 ; buttonIndex < pToolbarData[3] ; buttonIndex++) { if (pToolbarData[4 + buttonIndex] != 0) { // this is not a separator // make sure there is not already a map entry for this button if (m_commandToImage.find(pToolbarData[4 + buttonIndex]) == m_commandToImage.end()) { // does not exist, add it m_commandToImage[pToolbarData[4 + buttonIndex]] = m_commandToImage.size(); } else { TRACE("Warning, duplicate toolbar button" " %1d found, using first\n", pToolbarData[4 + buttonIndex]); } } } // add the toolbar images on mass m_buttonImages.Add(&toolbarBitmap, ::GetSysColor(COLOR_3DFACE)); toolbarBitmap.DeleteObject(); } } // return TRUE to keep enumerating any other toolbar // resources in the same module return TRUE; }
During the process of enumerating the toolbars, we also construct a std::map<int, int>
object which relates
WM_COMMAND
ids to image list index position (where the image is in the list).
Once all toolbars have been enumerated and the map constructed, we can create the disabled bitmap images used when the menu is rendered:
// we now have all the toolbars loaded into the // main image list m_buttonImages // generate the disabled versions of the images used CBitmap disabledImage; CWindowDC dc(NULL); dc.SaveDC(); disabledImage.CreateCompatibleBitmap(&dc, m_iconX, m_iconY); dc.SelectObject(&disabledImage); for(int image = 0 ; image < m_buttonImages.GetImageCount() ; image++) { CBitmap bmp; GetBitmapFromImageList(&dc, &m_buttonImages, image, bmp); DitherBlt3(&dc, bmp, ::GetSysColor(COLOR_3DFACE)); m_disabledImages.Add(&bmp, ::GetSysColor(COLOR_3DFACE)); } dc.RestoreDC(-1);
Doing this at the start saves us on processing time during menu display, as Brents original BCMenu
code would
generate the disabled bitmap image every time it needed to be rendered.
System menu icons
To get system menu icons to render correctly, I added a toolbar resource to the ownerdrawn menu plug-in. which is defined as follows:
IDR_TOOLBAR TOOLBAR DISCARDABLE 16, 15 BEGIN BUTTON 61472 BUTTON 61488 BUTTON 61536 BUTTON 61728 END
The numbers shown are the actual code values for SC_CLOSE, RC_RESTORE...
etc. I also added the toolbar image:

Problems encountered
While I was writing this plug-in I encountered the following problems:
What item am I measuring?
To be able to correctly measure an item, I was originally setting the menu items user data to the HMENU
of which it is a member of. This is because the LPMEASUREITEMSTRUCT
does not contain information of which menu you are actually
measuring an item for. Initially this appeared to be OK. I was using the itemID
value supplied to get the menu info
MF_BYCOMMAND
, this works fine for normal menu items, but as soon as you get a popup menu or a separator, you get an ID
of -1
or 0
which GetMenuItemInfo()
would fail on. So I was unable to measure popup/separator menu items!
After some further thought, I no longer saved the HMENU
in the user data, but the position in the menu.
This allowed the GetMenuItemInfo()
function to work correctly on popup menus when
retrieving information
MF_BYPOSITION
. But now I do not know which menu to do the call on! So I introduced a class member variable
HMENU m_hMenuBeingProcessed
and set this in the OnInitMenuPopup
handler. This works OK as we never
get a WM_INITMENUPOPUP
message between measuring all the items in that menu.
Saving the user data as the position in the menu also helps us process the menu item fully in the DrawItem
handler, although we are provided with the correct HMENU
of the item we are drawing for there.
All menu items are always enabled
When I wanted to get owner drawn disabled/checked items working, I added some items to the menu, setup
the correct ON_UPDATE_COMMAND_UI
handlers and set the relevant states. But in my DrawItem
code, the state of the item was always enabled and unchecked. In fact the only flag which was different between
calls was the ODS_SELECTED
for which item was currently highlighted. I tried various methods in the
DrawItem
procedure to get the correct state of the item with no success. So I went away and slept on this
problem.
When I awoke, I had the solution: The problem was in the OnInitMenuPopup
function where I was iterating
the menu about to be displayed and setting the MF_OWNERDRAW
flag. As this is a Post call, the MFC
has already done all its calls through to the ON_UPDATE_COMMAND_UI
handlers, and my MenuModify
call was just setting the MF_OWNERDRAW
flag only, thus overwriting the actual state of the item setup by
the ON_UPDATE_COMMAND_UI
handlers. The solution was to read the state of the menu item at this point and
or |
in the MF_OWNERDRAW
flag and not lose the state.
// a fixed version of the OnInitMenuPopup() function void CODMenu::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) { if (IsPostCall()) { // iterate any menu about to be displayed and make sure // all the items have the ownerdrawn style set // We receive a WM_INITMENUPOPUP as each menu is //displayed, even if the user // switches menus or brings up a sub menu. This means we only need to // set the style at the current popup level. // we also set the user item data to the index into the menu to allow // us to measure/draw the item correctly later if (pPopupMenu != NULL) { m_menuBeingProcessed = pPopupMenu->m_hMenu; // only valid for measure item calls int itemCount = pPopupMenu->GetMenuItemCount(); for(int item = 0; item < itemCount; item++) { int itemID = pPopupMenu->GetMenuItemID(item); // make sure we do not change the state of the menu items as // we set the owner drawn style MENUITEMINFO itemInfo; memset(&itemInfo, 0, sizeof(MENUITEMINFO)); itemInfo.cbSize = sizeof(MENUITEMINFO); itemInfo.fMask = MIIM_STATE; pPopupMenu->GetMenuItemInfo(item, &itemInfo, TRUE); // by position pPopupMenu->ModifyMenu(item, itemInfo.fState | MF_BYPOSITION | MF_OWNERDRAW, itemID, (LPCTSTR)item); } } } }
System menu(s) do not display correctly
During the debugging phase of this plug-in, I noticed that when we were rendering a system menu, we were drawing the
items incorrectly. After further study I found out that system menus do not seem to work correctly, ever! The code correctly
sets the MF_OWNERDRAWN
bit on every item of the system menu, and we receive the WM_DRAWITEM
message
correctly for all the items, yet we only receive 2 of the WM_MEASUREITEM
messages, when there are 7 items in the
menu.

The items we received the messages for were: Restore and Move. These menu strings are much shorter than Close\tAlt + F4, which is the one item that does not render correctly (in an unmodified system menu).
Well the only way around this problem I could find was a mini-hack! That is we set a member variable flag in the class when we are about to measure the items of a system menu:
m_bSysMenu = (bSysMenu != FALSE);
Then at the end of the MeasureItem()
procedure, we check the flag. If true
and the item we have
just finished measuring is smaller than the width for an item with the text of Close\tAlt + F4 then I just set the width
of the item to this so that the system menu will render correctly.
if (m_bSysMenu) { // solve problem with system menu items which we // do not receive a WM_MEASUREITEM for if (lpMIS->itemWidth < m_minSystemMenuWidth) { // set to minimum width for correct draw lpMIS->itemWidth = m_minSystemMenuWidth; } }

Default items not measured correctly

When I added default menu items, which have the ODS_DEFAULT style in the state member, these items never returned the correct length of the text for these items. The code I had to measure the item was this:
if (state & ODS_DEFAULT) pFont = pDC->SelectObject(&m_menuFontBold); else pFont = pDC->SelectObject(&m_menuFont);// Select menu font in...
After some investigation it turned out that we never receive or can get the correct state information of the menu item in the
OnMeasureItem
function. The easiest way of dealing with this issue was just to always measure all items using the
bold font. This would make standard menu items appear slightly larger, but makes default items render correctly, which
I think users of the class would prefer.
// always measure using the bold version of the font //if (state & ODS_DEFAULT) pFont = pDC->SelectObject(&m_menuFontBold); //else // pFont = pDC->SelectObject(&m_menuFont); // Select menu font in...
Conclusion
This plug-in demonstrates some of the power of the plug-in architecture and provides some standard functionality we as developers like to have in our applications. It fast tracks the development cycle of the end product and does so in a modular fashion.
Along the way, I have illustrated some of the problems I encountered and how I resolved them.
Well that's about it for this plug-in. Its been an interesting journey into menus, and I have also learnt a few nice enumeration functions and understand menus in a much better way. I wrestled with Brents owner drawn menu code - the main problem (from my point of view) being his coding style. Overall the whole project integrates very well.
I hope you have enjoyed reading (and using) this article.
References
Here are a list of related articles used in the making of this plug-in
- An MFC extension library to enable DLL plug-in technology for your application using MESSAGE_MAPs
- Brent Corcums Cool Owner Drawn Menus with Bitmaps - Version 3.03.
Version history
- V1.0 10th May 2004 - Initial release