wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassEx(&wc)) {
MessageBox(0, "Could Not Register Child Window", "Oh Oh…", MB_ICONEXCLAMATION | MB_OK);
return FALSE;
} else return TRUE;
}
This is basically identical to registering our regular frame window, there are no particularly special flags here for use with MDI. We've set the menu as NULL, and the window procedure to point to the child window procedure which we will write next.
MDI Child Procedure
The window procecure for an MDI child is much like any other with a few small exceptions. First of all, default messages are passed to DefMDIChildProc() instead of DefWindowProc().
In this particular case, we also want to disable the Edit and Window menu's when they aren't needed (just because it's a nice thing to do), so we handle WM_MDIACTIVEATE and enable or disable them depending on if our window is getting activated or not. If you have multiple types of child window, this is where you could put code to completely change the menu or toolbar or make alterations to other aspects of the program to reflect the actions and commands that are specific to the type of window being activated.
To be even more complete, we can disable the Close and Save File menu items as well, since they aren't going to be any good with no windows to act on. I've disabled all these items by default in the resource so that I don't need to add extra code to do it when the application first starts up.
LRESULT CALLBACK MDIChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_CREATE:
{
HFONT hfDefault;
HWND hEdit;
// Create Edit Control
hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL, 0, 0, 100, 100, hwnd, (HMENU)IDC_CHILD_EDIT, GetModuleHandle(NULL), NULL);
if (hEdit == NULL) MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK | MB_ICONERROR);
hfDefault = GetStockObject(DEFAULT_GUI_FONT);
SendMessage(hEdit, WM_SETFONT, (WPARAM)hfDefault, MAKELPARAM(FALSE, 0));
}
break;
case WM_MDIACTIVATE:
{
HMENU hMenu, hFileMenu;
UINT EnableFlag;
hMenu = GetMenu(g_hMainWindow);
if (hwnd == (HWND)lParam) { //being activated, enable the menus
EnableFlag = MF_ENABLED;
} else { //being de-activated, gray the menus
EnableFlag = MF_GRAYED;
}
EnableMenuItem(hMenu, 1, MF_BYPOSITION | EnableFlag);
EnableMenuItem(hMenu, 2, MF_BYPOSITION | EnableFlag);
hFileMenu = GetSubMenu(hMenu, 0);
EnableMenuItem(hFileMenu, ID_FILE_SAVEAS, MF_BYCOMMAND | EnableFlag);
EnableMenuItem(hFileMenu, ID_FILE_CLOSE, MF_BYCOMMAND | EnableFlag);
EnableMenuItem(hFileMenu, ID_FILE_CLOSEALL, MF_BYCOMMAND | EnableFlag);
DrawMenuBar(g_hMainWindow);
}
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case ID_FILE_OPEN:
DoFileOpen(hwnd);
break;
case ID_FILE_SAVEAS:
DoFileSave(hwnd);
break;
case ID_EDIT_CUT:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_CUT, 0, 0);
break;
case ID_EDIT_COPY:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_COPY, 0, 0);
break;
case ID_EDIT_PASTE:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_PASTE, 0, 0);
break;
}
break;
case WM_SIZE:
{
HWND hEdit;
RECT rcClient;
// Calculate remaining height and size edit
GetClientRect(hwnd, &rcClient);
hEdit = GetDlgItem(hwnd, IDC_CHILD_EDIT);
SetWindowPos(hEdit, NULL, 0, 0, rcClient.right, rcClient.bottom, SWP_NOZORDER);
}
return DefMDIChildProc(hwnd, msg, wParam, lParam);
default:
return DefMDIChildProc(hwnd, msg, wParam, lParam);
}
return 0;
}
I've implemented the File Open and Save as commands, the DoFileOpen() and DoFileSave() are nearly the same as in previous examples with the ID of the edit control changed, and additionally setting the title of the MDI Child to the filename.
The Edit commands are easy, because the edit control has built in support for them, we just tell it what to do.
Remember I mentioned that there are little things you need to remember or your application will behave strangely? Note that I've called DefMDIChildProc() at the end of WM_SIZE , this is important otherwise the system wont' have a chance to do it's own processing on the message. You can look up DefMDIChildProc() in MSDN for a list of the messages that it processes, and always be sure to pass them to it.
Creating and Destroying Windows
MDI Child windows are not created directly, isntead we send a WM_MDICREATE message to the client window telling it what kind of window we want by setting the members of an MDICREATESTRUCT. You can look up the various members of this struct in your documentation, they are fairly straight forward. The return value from the WM_MDICREATE message is the handle to the newly created window.
HWND CreateNewMDIChild(HWND hMDIClient) {
MDICREATESTRUCT mcs;
HWND hChild;
mcs.szTitle = "[Untitled]";
mcs.szClass = g_szChildClassName;
mcs.hOwner = GetModuleHandle(NULL);
mcs.x = mcs.cx = CW_USEDEFAULT;
mcs.y = mcs.cy = CW_USEDEFAULT;
mcs.style = MDIS_ALLCHILDSTYLES;
hChild = (HWND)SendMessage(hMDIClient, WM_MDICREATE, 0, (LONG)&mcs);
if (!hChild) {
MessageBox(hMDIClient, "MDI Child creation failed.", "Oh Oh…", MB_ICONEXCLAMATION | MB_OK);
}
return hChild;
}
One member of MDICREATESTRUCT that I didn't use that can be quite usefull is the lParam member. This can be used to send any 32bit value (like a pointer) to the child you are creating in order to provide it with any custom information you choose. In the WM_CREATE handler for your child window, the lParam value for the WM_CREATE message will point to a CREATESTRUCT. the lpCreateParams member of that structure will point to the MDICREATESTRUCT you sent along with WM_MDICREATE . So in order to access the lParam value from the Child window you need to do something like this in the child window procedure…