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;
switch(message)
{
case WM_CREATE:
MDIClientCreateStruct.hWindowMenu = NULL;
MDIClientCreateStruct.idFirstChild = IDM_FIRSTCHILD;
ghMDIClientArea = CreateWindow(TEXT("MDICLIENT"), NULL, WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
0, 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);
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 classes CSystemInfoData
and CSystemInfoView
, 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 in CSystemInfoData
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();
ghwndMainFrame = CreateWindow(gszSigmaFrameClassName,
gszAppName,
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, ghMainFrameMenu, hInstance, NULL);
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) ;
if (lParam == (LPARAM) hWnd)
{
SendMessage(hwndClient,
WM_MDISETMENU,
(WPARAM) ghSysInfoMenu,
(LPARAM) hSysInfoWindowMenu);
EnableMenuItem(ghSysInfoMenu, ID_INFORMATION_SYSTEMINFORMATION, MF_GRAYED);
}
if (lParam != (LPARAM) hWnd)
{
SendMessage(hwndClient,
WM_MDISETMENU,
(WPARAM) ghMainFrameMenu,
(LPARAM) NULL) ;
}
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:
ghwndStatusBar = CreateWindowEx(0, STATUSCLASSNAME, "", WS_CHILD | WS_VISIBLE,
-100, -100, 10, 10, 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:
RECT rcClient;
LPINT lpParts = NULL;
int nWidth = 0;
int nParts = SIGMA_STATUSBAR_PARTS;
GetClientRect(ghwndMainFrame, &rcClient);
HLOCAL hloc = NULL;
hloc = LocalAlloc(LHND, sizeof(int) * nParts);
lpParts = (int *)LocalLock(hloc);
nWidth = rcClient.right / nParts;
for (int i = 0; i < nParts; i++)
{
lpParts[i] = nWidth;
nWidth += nWidth;
}
SendMessage(ghwndStatusBar, SB_SETPARTS, (WPARAM)nParts, (LPARAM)lpParts);
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:
RECT rectStatusBar;
GetClientRect(ghwndStatusBar, &rectStatusBar); giStatusBarHeight = rectStatusBar.bottom;
MoveWindow(ghMDIClientArea,
0,
0,
rcClient.right, rcClient.bottom - giStatusBarHeight, true);
Text can be set into a particular StatusBar part by calling SendMessage
with SB_SETTEXT
as shown below:
SendMessage(ghwndStatusBar, SB_SETTEXT,
0, (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; 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;
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, MAILSLOT_WAIT_FOREVER, (LPSECURITY_ATTRIBUTES) NULL); .
.
bResult = GetMailslotInfo(ghSlot, (LPDWORD) NULL, &dw_MsgSize, &dw_MsgCount, (LPDWORD) NULL); .
.
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, (LPDWORD) NULL, &dw_MsgSize, &dw_MsgCount, (LPDWORD) NULL); while (dw_MsgCount != 0) {
lpszBuffer = (LPTSTR) GlobalAlloc(GPTR,
dw_MsgSize);
if( NULL == lpszBuffer )
return FALSE;
lpszBuffer[0] = '\0';
bResult = ReadFile(ghSlot,
lpszBuffer,
dw_MsgSize,
&dw_MsgRead,
&ov);
}
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, &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 call DefFrameProc
because calling DefFrameProc
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