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.

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.

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:

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.

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 TRACEing 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.

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.

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.

HINSTANCE CDLLWrapper::GetHInstance() returns the HINSTANCE 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!

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:

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.

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

Share

About the Author

A research and development programmer working for a pharmaceutical instrument company for the past 17 years.

I am one of those lucky people who enjoys his work and spends more time than he should either doing work or reseaching new stuff. I can also be found on playing DDO on the Cannith server (Send a tell to "Maetrim" who is my current main)

I am also a keep fit fanatic, doing cross country running and am seriously into [url]http://www.ryushinkan.co.uk/[/url] Karate at this time of my life, training from 4-6 times a week and recently achieved my 1st Dan after 6 years.