Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

A pure Win32 based MDI application

4.08/5 (12 votes)
25 Jun 2009CPOL5 min read 86.1K   6.4K  
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:

C++
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:

C++
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 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:

C++
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.

C++
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:

C++
//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:

C++
//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:

C++
// 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:

C++
// 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.

C++
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:

  1. Create the mailslot
  2. Get mailslot information
  3. Read mailslot messages

The calls on the client side to get the mailslot messages are shown below:

C++
#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:

  1. Open the mailslot file
  2. Write into the mailslot

The calls required to get this done are shown below:

C++
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 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)