Single Class DevStudio-Like Menu Bar

.
Home site www.spunge.com/~alagesan

The latest code will be available at www.spunge.org/~codebee.



Click here for a larger image.

Environment: VC5, Windows NT4 SP3, Windows 9x and above

Single Class Plug & Play DevStudio-Like MFC Menu Bar

CMenuBar is simple Menubar Control that can be integrated into any MDI MFC application by making a few changes in CMainFrame. The simplicity of the CMenuBar is that it uses the Windows Menu system to show a popup menu. It only overrides the CalcDynamicLayout() function to make the Menu Bar resizable docking bar. It simulates all the behaviour of a Windows menu system and menu messages such as WM_MENUCHAR, WM_INITMENU, WM_INITPOPUPMENU, WM_MENUSELECT, and so forth. The MenuBar is UniCode ready.

The CMenuBar class is derived from the CControlBar class. To make it dock and float like a DevStudio Menu Bar, the functions of CControlBar are overridden.

Function Description
Create() Initializes Menu Bar
CalcDynamicLayout() Calculates Menu Bar layout
OnUpdateCmdUI() Checks change of Menu Bar position and size, and to disable the Close button when floating

The CMenuBar subclasses the CMainFrame and MDIClient windows in the SubClassMDI() function, where it also removes the main and refresh menus of CMainFrame to shift it to the MenuBar control.

VOID CMenuBar::SubClassMDI( CWnd* pMainFrame )
{
    g_pMenuBar = this;
    m_pMainFrame = (CMDIFrameWnd*)pMainFrame;

    /// Type cast to retrieve the protected members
    /// m_hWndMDIClient, m_hMenuDefault
    _CMBMDIFrameWnd* _pMainFrame = (_CMBMDIFrameWnd*)pMainFrame;
    HWND hwndClient = _pMainFrame->m_hWndMDIClient;
    HMENU hmnuMDIMainDef = _pMainFrame->m_hMenuDefault;

    /// Subclass MainFrame and MDIClient Windows
    _gs_pfnMDIFrameProc = (WNDPROC)::SetWindowLong
                                     ( m_pMainFrame->m_hWnd,
                                       GWL_WNDPROC,
                                       (DWORD)_MDIFrameProc );
    _gs_pfnMDIClientProc = (WNDPROC)::SetWindowLong
                                      ( hwndClient, GWL_WNDPROC,
                                        (DWORD)_MDIClientProc );

    /// Get Window Popup of main menu
    HMENU hWindowMenu = NULL;
    INT nCnt          = GetMenuItemCount( hmnuMDIMainDef );
    if( nCnt > 2 )
        hWindowMenu = GetSubMenu( hmnuMDIMainDef, nCnt - 2 );

    /// Now activate Menu Bar
    ::SendMessage( hwndClient, WM_MDISETMENU,
                   (WPARAM)hmnuMDIMainDef, (LPARAM)hWindowMenu );

    /// Hide MainFrame main menu
    ::SetMenu( m_pMainFrame->m_hWnd, NULL );

    _gs_hhAppKbHookProc = SetWindowsHookEx
                             ( WH_KEYBOARD, _AppKbHookProc, NULL,
                               ::GetCurrentThreadId() );
}

Each time a menu is being updated by CMainFrame or a MDIChild, the WM_MDISETMENU message (see function _MDIClientProc()) will come to MDIClient, which is trapped and sends a NULL menu handle to DefaultProc() so that the menu will not be visible in MainFrame. Take care to update the menu using only a WM_MDISETMENU message to avoid the unexpected exposure of the Main menu in MainFrame.

The MenuBar uses the menu handle, gotten in the message, to draw menu items. The menu item string and position are stored in m_arpItem, which was calculated by the RecalcItemPos() function.

The menu handle is passed to the CMenuBar::OnSetMDIMenu() function. The OnSetMDIMenu() function should only be called by using PostMessage(), because at the time of the WM_MDISETMENU message, the MDIChild was not activated and will fail to update the Icon and System buttons if maximized.

