Click here to Skip to main content
15,860,844 members
Articles / Desktop Programming / MFC
Article

Sizers: An Extendable Layout Management Library

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
16 Jan 2000 165.4K   1.3K   63   28
An article on extendable layout management classes.

Sample Image - sizer.gif

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:

  1. 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.
  2. 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.
  3. A C API. This is even less of a problem. However, it's often easier to use an OO approach.
  4. 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.
  5. 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:

sample sizer image

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();

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);            // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon
    
    // TODO: Add extra initialization here

    // Create the "root" sizer for this dialog to be a vertical "row sizer"
    CRowSizer* pRoot = new CRowSizer(this, CRowSizer::VERTICAL);

    // Add the static text to the root
    pRoot->AddChild(new CFixedSizer(GetDlgItem(IDC_STATIC1), 
                                           CFixedSizer::HEIGHT), 0);

    // Create a new sizer to hold some controls
    // in a horizontal "row sizer" and add
    // the row to the root.
    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);

    // Add some more controls to the root.
    pRoot->AddChild(new CFixedSizer(GetDlgItem(IDC_STATIC2), 
                                          CFixedSizer::HEIGHT), 0);
    pRoot->AddChild(new CSizer(GetDlgItem(IDC_ITEM_LST)), 1);

    // Create another sizer to hold some more
    // controls in a horizontal "row sizer" and
    // add the row to the root.
    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;  // return TRUE  unless you set the focus to a control
}

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.

Sizer API

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)    // MFC only

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Windows developer with 10+ years experience working in the banking industry.

Comments and Discussions

 
QuestionCopyright info Pin
Member 25670955-Jul-16 0:48
Member 25670955-Jul-16 0:48 
GeneralWorks under XP, Crashes under Vista Pin
Kevin Yochum2-Dec-09 11:40
Kevin Yochum2-Dec-09 11:40 
AnswerRe: Works under XP, Crashes under Vista: FIXED Pin
Vitaly Shishakov26-Sep-10 11:22
Vitaly Shishakov26-Sep-10 11:22 
GeneralRe: Works under XP, Crashes under Vista: FIXED Pin
injector22-Apr-13 23:37
injector22-Apr-13 23:37 
GeneralWhen I use demo code in vs2005, I get some heap break ! Pin
zarelaky3-Sep-09 16:15
zarelaky3-Sep-09 16:15 
QuestionStatic Control (Border) with Controls in it ? Pin
AttNet6-Feb-05 20:46
AttNet6-Feb-05 20:46 
GeneralIn case you are looking for MFC version Pin
TW21-Jun-03 6:17
TW21-Jun-03 6:17 
QuestionAnother bug. Too late? Pin
Manni22-Aug-02 5:49
Manni22-Aug-02 5:49 
AnswerRe: Another bug. Too late? Pin
Tim Finer1-Nov-02 8:58
Tim Finer1-Nov-02 8:58 
General ASSERT(pMMI->ptMinTrackSize.y <= pMMI->ptMaxTrackSize.y) in CSizer::GetMinMaxInfo Pin
Jens Scheidtmann1-Aug-01 20:51
Jens Scheidtmann1-Aug-01 20:51 
GeneralRe: ASSERT(pMMI-&gt;ptMinTrackSize.y &lt;= pMMI-&gt;ptMaxTrackSize.y) in CSizer::GetMinMaxInfo Pin
William E. Kempf3-Aug-01 10:52
William E. Kempf3-Aug-01 10:52 
GeneralRe: ASSERT(pMMI->ptMinTrackSize.y <= pMMI->ptMaxTrackSize.y) in CSizer::GetMinMaxInfo Pin
Jens Scheidtmann6-Aug-01 7:49
Jens Scheidtmann6-Aug-01 7:49 
GeneralRe: ASSERT(pMMI->ptMinTrackSize.y <= pMMI->ptMaxTrackSize.y) in CSizer::GetMinMaxInfo Pin
William E. Kempf6-Aug-01 8:57
William E. Kempf6-Aug-01 8:57 
GeneralRe: ASSERT(pMMI->ptMinTrackSize.y <= pMMI->ptMaxTrackSize.y) in CSizer::GetMinMaxInfo Pin
6-Aug-01 9:26
suss6-Aug-01 9:26 
GeneralRe: ASSERT(pMMI->ptMinTrackSize.y <= pMMI->ptMaxTrackSize.y) in CSizer::GetMinMaxInfo Pin
Jens Scheidtmann6-Aug-01 9:42
Jens Scheidtmann6-Aug-01 9:42 
GeneralTwo bug reports Pin
30-Jul-01 4:51
suss30-Jul-01 4:51 
GeneralRe: Two bug reports Pin
William E. Kempf1-Aug-01 11:11
William E. Kempf1-Aug-01 11:11 
GeneralRe: Two bug reports Pin
6-Aug-01 9:12
suss6-Aug-01 9:12 
GeneralChild sizer out of bounds Pin
Anatoly Ivasyuk27-Apr-01 12:17
Anatoly Ivasyuk27-Apr-01 12:17 
GeneralBug Fix Pin
John Kohler5-Oct-00 5:34
John Kohler5-Oct-00 5:34 
GeneralRe: Bug Fix Pin
William E. Kempf5-Oct-00 5:51
William E. Kempf5-Oct-00 5:51 
GeneralCFormView, CSplitterWnd and Sizers Pin
Spencer Pablos31-May-00 21:25
Spencer Pablos31-May-00 21:25 
GeneralDoesn't seem to work with /Zp1 Pin
Marco Cunha16-Mar-00 3:24
Marco Cunha16-Mar-00 3:24 
GeneralRe: Doesn't seem to work with /Zp1 Pin
William Kempf16-Mar-00 3:55
sussWilliam Kempf16-Mar-00 3:55 
GeneralRe: Doesn't seem to work with /Zp1 Pin
Marco Cunha16-Mar-00 7:15
Marco Cunha16-Mar-00 7:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.