Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MDI update user interface extension

0.00/5 (No votes)
30 Apr 2003 1  
A WTL MDI update user interface extension

Introduction

Mostly you only have one menu, one toolbar and one status bar. Now you want to update these within your views, which are child windows of CMDIChildWindow. CUpdateUI doesn't work with these constructs. So I thought about an implementation, which can handle menu updates from view classes.

Background

We can have update handlers within the mainframe and within each view. How do we know which handler to call? Because the CUpdateUIBase class doesn't have members, which will work with these extensions, I implemented a new update handler class called CRGUpdateUIBase.

We also need to know the classes for the HWNDs. So I implemented a helper class for getting these classes from HWNDs. It's a little bit like in MFC.

Implementation

class CWndHandleMap
{
        typedef CAtlMap<HWND CWindow,*> CHandleMap;
        CHandleMap        m_map;
public:
        static CWndHandleMap& GetHandleMap();
        CWindow* FromHandle( HWND hWnd);
        void Add( CWindow* pWnd);
        void Remove( HWND hWnd);
};
///// in source code file

CWndHandleMap& CWndHandleMap::GetHandleMap()
{
        static CWndHandleMap mapStatic;  // the only one handle map object

        return mapStatic;
}
CWindow* CWndHandleMap::FromHandle( HWND hWnd)
{
        CWindow* pWnd;
        if( m_map.Lookup( hWnd, pWnd))
                return pWnd;
        return NULL;
}
void CWndHandleMap::Add( CWindow* pWnd)
{
        ATLASSERT( pWnd->IsWindow());
        if( pWnd->IsWindow())
                m_map.SetAt( pWnd->m_hWnd, pWnd);
}
void CWndHandleMap::Remove( HWND hWnd)
{
        m_map.RemoveKey( hWnd);
}

Now some code snippets from CRGUpdateUIBase. At first CRGUpdateUIBase uses maps for faster finding of command IDs.

// element data

struct _AtlUpdateUIElement
{
    HWND m_hWnd;
    WORD m_wType;
    // compare operator for searching UI elements

    bool operator==(const _AtlUpdateUIElement& e) const
    { return ((m_hWnd == e.m_hWnd) && (m_wType == e.m_wType)); }
};
// instance data

struct _AtlUpdateUIData
{
    WORD m_wType;
    WORD m_wState;
    void* m_lpData;
    _AtlUpdateUIData( WORD wType) : m_wType( wType),
              m_lpData( NULL), m_wState( wType)
    {}
    ~_AtlUpdateUIData()
    {
        free( m_lpData);
    }
};
// dynamic map which adds and removes entries on MDI (de)activation

CAtlMap<WORD _AtlUpdateUIData,*> m_UIUpdateMap; // only hold pointers

// the static map initialized once

CMapToAutoPtr<WORD _AtlUpdateUIData,>  m_UITempMap; // calls delete automatically

The next 2 functions are used when the currently active window changes

// this function initializes the update map with the static update data