In the OnSetMDIMenu() function, to calculate menu item(s)' positions, RecalcItemPos() is called. RecalcItemPos() is also called from OnUpdateCmdUI() on the changes of the MDIChild maximize state and MenuBar position.

void CMenuBar::OnUpdateCmdUI( CFrameWnd* pTarget,
                              BOOL bDisableIfNoHndler )
{
    if( NULL == m_pMainFrame )
        return;

    if( _Is_Floating() != IsFloating() )
    {
        if( IsFloating() )
        {
            _Set_Floating();

            /// If menu bar is floating, disble; close button
            /// if enabled
            if( m_pDockBar )
            {
                HMENU hMenu = ::GetSystemMenu( m_pDockBar->
                                               GetDockingFrame()->
                                               m_hWnd, FALSE );
                if( hMenu )
                {
                    INT nState = GetMenuState( hMenu, SC_CLOSE,
                                               MF_BYCOMMAND );
                    if( 0 == (nState & (MF_DISABLED | MF_GRAYED)) )
                        EnableMenuItem( hMenu, SC_CLOSE,
                                        MF_BYCOMMAND |
                                        MF_DISABLED | MF_GRAYED );
                }
            }
        }
        else
            _Reset_Floating();
    }

    if( _Is_LayoutUpdated() )
    {
        /// If Client rectangle differs that was stored,
        /// refresh menu
        CRect rect;
        GetClientRect( rect );

        _Reset_LayoutUpdated();
        InvalidateRect( NULL, FALSE );
        if( rect.Width() != m_cbBarLastSize.cx || rect.Height()
                         != m_cbBarLastSize.cy )
        {
            ResetContent();
            RecalcItemPos();
        }
    }
}

Now, the RecalcItemPos() function is able to find the Menu item position for Vertical docking and the Menu breaks at the item that goes off the Menu Bar's visibility. Those items removed at the Menu break will be shown as popups.

In the vertical docking state, Menu items are first rendered horizontally and their bitmaps rotated to 90 degrees by using the _RotateBmp90() function. See the functions used by _RotateBmp90(). It will interesting, useful, and you'll learn some image processing techinques.

The MainFrame is hooked by the _MDIFrameProc() function to get the WM_ACTIVATEAPP message to deactivate the menu if it is active on the message.

The active ChildFrames is also hooked by _MDIChildProc() to get the WM_WINDOWPOSCHANGED message to change the display Icon and System buttons on the Menu Bar.

Simulation of Keyboard and Mouse Action

The Menu Bar traps the keyboard by using the _AppKbHookProc() hook function. On keybaord messages, _AppKbHookProc() calls the following:

What Is Called When It Is Called
KeyDown() on WM_KEYDOWN
KeyUp() on WM_KEYUP
SysKeyDown() on WM_SYSKEYDOWN
KeyUp() on WM_SYSKEYUP

These functions will return TRUE if they trap the message; otherwise, FALSE to pass to the default target windows.

To simulate horizontal navigation to the next popup menu of a left or right key press, the _MenuKeyBoardHook() keyboard hook function is called from _AppKbHookProc() if _gs_bMenuKbHooked is set. The _MenuKeyBoardHook() function hides the current menu and shows the next popup menu when there's a left or right press and no changes of menu selection.

Like that, the mouse is also hooked by the _MenuMouseHookProc() function to change popup menus on moving the mouse over the next menu item.

How to Integrate

