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


Comments

  • How to add status bar to a dialog based App

    Posted by mwj on 09/08/2004 03:59am

    This article is very good.But my trouble is how to add a status bar to a dialog based application intead of frame window application.

    Reply
  • Minor correction

    Posted by Legacy on 10/18/2001 12:00am

    Originally posted by: Peter Spooner

    Sorry, the #include line should read:

    #include <afxpriv.h> // for WM_SETMESSAGESTRING and AfxLoadString

    Reply
  • Isn't this support built-in?

    Posted by Legacy on 10/05/2001 12:00am

    Originally posted by: Lee Hamel

    I didn't know about this, so I generated a Doc/View MFC app, added a few custom menu commands, and added text to the Prompt field of each. The Prompt appears in the Status Bar without using your code. You did good work, but it seems as if MFC already supports this. Like I said, I never bothered to look at Help for the Prompt field, so now that I know, I will use it ... I could be wrong.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • On-demand Event Event Date: October 23, 2014 Despite the current "virtualize everything" mentality, there are advantages to utilizing physical hardware for certain tasks. This is especially true for backups. In many cases, it is clearly in an organization's best interest to make use of physical, purpose-built backup appliances rather than relying on virtual backup software (VBA - Virtual Backup Appliances). Join us for this webcast to learn why physical appliances are preferable to virtual backup appliances, …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds