Automatic Dialog Resizing

Thursday Mar 20th 2003 by Dmitry Kochin

Another look at cleanly resizing a dialog and its controls.

Click here for a larger image.

Environment: VC.NET, VC6, Win32, MFC, WTL

Every GUI developer uses dialogs. Sometimes, dialogs should support resizing to make the user's life easier. To create a resizable dialog, you just need to handle a WM_SIZE message and resize/move child controls. But, when a dialog contains many controls, resizing it would be a pain for the developer, and the OnSize function will look like a monster!

When I ran into this problem, I wrote a simple resizer that could help significantly in making resizable dialogs. All you have to do now is:

  1. Create a dialog resource and position the controls as you wish.
  2. Make CResizer m_resizer a member of your dialog class.
  3. Initialize the resizer in OnInitDialog (m_resizer.Init(...)).
  4. Move controls in OnSize simply by calling m_resizer.Move()!

Let's consider the implementation of CResizer class in detail.

The main idea is the following: Each side of a child window (left, top, right, and bottom) is connected to a side of another window, the so-called relative window. The dialog window that owns the children also can be a relative window. When the dialog window is resized, the child window sides are moved after the relative window, thus preserving the connections.

There are several types of connections:

Connection Type Constant Name Description
Fixed CResizer::eFixed Preserves the distance between the side of the child window and the specified side of the relative window
Proportional CResizer::eProportional Preserves the ratio of distance between the side of the child window and left (top) side of the relative window to width (height) of the relative window.
Preserving width CResizer::eWidth An auxiliary connection that preserves the width of the specified child window. It also can be implemented with a fixed connection.
Preserving height CResizer::eHeight An auxiliary connection that preserves the height of the specified child window. It also can be implemented with a fixed connection.

The connections for child windows are specified in a static array of the CResizer::CBorderInfo structures. The format is as follows:

{IDC_CTRL_ID, {(conn type), (rel id), (rel side)}, //Left side
              {(conn type), (rel id), (rel side)}, //Top side
              {(conn type), (rel id), (rel side)}, //Right side
              {(conn type), (rel id), (rel side)}, //Bottom side

Here, (conn type) is one of the connection types listed above, (rel id) is an identifier of the relative window or IDC_MAIN if you want to reference parent dialog, and (rel side) is the side of the relative window to which a connection is bound.

Each child window has four sides, so it has four connections—for left, top, right, and bottom. They are listed subsequently in the CResizer::CBorderInfo initialization above.

Let's consider the connection format (CResizer::CBorder structure) in more detail.

 {(conn type)            , (rel id)    , (rel side)         }

  CResizer::eFixed                       CResizer::eLeft
  CResizer::eProportional  IDC_MAIN      CResizer::eTop
 {CResizer::eWidth       , IDC_CTRL_ID , CResizer::eRight   }
  CResizer::eHeight                      CResizer::eBottom

A fixed connection can bind the child window side not only to a side of the relative window, but also to its horizontal (CResizer::eXCenter) or vertical (CResizer::eYCenter) center line.

A proportional connection needs only the width or height as relative window side. To specify width, use one of the horizontal sides (eLeft, eRight, or eXCenter); to specify height, use one of vertical sides (eTop, eBottom, or eYCenter).

Connections preserving width or height don't require relative window info. So, there can be any.

Typical Usage

  1. Make CResizer m_resizer a member variable of your dialog class.

  2. Add the following code to OnInitDialog(), replacing the control IDs to your specific ones and adjusting connections as you wish (described above).

    static const CResizer::CBorderInfo s_bi[] = {
     {IDC_CTRL1, {CResizer::eFixed, IDC_MAIN, CResizer::eLeft},   //l
                 {CResizer::eFixed, IDC_MAIN, CResizer::eTop},    //t
                 {CResizer::eFixed, IDC_MAIN, CResizer::eRight},  //r
                 {CResizer::eFixed, IDC_MAIN, CResizer::eBottom}},//b
     {IDC_CTRL2, {CResizer::eFixed, IDC_MAIN, CResizer::eLeft},
                 {CResizer::eFixed, IDC_MAIN, CResizer::eBottom},
                 {CResizer::eFixed, IDC_MAIN, CResizer::eRight},
                 {CResizer::eFixed, IDC_MAIN, CResizer::eBottom}},
    const nSize = sizeof(s_bi)/sizeof(s_bi[0]);
    m_resizer.Init(m_hWnd, NULL, s_bi, nSize);
  3. Add the following code to the OnSize() handler:

  4. Everything should work now.

Some Final Tips

  1. Resizer resizes controls in the order they are defined in the array, so (rel id) should always be defined (and, therefore, moved by the resizer) before it is used as relative window. Otherwise, resizer ASSERTs.
  2. Windows that are defined earlier in the array have higher priority. When windows have too small a size and child windows overlap, the window with lower priority is hidden.
  3. If you need to limit minimum or maximum window size, you can handle the WM_GETMINMAXINFO message.


Download demo project - 103 Kb
Download source - 5 Kb
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved