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);
};
CWndHandleMap& CWndHandleMap::GetHandleMap()
{
static CWndHandleMap mapStatic;
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.
struct _AtlUpdateUIElement
{
HWND m_hWnd;
WORD m_wType;
bool operator==(const _AtlUpdateUIElement& e) const
{ return ((m_hWnd == e.m_hWnd) && (m_wType == e.m_wType)); }
};
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);
}
};
CAtlMap<WORD _AtlUpdateUIData,*> m_UIUpdateMap;
CMapToAutoPtr<WORD _AtlUpdateUIData,> m_UITempMap;
The next 2 functions are used when the currently active window changes
void Init()
{
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;
m_UIUpdateMap.SetAt( wKey, puiData);
}
}
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;
}
m_wDirtyType |= UPDUI_MENUBAR | UPDUI_CHILDWINDOW |
UPDUI_STATUSBAR | UPDUI_TOOLBAR;
}
void Append( CRGUpdateUIBase* updateBase)
{
m_pAppended = updateBase;
m_nAppend = m_UIElements.GetSize()-1;
for( int i=0; i<UPDATEBASE->m_UIElements.GetSize(); i++)
if( m_UIElements.Find( updateBase->m_UIElements[i]) == -1)
m_UIElements.Add( updateBase->m_UIElements[i]);
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;
m_UIUpdateMap.SetAt( wKey, pUIData);
}
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)
CHAIN_MSG_MAP(CRGUpdateUI<CMAINFRAME>)
if( uMsg == WM_COMMAND)
{
HWND hWnd = MDIGetActive();
if( hWnd)
{
CChildFrame* pChild =
(CChildFrame*)CWndHandleMap::GetHandleMap().FromHandle( hWnd);
if( pChild)
{
hWnd = pChild->GetActiveView();
CMyView* pView =
(CMyView*)CWndHandleMap::GetHandleMap().FromHandle( hWnd);
if( pView && pView->ProcessWindowMessage( hWnd,
uMsg, wParam, lParam, lResult))
return TRUE;
}
}
}
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 , WPARAM ,
LPARAM lParam, BOOL& bHandled)
{
LRESULT lRes = DefWindowProc();
CWndHandleMap::GetHandleMap().Add( this);
return lRes;
}
LRESULT CChildFrame::OnDestroy(UINT , WPARAM ,
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)
{
CMyView* pView =
(CMyView*)CWndHandleMap::GetHandleMap().FromHandle( hWnd);
ATLASSERT( pView);
CRGUpdateUI<CMAINFRAME>::Append( pView);
}
else
{
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 ;)