
Introduction
Java programmers have known about the usefulness of window managers for some time. If a dialog or view is to be resizable, you need some way to move and resize the child windows. There is no help from the Win32 API or from MFC for this chore. Numerous attempts have been made to rectify this, but all of them have been lacking in one or more of these areas:
- Complete reliance on a MFC base class. If the library contains a
CResizableDlg
and you need to add functionality to a dialog that's reliant on some other base class, you're pretty much out of luck. Sometimes, the solution provides some way to modify your own CWnd
class to be resizable, but this method always takes a lot of coding.
- Complete reliance on MFC, ATL or some other framework. This one is less of a problem, but some of us don't use MFC (or ATL, or...) for all of our projects. Some of us use other frameworks, the Win32 API directly, etc.
- A C API. This is even less of a problem. However, it's often easier to use an OO approach.
- Closed architectures. There's no way to extend the library to use unique algorithms for laying out the child windows. This is actually one of the worst deficiencies, since usually the provided algorithms provide only the most simplistic ways to lay out your windows.
- Non-open source. You have to pay for using the library, and usually through the nose. The layout management classes are typically just a subset of a much larger library, so the cost of adding layout management to your project can run into thousands of US dollars.
What I provide here is hopefully the first library for Win32 window layout management that suffers from none of these. The library is fully object oriented, written in C++ with no reliance on MFC. The library is extendable, allowing you to define your own layout mechanisms. Adding layout management to a window is quick, simple and reliant on no other libraries. Typical layout code can be provided for a window with only a few lines of code and no need to implement any message handlers.
General Usage
This section describes the general usage of sizer constraint objects.
CSizer
is a base class for "sizers", classes that constrain other child sizers according to specific constraint mechanisms. Derived classes can specify their own constraint mechanisms. Each sizer can have an associated window that will be automatically sized with the sizer. If you are familiar with Java, you can think of a sizer as equivalent to a LayoutManager, Container and a Component, all at the same time. There are several CSizer
derivatives defined in this library to handle different constraint mechanisms, and it's simple to derive your own to handle other constraint mechanisms.
For a complete discussion of the available sizers and how to use them, see the section Sizer API. For now, I'm just going to give you a general guideline for how to use sizers to lay out a complex dialog that will allow for dynamic resizing.
1. Add Source Files
First you need to add the source files to your project using the menu option "Project > Add to Project > Files". The files you'll need are:
- Sizer.h
- Sizer.cpp
- DeferPos.h
- DeferPos.cpp
- SubclassWnd.h
- SubclassWnd.cpp
You may want to place these files in a LIB or DLL for easier reuse.
2. Create the Dialog
Create an example dialog that looks like this:

