MDI update user interface extension






4.35/5 (8 votes)
Apr 23, 2003
2 min read

82321

1156
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 HWND
s. So I implemented a helper class for getting these classes from HWND
s. 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:
- derive your classes from CRGUpdateUI
- create a WM_MDIACTIVATE handler within your childframe
- forward the messages from childframe to view instead of calling PreTranslateMessage
- add a WM_FORWARD handler to your view
- add your updateui_map and command handlers to the view
It's much simpler than before I think. Hope you like it ;)