Many times, I have stumbled across questions regarding using multiple views shown in one frame without a splitter window. Therefore, I decided to write a small application showing how that can be done.
The sample application uses a fairly unknown message: WM_SIZEPARENT declared in afxpriv.h. But first, see how MFC frame windows maintain the child windows' layout. The WM_SIZE handler implementation calls RecalcLayout. In turn, RecalcLayout calls RepositionBars. Do not let the function name full you; it actually should be called RepositionChildren because this is exactly what that function does. It enumerates all child windows and sends a message to resize all.
To enumerate all child windows, it calls GetNextWindow after retrieving top child—in other words, the child that is first in the Z order; so, the search starts with the first child window that is created and goes down to the last one. To resize windows, the function uses BeginDeferWindowPos, DeferWindowPos, and EndDeferWindowPos; they resize all children at the same time, thus reducing flickering. For each child window, RepositionBars sends the above-mentioned WM_SIZEPARENT message to allow each child to decide what area to occupy and add itself to a DeferWindowPos path.
There is one exception. The view with the magic AFX_IDW_PANE_FIRST ID is put aside until the end, and resized after all other children are already positioned, taking whatever real estate is left for it. By the way, the AFX_IDW_PANE_FIRST ID is used by the default implementation of the RecalcLayout. RecalcLayout can be overridden and other IDs can be designated as the special one.
How do other child windows know that certain areas of the parent's client have already been taken by other children? MFC is using its own structure to carry that information from the first child to the last one: AFX_SIZEPARENTPARAMS. MSDN is quite shy about documenting it. It has the following members:
- HDWP hDWP
- RECT rect
- SIZE sizeTotal
- BOOL bStretch
- hDWP: A handle returned by BeginDeferWindowPos
- rect: A rectangle that represents the parent's client area trimmed by other children
Basically, it is an area that is available for taking the following:
- sizeTotal: Represents the size of the child window (width and height) after repositioning.
- bStreech: Tells the window to stretch itself to take all available space. Can be ignored by non-toolbar windows.
How this structure is used is totally up to the implementation.
View windows should be created after all toolbars and the status bar are created (remember the Z order). The best place to do this is in the WM_CREATE message handler. A child window with AFX_IDW_PANE_FIRST can be created anytime.
How to Use the AFX_SIZEPARENTPARAMS Structure
When the frame window is created, it receives the WM_CREATE message. Before toolbars are created, the base class calls OnCreateClient where a view registered with the document template is created. After it returns, it is time to create control bars if desired. I have included a child toolbar and commented out a part of the code that creates a status bar. Uncomment it if you want to see status bar too.
After the control bars are done, I placed a call to CreatePanes to creates other views. To simplify, I added two views. This order assures that the WM_SIZEPARENT message will be sent in the proper order. This sample only illustrates how to use this message and the AFX_SIZEPARENTPARAMS structure. Each layout design will require a different approach; however, it is very similar to what is presented here.
After the control bars have decided what part of the client area of the frame window to take, the framework sends a WM_SIZEPARENT message to the first additional pane (CTitleFormView) that serves as a kind of title. The height of this window does not change and is calculated in the view's OnCreate handler. In sizing the message handler, the window takes half of the width of the available client area and sets its own heights; it uses a lpLayout structure to pass those values to the next window and sets the sizeTotal member of the structure to own rectangle size. After all calculations, the view calls AfxRepositionWindow that calls DeferWindowPos, adding the view's parameters to a defer path. If the handle to the defer window position is null, it only performs calculations without calling AfxRepositionWindow.
Now, a message is sent to a CDisplayView. It uses values from the lpLayout structure to position itself in the lower part of the right half of the client area and adjusts the rect member of the structure to show the leftover area that will be taken by the main view—AFX_IDW_PANE_FIRST. As a result of passing the document pointer when panes were created, all views share the same document object; therefore, UpdateAllViews can be called to update all panes. It happens upon the list view selection change. That is a reminder showing how the doc/view architecture works.
There is no additional class that could be included in other projects. This article serves as an example, showing how to use WM_SIZEPARENT message as well as explaining (documenting) the AFX_SIZEPARENTPARAMS structure.