A Simple C++ Layout Manager

By: Daniel Horn

Abstract: This article will demonstrate a simple layout manager that handles many of the common cases needed for resizing and repositioning child windows in a parent window in a Windows program written in C++.

Cases not handled:

A Simple C++ Layout Manager by Daniel Horn

 

 

Introduction:

 

Modern tools such as Java, Delphi, or .NET have layout managers that greatly simplify the work of developers in controlling the appearance of a form or dialog when the parent window is resized.

However, if one must write a GUI application without the advantages of certain visual tools (say, directly using the Windows API), the work involved is quite different.  One needs to convert between screen, child window, and client coordinates, and then use MoveWindow or SetWindowPos in a manner that will change on a case by case basis. Needless to say, this is much more tedious, time consuming, complicated, and, above all, error-prone.

This article will demonstrate a simple layout manager that handles many of the common cases needed for resizing and repositioning child windows in a parent window in a Windows program written in C++.

 

Using the Layout Manager:

 

The source (http://codecentral.borland.com/codecentral/ccWeb.exe/listing?id=19502) for the accompanying program demonstrates a pattern for layout managers in general.

 

1) Add the source file, SimpleLayoutManager.cpp, for the layout manager to your project, and place its header file, SimpleLayoutManager.h, somewhere in your Include path.

 

2) In the source file containing the window or dialog procedure that will use the layout manager add an include statement for the header:

#include "SimpleLayoutManager.h"

 

3) In the window or dialog procedure, declare and initialize the SimpleLayoutManager instance, called layoutManager in the code below:

 

BOOL CALLBACK MainDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

static SimpleLayoutManager *layoutManager = NULL;

 

switch (uMsg)

{

case WM_INITDIALOG:

{

// 

layoutManager = new SimpleLayoutManager(hDlg);

// Add constraint information for all child windows.

layoutManager->AddChildConstraint(IDC_LIST, (CWAttribute)(CWA_LEFTRIGHT | CWA_TOPBOTTOM));

// More calls to AddChildConstraint here

// . . .

}

return TRUE;

 

case WM_COMMAND:

if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)

{

delete layoutManager;

 

EndDialog(hDlg, LOWORD(wParam));

return TRUE;

}

break;

 

case WM_SIZE:

{

layoutManager->DoLayout(wParam, lParam);

}

break;

}

return FALSE;

}

 

The above code snippet is an abridged version of an actual dialog proc that uses a layout manager.

 

In particular, note that the layout manager is:

  • initialized in the WM_INITDIALOG event handler (this would be WM_CREATE in the case of a window proc);
  • deconstructed when the dialog is closed (immediately before the call to EndDialog in this example; in a window proc, one would do this in the WM_DESTROY or WM_CLOSE handler); and
  • used in the WM_SIZE event handler.

 

The only part you need to know more about is adding constraint information for each child window via the call to SimpleLayoutManager::AddChildConstraint(). The first argument to this method is the integer ID of the associated child window; this is the same value used, say, to distinguish the child window in a WM_COMMAND message (i.e., LOWORD(wParam)).

 

The second argument tells what type of resizing or repositioning the child window should go through whenever its parent window receives a WM_SIZE message. It will actually be a bitwise or (|) of values defined in the CWAttribute enum.

 

An attribute of CWA_LEFT indicates that the child is anchored to the left side of its parent; whenever the parents size changes, the child maintains the same distance relative to the left side of the parents client area. CWA_RIGHT indicates that the child should maintain the same distance relative to the right side of the parents client area. CWA_LEFTRIGHT indicates both that the childs size will change to maintain the same relative distances from the left and right sides of the parents client rectangle.

 

CWA_TOP, CWA_BOTTOM, and CWA_TOPBOTTOM define similar behaviors with respect to the top and bottom sides of the parents client area.

 

CWA_FORCEINVALIDATE is an additional attribute provided to fix a repaint blemish that occasionally occurs when the parents size is reduced and causes one child window to partially cover another. Dont bother using this attribute unless you see this problem. (The problem can be demonstrated if you remove this attribute in the demo program from the AddChildConstraint call for the move down (or the move up) button.

 

 

How It Works:

 

If you read the source code in SimpleLayoutManager.cpp, youll find that this is not complicated at all. The SimpleLayoutManager maintains a linked list of constraints, plus the previous size of the parent window (because some layouts require knowledge of relative positions in the parent).

 

When the parent window receives a WM_SIZE message, the DoLayout member function is called. If a layout is mandated, i.e. if the WM_SIZE message occurred because the window was maximized or resized -- there are other actions that result in a WM_SIZE --,

then the linked list is looped through. Each item in the list represents the resizing/reposition instructions for each child window.

 

 

Caveats and Future Directions:

 

If reader interest is there, a follow-up article will cover a more sophisticated version of the layout manager. In the meantime, the reader should consider the following caveats and possible enhancements.

 

While this layout manager handles many useful cases, one case not handled is the following: If you have a window with a button, say, along the center of the right side, then there is no way to maintain its position in the center when the window is resized. A CWA_TOP attribute wont maintain its relationship with the bottom; a CWA_BOTTOM wont maintain its relationship with the top; and CWA_TOPBOTTOM will maintain distance relationships with the top and bottom but will also result in the button be stretched or shrunken.

 

To handle the above case, one could add support for new attributes such as CWA_VCENTER and CWA_HCENTER.

 

A more general example that one will eventually want to support involves having two child windows, say list views, with one on top of the other (or side by side). When the parent is resized, one would probably want both of these to be resized and repositioned in an appropriate manner. A solution for this might involve adding a weighting factor to the child constraints to indicate how to do this.

 

A third possible enhancement to the layout manager would to be to enforce a minimum size for the parent window based on the minimum size constraints for the child windows.

 

Also, note that the demo program uses a dialog box for its main window instead of an overlapped window to simplify the code. In this case, the initial positions of the child windows are provided in the resource file; we dont have to write code giving their initial position. This suggests another enhancement; the constraints given to the layout manager could be modified to help determine the initial position of the children.

 

Questions, comments, and suggestions may be sent to the author at dan@nerds.com.

Server Response from: ETNASC01