3. Create a MFC Dialog Class
Create a MFC dialog class from the example dialog. You should be aware that the sizer library does not require MFC to be used. We're using it in this example simply to show how the library can coexist with MFC.
4. Modify the OnInitDialog Method
We'll modify the OnInitDialog
method to actually implement the layout constraints. Your code should look similar to this:
BOOL CSizerTestDlg::OnInitDialog()
{
CDialog::OnInitDialog();
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
CRowSizer* pRoot = new CRowSizer(this, CRowSizer::VERTICAL);
pRoot->AddChild(new CFixedSizer(GetDlgItem(IDC_STATIC1),
CFixedSizer::HEIGHT), 0);
CRowSizer* pRow = new CRowSizer(CRowSizer::HORIZONTAL);
pRow->AddChild(new CFixedSizer(GetDlgItem(IDC_NEW_ITEM_EDT),
CFixedSizer::HEIGHT), 1);
pRow->AddChild(new CFixedSizer(GetDlgItem(IDC_ADD_BTN),
CFixedSizer::WIDTH), 0);
pRoot->AddChild(pRow, 0);
pRoot->AddChild(new CFixedSizer(GetDlgItem(IDC_STATIC2),
CFixedSizer::HEIGHT), 0);
pRoot->AddChild(new CSizer(GetDlgItem(IDC_ITEM_LST)), 1);
pRow = new CRowSizer(CRowSizer::HORIZONTAL);
pRow->AddChild(new CSizer(), 1);
pRow->AddChild(new CFixedSizer(GetDlgItem(IDOK)), 0);
pRow->AddChild(new CFixedSizer(GetDlgItem(IDCANCEL)), 0);
pRoot->AddChild(pRow, 0);
pRoot->LayoutChildren();
return TRUE;
}
The first thing you need to do is create a sizer associated with the dialog. Note that sizers must always be created on the heap via a call to new
. Memory management is automatically handled for you since sizers are deleted either by their parent or when the associated window is destroyed. In our example, we create the dialog's sizer as a CRowSizer
which lays out its children in either a vertical or horizontal row (we specify vertical in this example). Each child added to the sizer will be first sized to its minimum size and then placed in a row. After this, any left over space is calculated and the children are grown to fill all available space (maintaining the row layout) according to a "weight" that's specified as they are added. If the total weight of all children added is 9 and an individual child has a weight of 1, it will grow to fill 1/9 of the available space. This is one of the more flexible sizers.
The first child we add to the dialog's sizer is a sizer for the static "New Item:" label. This sizer won't have any children but we want to ensure that it retains its height as a fixed dimension, so we use a CFixedSizer
. The CFixedSizer
can retain either a fixed height, width or both. It will never have children of its own but is ideal for situations like this one where we need to maintain a fixed size regardless of what the parent wants to do with us.
The next thing we want to add to our constraint system is the edit box and "add" buttons, but these need to be placed side by side instead of in a vertical row. This can be achieved easy enough by nesting sizers. So, we create another CRowSizer
, this time a horizontal one. The edit control is associated with a CFixedSizer
having a fixed height, and the button is associated with a CFixedSizer
having a fixed width. These sizers are added to the horizontal CRowSizer
which is, in turn, added to the dialog's vertical CRowSizer
.
Another static label is added to the dialog's CRowSizer
using a CFixedSizer
with a fixed height.
Next, we'll add a generic CSizer
associated with the list control. We use a generic CSizer
because we don't care about constraining the control in any way, and it won't have any children. The dialog sizer will be free to resize this child in any way it wishes.
Finally, we'll use the nested sizer technique again to add another horizontal row of sizers for the "OK" and "Cancel" buttons. The trick to make them right aligned is to add a generic CSizer
that's not associated with any window to this CRowSizer
first. Since the buttons use CFixedSizer
objects (fixed in both dimensions), the generic sizer will receive all available extra space effectively pushing the buttons to the far right side.
This is a relatively simple example, but hopefully it's enough to demonstrate how sizers can be used to define layout constraints for any window.
This library defines the following sizer classes:
- CSizer
- Generic sizer. This sizer has no children and specifies no constraints beyond the min/max size constraints for any associated owner.
- CFixedSizer
- This sizer has no children but constrains itself to a fixed size for either the width, the height, or both.
- CRowSizer
- This sizer constrains its children to fit within a row either vertically or horizontally. The children are initially sized to their smallest dimensions and then are resized to fill any left over space according to their associated weights. If all the children have a combined weight of 9 and an individual child has a weight of 1 then it will receive 1/9 of the available left over space. Left over space may be calculated multiple times if a child reaches its maximum size without taking up all of its allotted extra space.
- CFillSizer
- This sizer constrains all of its children to entirely fill its client space. If there are multiple children, they will overlap.
There are some general terms that should be defined before describing the API for the above classes.
- Sizer
- A sizer is an object that defines sizing constraints and a possible constraint mechanism for any possible child sizers. Not all sizers can have children. A sizer may have an "owner".
- Owner
- An owner is an associated window. Owners are sized with the sizer.
- Constraint
- A set of rules specifying how a window can be sized and moved.
- Insets
- Insets define the space between the sizer's border and its owner's border. Each border can have its own inset dimension.
- Areas
- An area is a rectangular specification (using the
RECT
data type) for the various parts of a sizer. The sizer's rect
is the outer area. The owner's area is next and is defined as the sizer's area minus the sizer's insets. The client area is next and is defined in terms of the client area of the owner's window. Normally, children will be constrained (or clipped) to the client area. If there is no owner associated with a sizer, there will still be an owner and a client area, they will just be identical and will be defined as the sizer's area minus the insets.
The interfaces for the sizer classes are defined as follows:
CSizer
CSizer()
CSizer(HWND hWndOwner)
CSizer(CWnd* pWndOwner)
Constructs a CSizer
object with or without an owner.
HWND GetOwner()
Gets the HWND
for the associated owner or returns NULL
if there is none.
HWND GetParentWindow()
Gets the HWND
for the parent window. This works even if the sizer has no associated owner, by calling the parent sizer's GetParentWindow()
.
void SetParent(CSizer* pParent)
Sets the sizer's parent. This should only be called by derived classes after they have added the sizer to their child lists. This method ensures that the sizer is removed from any existing parent's list.
CSizer* GetParent()
const CSizer* GetParent() const
Gets a pointer to the parent sizer.
void SetRect(RECT rect)
Sets the sizer's area to the specified RECT
. Note that the owner is not resized/moved until a call to OnRealizeLayout
is made, which is normally called by the framework.
void GetRect(LPRECT pRect)
Gets the sizer's area.
void GetOwnerRect(LPRECT pRect)
Gets the owner's area. Note that this may be different than the area returned by GetWindowRect
on the owner if the sizer's layout has not been realized yet. This works even if there is no owner.
void GetClientRect(LPRECT pRect)
Gets the client area. Note that this may be different than the area returned by GetClientRect
on the owner if the sizer's layout has not been realized yet. This works even if there is no owner.
void SetInsets(int nInsets)
void SetInsets(RECT insets)
Sets the insets for the sizer.
void GetInsets(LPRECT pInsets)
Gets the insets for the sizer.
void GetMinMaxInfo(MINMAXINFO* pMMI)
Gets the minimum and maximum dimensions for the sizer. This is normally based upon the min/max info for the owner and takes into consideration the insets. Derived classes may also take into consideration any min/max info of their children.
void LayoutChildren()
Lays out the child sizers according to the sizer's constraint mechanisms, if any.
void OnRemoveChild(CSizer* pSizer)
This should be overridden by derived classes to remove a child from its child list. This should never be called directly.
void OnGetMinMaxInfo(MINMAXINFO* pMMI)
This should be overridden by derived classes to calculate the min/max dimensions for the sizer. This calculation should not take into consideration the insets for the sizer. Derived sizers should call the base class version of this method. This method should never be called directly. Instead, call GetMinMaxInfo
.
void OnLayoutChildren()
This should be overridden by derived classes to layout any child sizers according to the constraint mechanism for the sizer. This should never be called directly. Instead, call LayoutChildren
.
void OnRealizeLayout(CDeferPos& dp)
This should be overridden by derived classes to realize the layout (in other words, to finalize the areas by moving the owner window and calling any child sizer's OnRealizeLayout
). This should never be called directly. It's called by LayoutChildren
if needed.
CFixedSizer
CFixedSizer(RECT rect, FixedType nType=BOTH)
CFixedSizer(HWND hWndOwner, FixedType nType=BOTH)
CFixedSizer(HWND hWndOwner, RECT rect, FixedType nType=BOTH)
CFixedSizer(CWnd* pWndOwner, FixedType nType=BOTH)
CFixedSizer(CWnd* pWndOwner, RECT rect, FixedType nType=BOTH)
Constructs a CFixedSizer
constrained in any dimension. The type can be HORIZONTAL
, VERTICAL
or BOTH
. The sizer is initially set to size rect
if supplied.
CFillSizer
CFillSizer()
CFillSizer(HWND hWndOwner)
CFillSizer(CWnd* pWndOwner)
Constructs a CFillSizer
which constrains its children to completely fill its client area.
CSizer* AddChild(CSizer* pChild)
Adds a child to the child list.
CRowSizer
CRowSizer(RowType nType)
CRowSizer(HWND hWndOwner, RowType nType)
CRowSizer(CWnd* hWndOwner, RowType nType)
Constructs a CRowSizer
which constrains its children to fill its client area in a vertical or horizontal row. The type can be VERTICAL
or HORIZONTAL
.
CSizer* AddChild(CSizer* pChild, int nWeight)
Adds a child to the sizer's child list with an associated weight. The weight determines how much of the extra space is to be allotted to the child. If all children have a combined weight of 9 and an individual child has a weight of 1, it will receive 1/9 of the extra space.
Programmer Notes
These classes were written and tested with the example where they function well. However, I can't guarantee there are no bugs. If you find any, I'd appreciate knowing about them so I can add them to a future update. I'm also interested in seeing any new sizers that others come up with and may add them to a future update as well. I meant for these classes to be extendable and easy to use, and I'm quite interested in hearing your comments.
Windows developer with 10+ years experience working in the banking industry.