Stingray® Foundation : Chapter 7 Layout Manager : Integration with ATL
Integration with ATL
The composite-based layout tree described above is framework-independent. However, at some point this functionality needs to be integrated into the behavior of the desired window. This part of the package relies on the message handling mechanism of the framework being used. An ATL integration layer is included with SFL.
To add layout management to a window in ATL you need to:
1. Include the templated class CLayoutManager<> among the base classes of your window.
2. Delegate messages to this base class.
3. Override the InitLayout() method to initialize your layout logic.
The CLayoutManager class takes as its first template parameter the name of the most-derived class. The second template parameter is the creation message that triggers the layout logic initialization. In general, two messages are used for this purpose: WM_INITDIALOG for dialog boxes and WM_CREATE for all other classes of windows. Example 50 shows the use of WM_CREATE.
Example 50 – Initializing window layout logic
class CMyWindow:
public CWindowImpl<CMyWindow>,
public CLayoutManager<CMyWindow, WM_CREATE>
You must delegate the messages your window receives to the LayoutManager base, otherwise the layout manager will not be able to trigger some of the processes necessary for the layout logic to work. Your window can process the messages it is interested in first. But, if your window processes the following messages:
WM_SIZE
WM_GETMINMAXINFO
WM_ERASEBKGND
WM_PAINT
you need to make sure that you do not stop their routing in your window’s message map, so they eventually reach the CLayoutManager message map.
In addition to the previous messages, the individual layout node classes might need some other messages to be passed to them.
NOTE >> Do not stop the routing of messages, except when you are absolutely sure you do not want further processing of a particular message.
In ATL, the routing of a Windows message is stopped if the bHandled parameter is set to TRUE in the message map. This is done by default by all the ATL macros similar to MESSAGE_HANDLER(). SFL offers an alternative macro for use in your message maps, MESSAGE_HANDLER_DELEGATE(). This message map entry differs from the traditional MESSAGE_HANDLER only in that it does not set the bHandled parameter to TRUE, and therefore allows processing of a message without stopping its routing to the base classes.
The message map in an ATL CWindowImpl-derived window class with layout management should look like Example 51.
Example 51 – Message map in a window class with layout management
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER_DELEGATE(WM_CREATE, OnCreate)
MESSAGE_HANDLER_DELEGATE(WM_PAINT, OnPaint
CHAIN_MSG_MAP(CLayoutManager<CMyWindow, WM_CREATE>)
END_MSG_MAP()
The third step is to override the InitLayout() virtual method inherited from your CLayoutManager base class. This method is called during the initialization process that takes place in the creation message (WM_CREATE or WM_INITDIALOG), hence the importance of always delegating this message.
InitLayout() receives one single parameter, of type ILayoutNode*, which is the root node of your layout tree. If this parameter is NULL, it is the responsibility of your window to create the node before going any further. This will usually be the case, unless the root node is created in a base class located in the inheritance chain between your final window and the CLayoutManager class. In that case, you should use the root node handed to you and just create all the additional nodes your window needs.
Always call your base class’ version of InitLayout(), so the initialization process can be completed. Later in the examples section you will see some instances of how to override this method.
Besides integration with the ATL message routing mechanism, CLayoutManager offers some shortcut functions for creation and initialization of layout nodes. As outlined previously, the normal process of creation and initialization of a node is as follows:
1. Create a node instance using the class factory.
2. Call Init().
3. Add it to the layout tree by calling the parent’s AddLayoutNode().
These three steps must be performed in that order, unless the specific interface of the parent node requires you to add the nodes in some other fashion, as in the case of the splitter class.
CLayoutManager provides some routines that encapsulate those three steps in a single call. The various overloads of the CreateLayoutNode() method serve this purpose. For example:
 
ILayoutNode* pNodeOKCANCEL =
CreateLayoutNode(__uuidof(CScaleLayout), pRootNode);
This call creates a new instance of CScaleLayout, initializes it to this window (remember, the CLayoutManager<> is a base class), and assigns it as a child of the pRootNode node, using the standard ILayoutManager::AddLayoutNode().
A very common special case is the CWindowLayoutNode class. As mentioned before, this class’ initialization process requires the handle of the child window in addition to the master window. An additional overload takes care of this, as illustrated here:
 
ILayoutNode* pNodeSearchText =
CreateLayoutNode(__uuidof(CWindowLayoutNode),pRootNode,
IDC_SEARCH_STATIC);
Notice that here, a window id is passed as an extra parameter. This window id must correspond to an actual child window, in which case its handle will be passed as a second parameter in the call to Init().
Often, you want to create a layout node, but the default interface returned, ILayoutNode, is not the one you need to perform the necessary configuration. You can cast the interface pointers by using the guid_cast<> operator or calling QueryGuid directly. A more convenient alternative is to use another overload of CreateLayoutNode() to return you the right interface pointer. This overload requires passing a pointer to the interface you are requesting as a parameter. This pointer does not need to be initialized, because its value is never accessed. Rather, it is used only to determine the right interface type that must be returned:
 
IRelativeLayout* pRelative =
CreateLayoutNode(__uuidof(CRelativeLayout),pRelative);
Adding Layout Management to Your Applications
The process of merging the layout framework into your application is easy. The following procedure outlines the recommended steps:
1. Add layout management to one or more windows in your application, by following the steps outlined in “Integration with ATL”.
2. Add a layout map to your program to define the factory entries for the layout node classes you are going to use.