Home made MDI windows list in Window menu

–>

Why we need it.

Windows list is a part of standard MDI windows interface.
There are some bug(s) admitted by Microsoft. See MS KB article
Q125435 for example. It is (almost) impossible to modify it.
It is much easier to reproduce Windows menu behavior. This
project can serve as a start point for window menu
modification. Add something to menu items, graphics for
example, or create a different MoreWindows dialog. To include
invisible MDIChilds to activate them from a menu etc.

How to do this thing.

First of all this example based on MFC application generated by AppWizard.
Modifications are mostly made in MainFrm.cpp,.h module, using wizards when
possible.

1. Remove standard windows list.

Modify all standard Window menu IDs generated by AppWizard.
MDI interface relays on this IDs. So just rename
ID_WINDOW_NEW to ID_WINDOWS_NEW
ID_WINDOW_CASCADE to ID_WINDOWS_CASCADE etc. for all menu items under Window menu.
AppStudio creates new IDs for existing items.
Build and run project to be sure the windows list does not exist any more and standard MDI
commands (New Window, Cascade, Tile…) don’t work. Add the fifth item – separator to the menu.
Add #define NUM_FIXED_ITEMS 5 to MainFrm.cpp

2. Using ClassWizard add message handlers for new menu IDs.

Implement them however you want. For Example mimic a usual behavior.


void CMainFrame::OnWindowsTileHorz()
{
// TODO: Add your command handler code here
MDITile(MDITILE_HORIZONTAL);
}

Now we have reproduced a fixed part of Window menu. Build and try it.

3. Add a protected member function to return CMenu object for Window menu to CMainFrame


CMenu* CMainFrame::GetWindowMenu()
{
// MENU_POSITION is not reliable because it’s different for different views

CMenu* pWindowMenu = NULL;
CMenu* pTopMenu = GetMenu();
CString strMenu;
for (int iPos = pTopMenu->GetMenuItemCount()-1; iPos >= 0; iPos–)
{
pTopMenu->GetMenuString( iPos, strMenu, MF_BYPOSITION );
if (strMenu == “&Window”)
{
pWindowMenu = pTopMenu->GetSubMenu(iPos);
break;
}
}
ASSERT(pWindowMenu != NULL);
return pWindowMenu;
}

Now we are ready to modify the menu.

4. Add new ID to resource.h manually


#define ID_WINDOWS_FIRST 32001
#define ID_WINDOWS_2 32002
#define ID_WINDOWS_3 32003
#define ID_WINDOWS_4 32004
#define ID_WINDOWS_5 32005
#define ID_WINDOWS_6 32006
#define ID_WINDOWS_7 32007
#define ID_WINDOWS_8 32008
#define ID_WINDOWS_LAST 32009
#define ID_WINDOWS_MORE 32010

Actually we need the first and two last. But just in case reserve all of them
Also define a max number of windows in windows list in a menu.
Add #define NUM_WINDOWS_ITEMS 9 to MainFrm.cpp

5. Add a protected member function looping through all visible MDI childs in CMainFrame to
add them to Window menu.


void CMainFrame::UpdateWindowMenu()
{
CMenu* pWindowMenu = GetWindowMenu();

//Remove all entries after separator
for ( int i = pWindowMenu->GetMenuItemCount()- 1 ; i > NUM_FIXED_ITEMS – 1;
pWindowMenu->DeleteMenu(i–, MF_BYPOSITION) );

//m_hWndMDIClient – undocumented

CWnd* pWnd = CWnd::FromHandle(m_hWndMDIClient)->GetWindow(GW_CHILD);

CString strTitle;
i = 0;
char buff[3];

while(pWnd)
{
if(pWnd)
if(pWnd->IsWindowVisible())
{
if (i == NUM_WINDOWS_ITEMS) //Critical number ow windows (9) reached
{
pWindowMenu->AppendMenu( MF_STRING, ID_WINDOWS_MORE, “&Windows…”);
return;
}
pWnd->GetWindowText(strTitle);
strTitle = CString(“&”) + itoa(i + 1,buff,10) + ” ” + strTitle;
pWindowMenu->AppendMenu( MF_STRING, ID_WINDOWS_FIRST + i++,strTitle);
}
pWnd = pWnd->GetWindow(GW_HWNDNEXT);
}

}

