A pure Win32 based MDI application






4.08/5 (12 votes)
Explains MDI support in Windows with a simple example.
Introduction
This article explains how to create a basic MDI application using pure Win32. The MDI child window created as part of this sample shows the following basic set of information about the system: computer name, OS version and service pack, and the CPU count.
Additional features in this article are:
- Updating menu on opening child window
- Creating multi-part status bar
- Adding and removing Systray Icon
- Inter-process communication using Mailslot
Pure Win32 Based MDI Creation
The MDI frame window is created in WinMain()
. In the WM_CREATE
of the MDI frame window, the MDI client area is created as follows. All the child windows will be floating around in this client area:
CLIENTCREATESTRUCT MDIClientCreateStruct;
// Structure to be used for MDI client area
switch(message)
{
case WM_CREATE:
// On creation of main frame, create the MDI client area
MDIClientCreateStruct.hWindowMenu = NULL;
MDIClientCreateStruct.idFirstChild = IDM_FIRSTCHILD;
ghMDIClientArea = CreateWindow(TEXT("MDICLIENT"), // predefined value for
// MDI client area
NULL, // no caption required
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
0, // No need to give any x/y or height/width.
0,
0,
0,
hwnd,
NULL,
ghInstance,
(void*) &MDIClientCreateStruct);
MDICLIENT is used as the first argument and is pre-defined as part of the Windows MDI support. The variable of type CLIENTCREATESTRUCT
is passed as a pointer, as the last parameter of CreateWindow
. CLIENTCREATESTRUCT
has two elements: one is the handle to the application window’s menu, and the second one represents the starting identifier for the first child window created (following windows will have this incremented).
The MDI child window which shows the system information is created on getting the command message ID_INFORMATION_SYSTEMINFORMATION
in the main frame window proc of SigmaFrameWndProc
. Here, a CSystemInfo
object is created on the heap. This g_pSystemInfo
object is then used to create the window and fill in the tree control.
CSystemInfo::CreateSystemInfoWindow()
registers the child window and creates the MDI child window. The code snippet is shown below:
MDICREATESTRUCT MDIChildCreateStruct;
MDIChildCreateStruct.szClass = TEXT("SigmaSystemInfoWnd");
MDIChildCreateStruct.szTitle = TEXT("System Information");
MDIChildCreateStruct.hOwner = ghInstance;
MDIChildCreateStruct.x = CW_USEDEFAULT;
MDIChildCreateStruct.y = CW_USEDEFAULT;
MDIChildCreateStruct.cx = CW_USEDEFAULT;
MDIChildCreateStruct.cy = CW_USEDEFAULT;
MDIChildCreateStruct.style = 0;
MDIChildCreateStruct.lParam = 0;
//
m_hwndSystemInformation = (HWND) SendMessage(ghMDIClientArea,
WM_MDICREATE,
0,
(LPARAM) (LPMDICREATESTRUCT) &MDIChildCreateStruct);
// return if its not possible to create the child window
if(NULL == m_hwndSystemInformation)
{
return 0;
}
MDICREATESTRUCT
is used to set the child window parameters. This is passed as the last parameter of SendMessage
. SendMessage
is used to send a WM_MDICREATE
message to the client area of the main frame window.
Classes and their Functionality
CSystemInfo
– This class holds the member elements representing the Data and View by means of the classesCSystemInfoData
andCSystemInfoView
, respectively. A child window is created in this class.CSystemInfoData
– This stores the data that should be shown in the tree control.CSystemInfoView
– This class is used to initialize the tree control and fill in the data that is available inCSystemInfoData
into the tree control.
Creating the Menu and Changing the Menu when Child Window is Active
Frame window menu is represented by the resource: IDR_MAINFRAME_MENU
. This menu is created and loaded by following call in WinMain
itself during the creation of mainframe window as highlighted below:
ghMainFrameMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MAINFRAME_MENU));
ghSysInfoMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_SYSINFO_MENU));
DWORD derror = GetLastError();
//Create the main MDI frame window
ghwndMainFrame = CreateWindow(gszSigmaFrameClassName,
gszAppName,
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, // allows system choose an x position
CW_USEDEFAULT, // allows system choose a y position
CW_USEDEFAULT, // width, CW_USEDEFAULT allows system to
// choose height and width
CW_USEDEFAULT, // height, CW_USEDEFAULT ignores heights
// as this is set by setting
// CW_USEDEFAULT in width above.
NULL, // handle to parent window
ghMainFrameMenu, // handle to menu
hInstance, // handle to the instance of module
NULL); // Long pointer to a value to be passed
// to the window through the
// CREATESTRUCT structure passed in the
// lParam parameter the WM_CREATE message
Child window menu is represented by IDR_SYSINFO_MENU
. Under the WM_MDIACTIVATE
of child window proc (SigmaSystemInfoWndProc
), the code to modify the Child window menu and Window menu is added. When the focus is lost from open child window, it will set the frame window menu in below code. For this activation and deactivation to be visible, there should be more than one child window. DrawMenuBar()
needs to be called once SendMessage()
with WM_MDISETMENU
is called. Once the child window is opened, its respective menu item is grayed out by calling EnableMenuItem()
below.
case WM_MDIACTIVATE:
{
HWND hwndClient = GetParent(hWnd);
HWND hwndFrame = GetParent(hwndClient);
HMENU hSysInfoWindowMenu = GetSubMenu(ghSysInfoMenu, SIGMA_SYSINFO_WINDOW_MENU_POS) ;
// Set the system info menu when getting activated
if (lParam == (LPARAM) hWnd)
{
SendMessage(hwndClient,
WM_MDISETMENU,
(WPARAM) ghSysInfoMenu,
(LPARAM) hSysInfoWindowMenu);
//Gray out the system information menu item
EnableMenuItem(ghSysInfoMenu, ID_INFORMATION_SYSTEMINFORMATION, MF_GRAYED);
}
// Set the frame window menu when losing focus
if (lParam != (LPARAM) hWnd)
{
SendMessage(hwndClient,
WM_MDISETMENU,
(WPARAM) ghMainFrameMenu,
(LPARAM) NULL) ;
}
// call DrawMenuBar after the menu items are set
DrawMenuBar(hwndFrame);
Status Bar and Re-Arranging MDI Client Area
Status Bar is created by calling CreateStatusBar()
in WinMain()
. Create the status bar by calling CreateWindowEx
with the class name of STATUSCLASSNAME
as shown below:
//Create the status bar
ghwndStatusBar = CreateWindowEx(0, // extended not required
STATUSCLASSNAME, // status bar class name, equivalent to
// "msctls_statusbar32"
"", //caption not required
WS_CHILD | WS_VISIBLE,
-100, // x
-100, // y
10, // width
10, // height
ghwndMainFrame,
NULL,
(HINSTANCE) GetWindowLong (ghwndMainFrame, GWL_HINSTANCE),
NULL);
Partition the status bar into three different parts by calling SendMessage(ghwndStatusBar
, SB_SETPARTS, (WPARAM)nParts, (LPARAM)lpParts). Exact call sequences are detailed below:
//Create parts to the status bar
RECT rcClient;
LPINT lpParts = NULL;
int nWidth = 0;
int nParts = SIGMA_STATUSBAR_PARTS;
// Get the coordinates of the parent window's client area.
GetClientRect(ghwndMainFrame, &rcClient);
// Allocate an array for holding the right edge coordinates.
HLOCAL hloc = NULL;
hloc = LocalAlloc(LHND, sizeof(int) * nParts);
lpParts = (int *)LocalLock(hloc);
// Calculate the right edge coordinate for each part, and
// copy the coordinates to the array.
nWidth = rcClient.right / nParts;
for (int i = 0; i < nParts; i++)
{
lpParts[i] = nWidth;
nWidth += nWidth;
}
// Create status bar parts.
SendMessage(ghwndStatusBar, SB_SETPARTS, (WPARAM)nParts, (LPARAM)lpParts);
// Free the array
LocalUnlock(hloc);
LocalFree(hloc);
Once the status bar is created, re-arrange the client area so that the client area does not overwrite the status bar. This needs to be done on frame window resize also. In the frame window WM_SIZE
handling, do not call DefFrameProc
because calling DefFrameProc
will cause the MDI client area to be resized and overwrite the status bar:
// re-arrange the client area so that the status bar is always visible
RECT rectStatusBar;
GetClientRect(ghwndStatusBar, &rectStatusBar); // get status bar client area
giStatusBarHeight = rectStatusBar.bottom;
MoveWindow(ghMDIClientArea,
0,
0,
rcClient.right, //width
rcClient.bottom - giStatusBarHeight, //height
true);
Text can be set into a particular StatusBar part by calling SendMessage
with SB_SETTEXT
as shown below:
// Set a message in first part (zero based index) of the status bar
// Each part can have maximum of 127 characters
SendMessage(ghwndStatusBar, SB_SETTEXT,
0, // Part 1 of status based on Zero based index
(LPARAM)"Application started, no error detected.");
Adding Systray Icon
Systray Icon is created by calling AddSysTrayIcon()
in WinMain()
. Shell_NotifyIcon()
is called to add the icon to system tray.
NOTIFYICONDATA nid = {0};
nid.cbSize = sizeof(nid);
nid.uID = SIGMA_SYSTRAY_ICON_ID; // 0 to 12 are reserved and should not be used.
nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
nid.hIcon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_SIGMA_MAIN_ICON));
strcpy(nid.szTip, "Sigma");
nid.hWnd = ghwndMainFrame;
nid.uCallbackMessage = WM_SIGMA_SYSTRAY_MSG;
//Add the notification to the tray
Shell_NotifyIcon(NIM_ADD, &nid);
Systray icon is removed while closing the application by calling Shell_NotifyIcon
(
NIM_DELETE, &nid)
.
IPC using Mailslot
Mailslot is an interprocess communication mechanism. The way it works is that one process will open a mail slot and the second process writes its message into it. It is up to the first process to read the message that written into the mailslot by the second process. In this sample, mailslot is setup by calling SetupMailslot()
in WinMain()
. To see the mailslot communication in action, please register the service from another of my article. This service article is available at: SigmaService.aspx . The service when started will update this client application continuously in the third part of the status bar. Service updates the status bar with exact status of the service.
The following are the steps required to get the messages in mailslot:
- Create the mailslot
- Get mailslot information
- Read mailslot messages
The calls on the client side to get the mailslot messages are shown below:
#define MAILSLOTNAME \\\\.\\mailslot\\sigmamain
.
.
ghSlot = CreateMailslot(TEXT(MAILSLOTNAME),
0, // no maximum message size
MAILSLOT_WAIT_FOREVER, // no time-out for operations
(LPSECURITY_ATTRIBUTES) NULL); // default security
.
.
bResult = GetMailslotInfo(ghSlot, // mailslot handle
(LPDWORD) NULL, // no maximum message size
&dw_MsgSize, // size of next message
&dw_MsgCount, // number of messages
(LPDWORD) NULL); // no read time-out
.
.
// if there are any messages then we read the mailslot
DWORD dw_MsgSize = 0;
DWORD dw_MsgCount = 0;
DWORD dw_MsgRead = 0;
LPTSTR lpszBuffer;
BOOL bResult;
HANDLE hEvent;
OVERLAPPED ov;
hEvent = CreateEvent(NULL, FALSE, FALSE, TEXT("SigmaMailSlot"));
ov.Offset = 0;
ov.OffsetHigh = 0;
ov.hEvent = hEvent;
bResult = GetMailslotInfo(ghSlot, // mailslot handle
(LPDWORD) NULL, // no maximum message size
&dw_MsgSize, // size of next message
&dw_MsgCount, // number of messages
(LPDWORD) NULL); // no read time-out
while (dw_MsgCount != 0) // retrieve all messages
{
// memory for the message.
lpszBuffer = (LPTSTR) GlobalAlloc(GPTR,
dw_MsgSize);
if( NULL == lpszBuffer )
return FALSE;
lpszBuffer[0] = '\0';
bResult = ReadFile(ghSlot,
lpszBuffer,
dw_MsgSize,
&dw_MsgRead,
&ov);
//Write or display the message in lpszBuffer
}
//Call GetMailSlotInfo() again and repeat the above loop to get further messages.
To post messages into a mailslot the following steps are required:
- Open the mailslot file
- Write into the mailslot
The calls required to get this done are shown below:
LPTSTR lpszSlotName = TEXT("\\\\.\\mailslot\\sigmamain");
.
.
g_hFile = CreateFile(lpszSlotName,
GENERIC_WRITE,
FILE_SHARE_READ,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
.
.
bResult = WriteFile(g_hFile,
lpszMessage,
(DWORD) lstrlen(lpszMessage) + 1, // add null termination
&dw_MsgWrittenLen,
(LPOVERLAPPED) NULL);
Additional Notes
InitCommonControlsEx()
is called to initialize the common controls. This is required for the tree control to be created.- In the Frame window
WM_SIZE
handling, do not callDefFrameProc
because callingDefFrameProc
will cause the MDI client area to be resized and overwrite the status bar. - This application had been tested only on XP SP3.
Environment
VC++ 6.0, UNICODE, C++, and XP SP3.
History
18th May 2009:
- Updated with following additional features:
- Updating menu on opening child window
- Creating multi-part status bar
- Adding and removing Systray Icon
- Inter-process communication using Mailslot