Use the Status Bar to Describe How To Enable Menu Commands and Toolbar Buttons

Environment: VC6, W98, NT4

One of the most frustrating scenarios I have as a user is trying to determine how to
enable a disabled menu item. You find the command you want to use, but it is grayed
out with no indication on what you need to do to enable it.

So now as a developer, I put the following code in all of my applications. It uses
the text pane of the status bar (where the “Ready” text appears) to indicate how to
enable disabled menu items when the user moves the cursor over them. It reuses the
ON_UPDATE_COMMAND_UI handlers that you’ve already written to enable/disable menu items.
The only additional step after including these code segments is to add a “\nEnable Text”
to the end of each menu item that indicates how to enable the menu item. For example,

Step 1: Add method declarations to the frame window’s header file

In your CFrameWnd-derived class (e.g. CMainFrame), add the following declaration to the
header file as a protected method:


// This method overrides the CFrameWnd’s handler for the set
// message pane for the status bar. This code was taken from
// CFrameWnd’s method and modified. Code was added to
// determine if the control is enabled or not and if disabled
// then indicate how to enable the control or menu item.

LRESULT OnSetMessageString(WPARAM wParam, LPARAM lParam);

Step 2: Include an MFC header file

Add the following include file to the CFrameWnd-derived implementation file (e.g.
MainFrm.cpp):


#include // for WM_SETMESSAGESTRING and AfxLoadString

Step 3: Add the OnSetMessageString message map

In the CFrameWnd-derived implementation file (e.g. MainFrm.cpp), add the following code
outside of the AFX_MSG_MAP brackets in the message map section (i.e. between
BEGIN_MESSAGE_MAP and END_MESSAGE_MAP):


// Override the setting of the text in the message pane
// of the status bar

ON_MESSAGE(WM_SETMESSAGESTRING, OnSetMessageString)

Step 4: Add the implementation of OnSetMessageString and a helper function

The following code uses the ON_UPDATE_COMMAND_UI handlers already written elsewhere
in the code to determine whether the menu item is enabled or disabled. It gets the
“how to enable” text from the string table for the associated menu item. You will need
to add a third text string for each menu item as detailed in the next step.


// Returns the offset of the given menu item’s ID or -1
// if not found

int GetMenuPosition( CMenu* in_menu, DWORD in_menuItemID )
{
if ( NULL == in_menu )
return -1;

UINT max = in_menu->GetMenuItemCount();
for ( UINT i = 0; i < max; i++ )
if ( in_menuItemID == in_menu->GetMenuItemID( i ) )
return i;

return -1;
}

// This method overrides the CFrameWnd’s handler for the
// set message pane for the status bar. This code was
// taken from CFrameWnd’s method and modified. Code was
// added to determine if the control is enabled or not
// and if disabled then indicate how to enable the control
// or menu item.

LRESULT CMainFrame::OnSetMessageString(WPARAM wParam,
LPARAM lParam)
{
UINT nIDLast = m_nIDLastMessage;
m_nFlags &= ~WF_NOPOPMSG;

// Determine if the menu item that matches the wParam
// is enabled. If not, then indicate how to enable it.
//
// Load the main frame’s menu. Note we can’t use
// GetMenu because the menu may not have been loaded
// yet and we need to indicate how to enable the toolbar
// buttons as well.

CMenu mainMenu;
if ( 0 != wParam && mainMenu.LoadMenu( IDR_MAINFRAME ) )
{
// Go through each submenu of the main frame looking
// for the ID passed in.

UINT max = mainMenu.GetMenuItemCount();
for ( UINT i = 0; i < max; i++ )
{
CMenu* pMenu = mainMenu.GetSubMenu( i );
if ( NULL != pMenu )
{
// We need to build a CCmdUI to be used when
// determining if the item is enabled.

CCmdUI state;
state.m_pMenu = pMenu;
state.m_nIndexMax = pMenu->GetMenuItemCount();
state.m_nIndex = GetMenuPosition( pMenu, wParam );

// If the ID passed in is found in the submenu,
// then have this frame window enable/disable it.

if ( -1 != state.m_nIndex )
{
// First disable the menu item in case it is not
// handled.

pMenu->EnableMenuItem( wParam, MF_GRAYED );

// Invoke the UPDATE COMMAND UI handler for this
// menu item.

this->OnCmdMsg(wParam,
CN_UPDATE_COMMAND_UI,
&state,
NULL);

// Get the state of the menu item.

UINT menuState = pMenu->GetMenuState( wParam,
MF_BYCOMMAND );

// If the menu item is disable or grayed out, then
// get the text to indicate how to enable it.

if ( ( 0xFFFFFFFF != menuState ) &&
( ( MF_DISABLED & menuState ) ||
( MF_GRAYED & menuState ) ) )
{
TCHAR szFullText[256];
CString strEnableText;
CWnd* pMessageBar = GetMessageBar();

// Load the string for this ID. The string after
// the second ‘\n’ indicates how to enable.

AfxLoadString(wParam, szFullText);
AfxExtractSubString(strEnableText,
szFullText,
2,
‘\n’);

// If there is enable text, then set the message
// bar to it, otherwise let the parent class handle it.

if ( 0 != strEnableText.GetLength() )
{
pMessageBar->SetWindowText( strEnableText );

m_nIDLastMessage = (UINT)wParam; // new ID (or 0)
m_nIDTracking = (UINT)wParam; // so F1 on toolbar
// buttons work
return nIDLast;
}
}
}
}
}
}

return CFrameWnd::OnSetMessageString( wParam, lParam );
}

Step 5: Add a \n and “how to enable” text to each menu item

The last step in this process is to open the Resource Editor, select the IDR_MAINFRAME
menu, display the properties for each menu item, and add a backslash-n (\n) and the
“how to enable text” to the end of each Prompt. The “how to enable” text might state
what objects need to be selected in the view to enable that command.

Since toolbar buttons use the same resource IDs as the menu items, there is nothing
additional you need to do to display the “how to enable” text when those buttons are
grayed out.

Downloads

Download demo project – 19 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read