OK. Well done with implementation. Now we need to call this function from somewhere.
Not the best but possible place to do it is one of OnUpdateUI for a fixed menu item.


void CMainFrame::OnUpdateWindowsCascade(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
//It is a challenge to find a better place to call it from
UpdateWindowMenu();
}

7. Add a member function to activate a particular MDI child


void CMainFrame::ActivateFrameFromMenu(int nItem)
{
CWnd* pWnd = CWnd::FromHandle(m_hWndMDIClient)->GetWindow(GW_CHILD);
int i = ID_WINDOWS_FIRST;
BOOL bMaximized;

while(pWnd)
{
if(pWnd)
if(pWnd->IsWindowVisible())
if ( i++ == nItem)
{
MDIGetActive( &bMaximized );
if (!bMaximized)
MDIRestore(pWnd);
MDIActivate(pWnd);
return;
}
pWnd = pWnd->GetWindow(GW_HWNDNEXT);
}
}

6. Add message handler for all new menu items.

First we need to handle WM_COMMAND. Using ClassWizard add virtual OnCmdMsg and implement it like:


BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
// If pHandlerInfo is NULL, then handle the message
if (pHandlerInfo == NULL)
{
// Filter the commands sent to Window menu
if (nID >= ID_WINDOWS_FIRST && nID <= ID_WINDOWS_LAST) { if (nCode == CN_COMMAND) // Handle WM_COMMAND message ActivateFrameFromMenu(nID); else if (nCode == CN_UPDATE_COMMAND_UI) // Update UI element state DoUpdateWindowMenu(nID, (CCmdUI*)pExtra); return TRUE; } } // If we didn't process the command, call the base class // version of OnCmdMsg so the message-map can handle the message return CMDIFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); }

We also need a function to update UI for windows list items


void CMainFrame::DoUpdateWindowMenu(int nID, CCmdUI* pCCmdUI)
{
pCCmdUI->Enable(TRUE);
if (nID == ID_WINDOWS_FIRST)
pCCmdUI->SetCheck();
}

7. Add a windows list dialog.

CWinList is a CDialog derived class based on template with a non-sorted listbox and a couple of buttons.
On Init Dialog we call a CMainFrame public function to fill a list box like we did for a menu.


((CMainFrame*) AfxGetMainWnd())->FillWinList(&m_lbWinList);

void CMainFrame::FillWinList(CListBox* pListBox)
{
CWnd* pWnd = m_wndMdiClient.GetWindow(GW_CHILD);
CString strTitle;
while(pWnd)
{
if(pWnd)
if(pWnd->IsWindowVisible())
{
pWnd->GetWindowText(strTitle);
pListBox->AddString(strTitle);
}
pWnd = pWnd->GetWindow(GW_HWNDNEXT);
}
pListBox->SetCurSel(0);
}

OnOk the dialog returns item selected:


void CWinList::OnOK()
{
// TODO: Add extra validation here
EndDialog(m_lbWinList.GetCurSel() + ID_WINDOWS_FIRST);
//CDialog::OnOK(); Do not call default implementation
}

8. Handle ID_WINDOWS_MORE command

The only thing left is to handle ID_WINDOWS_MORE command. There is a little problem here. ClassWizard doesn’t see ID_WINDOWS_MORE command. So either add
a handler manually or temporary add this extra item to any menu and use ClassWizard, then remove it.


void CMainFrame::OnWindowsMore()
{
int nItem = CWinList().DoModal();
if (nItem != IDCANCEL)
ActivateFrameFromMenu(nItem);
}

9. Build. Run. Enjoy!

Download demo project – 21 KB

Last Updated: January 15, 1999

More by Author

Must Read