The Menu Bar can be integreted in three simple steps:

  1. Declare a member m_MenuBar for CMenuBar in CMainFrame.
  2. Create a Menu Bar in CMainFrame::OnCreate(), along with Tool Bars.
  3.     int CMainFrame::OnCreate( LPCREATESTRUCT lpCreateStruct )
        {
            if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
                return -1;
    
    
            if( !m_MenuBar.Create( _T("Menu bar") ) )
            {
                TRACE0("Failed to create Menu bar\n");
                return -1;
            }
    
            m_MenuBar.EnableDocking( CBRS_ALIGN_ANY );
    
            ....
    
            EnableDocking(CBRS_ALIGN_ANY);
            DockControlBar( &m_MenuBar );
    
            ....
            ....
    
            return 0;
        }
    
  4. Override CMainFrame::LoadFrame(); call CMenuBar::SubClassMDI() to subclass MainFrame and the MDI Client window.
  5.     BOOL CMainFrame::LoadFrame( UINT nIDResource,
                                    DWORD dwDefaultStyle,
                                    CWnd* pParentWnd,
                                    CCreateContext* pContext )
        {
            if( FALSE == CMDIFrameWnd::LoadFrame( nIDResource,
                                                  dwDefaultStyle,
                                                  pParentWnd,
                                                  pContext ) )
                return FALSE;
    
            m_MenuBar.SubClassMDI( this );
            return TRUE;
        }
    



Comments

  • Couple of fixes needed and it works

    Posted by Legacy on 10/10/2003 12:00am

    Originally posted by: Gary Carter

    Nice job, useful tool.

    I downloaded the project and found there were a couple of fixes needed before it would build:

    1) The .dsp file puts menubar.h and .cpp in a sibling
    directory ..\menubar_src. There is no such directory in the zip file, and the menubar.* files are in the main project dir. So first, edit the .dsp file to change
    SOURCE=..\menubar_src\MenuBar.cpp to say SOURCE=.\MenuBar.cpp. Same for MenuBar.h.

    2) MainFrm.h has an #include <MenuBar.h> at the top. This doesn't work because angle brackets <> tell the compiler to search only the standard include dir. The file is actually in the project dir. So change the <>'s to "".

    3) The CMenuBar::Create () function takes five parameters; the demo only calls it with two. So add some parameters. It would be nice if the CSize were computed for me. I put in CSize (200, 400) and got a way too thick bar.

    Ok, that was three things, not two.

    Now for one more thing that would be nice:

    The menu bar can be docked to the right of a toolbar, saving space by getting two bars on one line. But I would like to put the menu at the left, and it won't let anything dock to the right of it.

    Thanks,

    Gary


    Reply
  • Interesting, but...

    Posted by Legacy on 08/21/2003 12:00am

    Originally posted by: Michal Mecinski

    I compared your article with my article (IEBars) which covers a similar topic.

    One big advantage of your menu bar is that it supports MDI applications and this works very well. And it's dockable, some would tell it's an advantage, others won't (I prefer rebars as they look better and it's quite uncommon to have the menu bar anywhere else than just under the window's title bar :).

    I'm not sure why do you use PreTranslateMessage to capture keyboard input, because you use a keyboard hook anyway. Shouldn't it be enough? And I really don't like the idea of declaring a dummy class to gain access to its protected members... it's much better to add one or two methods to the CMainFrame class, and you have to modify it anyway.

    And there are a few details that need to be fixed. For example, once the menu bar is activated, it remains active even if I click somewhere else, and even if I open a dialog window. Strange :). Besides, when I press cursor lefy/right, the previous/next menu is not activated until I release the key... Well... fixing all those little details takes a lot of time... but that's just a programmer's job :).

    regards,
    Michal

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

Top White Papers and Webcasts

  • This paper introduces IBM Java on the IBM PowerLinux 7R2 server and describes IBM's implementation of the Java platform, which includes IBM's Java Virtual Machine and development toolkit.

  • Get Gartner's NEW Magic Quadrant for Solid-State Arrays. Selecting new storage or just researching? Simplify your vendor evaluation with Gartner's 2014 Magic Quadrant for Solid-State Arrays. This report covers: Strengths & cautions for 12 vendors Assessment of each vendor's completeness of vision and ability to execute Key criteria for evaluating Solid-State Array vendors

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds