Displaying a Window in an MDI application

The R Project is an open source statistics package that can run in MS Windows in either MDI (multiple document interface) or SDI (single document interface) mode. MDI is the mode where there is one big window that contains multiple smaller windows; in SDI each window is separate.

The rgl package for R is a 3D graphics package based on OpenGL. Until very recently, it could only display in SDI mode, but recently Daniel Adler and I made the necessary changes to allow it to be hosted as a child window with R running in MDI mode. I found it hard to track down instructions for doing this, so I've decided to write up what we worked out to be necessary. Hopefully these instructions could be used by others who want to host a window in a foreign MDI application. For example, it would be nice if TCL/TK windows could be hosted within the MDI frame in R.

Necessary support from the application

In Windows, MDI applications have two special windows: the frame and the client. The frame is the parent of the client; the client is the parent of all child windows. For the methods below to work you will need access to the handles of these two windows. If you have the handle to an existing child, you can get them using the GetParent Win32 API call. For example, if consoleHandle is the handle of a child window,
    gMDIClientHandle = GetParent(consoleHandle);
    gMDIFrameHandle  = GetParent(gMDIClientHandle);

Creating the Window

There are two ways to create an MDI child window: the CreateMDIWindow() API call, and sending a WM_MDICREATE message to the client window. The API call is quite similar to the usual CreateWindow() call, so you might as well use that. You would normally use the MDIS_ALLCHILDSTYLES style in addition to other styles, and you pass the client handle rather than a parent window handle. For example,
CreateMDIWindow(
      classname
    , title
    , MDIS_ALLCHILDSTYLES|WS_OVERLAPPEDWINDOW
    , CW_USEDEFAULT, CW_USEDEFAULT
    , width, height
    , gMDIClientHandle
    , GetModuleHandle(NULL)
    , (void *)UserData
    );
Sometime after this call, your window procedure will receive a WM_CREATE message for the child, as with any other window. Your new window will not be automatically added to the list of child windows in the Windows menu, you need to do that yourself. However, we've found that doing it in the WM_CREATE handler doesn't work, so we just set a flag here, and when a WM_PAINT message comes through, we make the calls:
    SendMessage(gMDIClientHandle, WM_MDIREFRESHMENU, 0, 0);    
    DrawMenuBar(gMDIFrameHandle);
Alternatively, you could send the WM_MDISETMENU message to replace the whole menu rather than just updating it.

Getting User Data

To get the UserData sent by CreateMDIWindow, an extra level of indirection is needed. The WM_CREATE message lparam points to a CREATESTRUCT structure; its lpCreateParams member points to a MDICREATESTRUCT structure, whose lParam member is the UserData that was passed to CreateMDIWindow. To extract it, use code like this:
 LPCREATESTRUCT pCreateStruct = (LPCREATESTRUCT)lParam;        
 LPMDICREATESTRUCT pMDICreateStruct = (LPMDICREATESTRUCT)(pCreateStruct->lpCreateParams);
 void *UserData = (void *)pMDICreateStruct->lParam;

Handling Messages

It's important to replace the default window procedure DefWindowProc with DefMDIChildProc to handle messages not handled by your application. In addition to those, you also need to make sure that whether you have handled them or not you pass WM_CHILDACTIVATE, WM_GETMINMAXINFO, WM_MENUCHAR, WM_MOVE, WM_SETFOCUS, WM_SETTEXT, WM_SIZE and WM_SYSCOMMAND to DefMDIChildProc, because these messages also need to be handled by the client or frame windows.

Destroying the Window

In response to a WM_CLOSE message, send a WM_MDIDESTROY message to the client window instead of calling DestroyWindow() directly. This will let it update its list of active children.

Duncan Murdoch

 

Last modified 31 July 2006

 

Back to home page