void Init()
{
    // remove all appended ui elements

    m_pAppended = NULL;
    for( int i= m_UIElements.GetSize()-1;i>m_nAppend; i--)
        m_UIElements.RemoveAt( i);
    m_nAppend = -1;

    if( m_UITempMap.GetCount())
    {
        m_UIUpdateMap.RemoveAll();
        for( POSITION pos= m_UITempMap.GetStartPosition(); pos;
        )
            { 
WORD wKey;
_AtlUpdateUIData* puiData;
m_UITempMap.GetNextAssoc( pos, wKey,puiData);
puiData->m_wState |= puiData->m_wType; // force update m_UIUpdateMap.SetAt( wKey, puiData); } } // first initialization call else for( const _AtlUpdateUIMap* pMap= m_pUIMap; pMap->m_nID!=(WORD)-1; pMap++) { _AtlUpdateUIData* pData = new _AtlUpdateUIData(pMap->m_wType);
m_UITempMap[pMap->m_nID].Attach(pData);
m_UIUpdateMap[pMap->m_nID] = pData; } // force update m_wDirtyType |= UPDUI_MENUBAR | UPDUI_CHILDWINDOW | UPDUI_STATUSBAR | UPDUI_TOOLBAR; } // appending the currently active update data void Append( CRGUpdateUIBase* updateBase) { m_pAppended = updateBase; m_nAppend = m_UIElements.GetSize()-1; // appending UI elements for( int i=0; i<UPDATEBASE->m_UIElements.GetSize(); i++) // only add elements not found if( m_UIElements.Find( updateBase->m_UIElements[i]) == -1) m_UIElements.Add( updateBase->m_UIElements[i]); // append to the dynamic update map for( POSITION pos=updateBase->m_UIUpdateMap.GetStartPosition(); pos; ) { WORD wKey; _AtlUpdateUIData* pUIData; updateBase->m_UIUpdateMap.GetNextAssoc( pos, wKey, pUIData); pUIData->m_wState |= pUIData->m_wType; // force update m_UIUpdateMap.SetAt( wKey, pUIData); } // force update m_wDirtyType |= UPDUI_MENUBAR | UPDUI_CHILDWINDOW | UPDUI_STATUSBAR | UPDUI_TOOLBAR; }

Using the code

First, derive your classes which have update handlers from CRGUpdateUI<> and chain messages like it is done with CUpdateUI<>.

class CMainFrame : // ...

                  public CRGUpdateUI<CMAINFRAME>
{
public:
  BEGIN_MSG_MAP(CMainFrame)
    // some messages...

    CHAIN_MSG_MAP(CRGUpdateUI<CMAINFRAME>)
    // better message routing

    if( uMsg == WM_COMMAND)
    {
        HWND hWnd = MDIGetActive();
        if( hWnd)
        {
            CChildFrame* pChild =
              (CChildFrame*)CWndHandleMap::GetHandleMap().FromHandle( hWnd);
            // Update because MDI child windows doesn't

            // handle command messages

            if( pChild)
            {
                hWnd = pChild->GetActiveView();  // couldn't be NULL

                CMyView* pView =
                     (CMyView*)CWndHandleMap::GetHandleMap().FromHandle( hWnd);
                if( pView && pView->ProcessWindowMessage( hWnd,
                     uMsg, wParam, lParam, lResult))
                    return TRUE;
            }
        }
        // COMMAND_ID_HANDLERs whithin CMainFrame

    }
  END_MSP_MAP()
};

Because each HWND should have an entry within the handle map, we need to add and remove it.

LRESULT CChildFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/,
                                  LPARAM lParam, BOOL& bHandled)
{
    LRESULT lRes = DefWindowProc();  // first create the window

    CWndHandleMap::GetHandleMap().Add( this);
    // ...

    return lRes;
}

LRESULT CChildFrame::OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/,
                                 LPARAM /*lParam*/, BOOL& bHandled)
{
    bHandled = FALSE;
    CWndHandleMap::GetHandleMap().Remove( m_hWnd);
    return 0;
}

Next we need to notify our main window when an MDI child window has been activated or deactivated.

LRESULT CChildFrame::OnMDIActivate(UINT uMsg, WPARAM wParam,
                                LPARAM lParam, BOOL& bHandled)
{
    LRESULT lRes = DefWindowProc();
    bHandled = FALSE;
    MSG msg;
    msg.hwnd = ((HWND)lParam == m_hWnd) ? GetActiveView() : NULL;
    msg.lParam = lParam;
    msg.message = uMsg;
    msg.wParam = wParam;
    ::SendMessage( GetMainWnd(), WM_FORWARDMSG, 0, (LPARAM)&msg);
    return lRes;
}

The MDI activation is handled like the following:

 BOOL CMainFrame::PreTranslateMessage(MSG*
pMsg)
    { //

    ... if(pMsg->message == WM_MDIACTIVATE)
    {
        HWND hWnd = pMsg->hwnd;
        if( hWnd) // activated

        {
            // append ui handlers

            CMyView* pView =
                (CMyView*)CWndHandleMap::GetHandleMap().FromHandle( hWnd);
            ATLASSERT( pView);
            CRGUpdateUI<CMAINFRAME>::Append( pView);
        }
        else  // deactivated

        {
            // remove appended ui handler

            CRGUpdateUI<CMAINFRAME>::Init();
        }
    }
    return FALSE;
}

Last but not least implement your update handlers within your views.

class CMyView :  // ...

        public CRGUpdateUI<CDPPVIEW>
{
public:
BEGIN_UPDATE_UI_MAP(CMyView)
  // ...

END_UPDATE_UI_MAP()

};

Update V1.1: Using the code without a handle map

In this new version the mainframe doesn't need to know the type of the active view. If we want to update the updateui_map within the mainframe, our childframes have to do this within the WM_MDIACTIVATE handler or we could send a message to the mainframe. Please look at the sample code.

Now there are only a few changes:

  1. derive your classes from CRGUpdateUI
  2. create a WM_MDIACTIVATE handler within your childframe
  3. forward the messages from childframe to view instead of calling PreTranslateMessage
  4. add a WM_FORWARD handler to your view
  5. add your updateui_map and command handlers to the view

It's much simpler than before I think. Hope you like it ;)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here