Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Helper Class for Resizable MFC Dialogs

0.00/5 (No votes)
19 Mar 2009 1  
This class takes care of the child window placement when you resize an MFC dialog. Also stores the size in the Registry for the next call.

Introduction

Hi, this is my first article here, have mercy.

This article describes a very easy way to take care of resizable dialogs in MFC applications, using Visual Studio 6 to 2005 (I don't know about 2008 or newer versions, yet).

Background

I like MFC. Believe it or not, I do. But, I dislike the way you have to be careful when you want a resizable dialog. You have to program the position for each child window manually into the OnSize method of every dialog you program. So, I took an afternoon investigating how this could be simplified.

So, starting from what I know about a CDialog, I can get the current window size (that you set in the resource editor) in the OnInitialUpdate() method of the dialog. Store that, so the next time the dialog gets resized, you can make sure it won't be smaller than this, in the OnGetMinMaxInfo() method.

void Init(CDialog* pDialog);

Easy, see. You just pass the "this" pointer from your dialog.

I can also get the current position of the child windows, so I can use that information later to keep the distance to the left/right and top/bottom border when the dialog gets resized.

By default, all child windows stick their position to the top, left hand corner automatically if you don't move them. If, however, you want to have them move, use this function to give the resizer a rule to move your child:

void AddCtrl(int id, int align=DT_LEFT|DT_TOP, 
             BOOL keep_dx=TRUE, BOOL keep_dy=TRUE);

For the alignment flags, I chose the DrawText API function constants, since they (on contrary to the TA_ constants for the TextOut API) have a DT_VCENTER constant. So, yes, you can have the child windows align to the center of the dialog, too.

The flags "keep_dx" and "keep_dy" tell the resize helper whether you allow to stretch that child window, or keep the size. For a button, you mostly want to keep the size, whereas you want the main edit control stretched, most likely.

This is done here:

// ---------------------------------------------------
// OnSize handling of the Child Ctrls
// ---------------------------------------------------
void GFResizeDialogHelper::OnSize(int cx, int cy)
{
    m_CurSize.cx = cx;
    m_CurSize.cy = cy;

    for(int i=0; i<(int)m_Ctrls.size(); ++i)
    {
        CTRL_ALIGN& ctrl = m_Ctrls[i];

        int x = ctrl.place.left;
        int width = ctrl.place.Width();
        if(!ctrl.keep_dx)
        {
            width += (cx - m_OrigSize.cx);
            // new size increment = dialog increment
        }
        if(ctrl.align & DT_RIGHT)
        {
            x = ctrl.place.right - width + cx-m_OrigSize.cx;
        }
        else if(ctrl.align & DT_CENTER)
        {
            // ctrl center
            int xcenter = (ctrl.place.left+ctrl.place.right)/2;
            // dialog offset
            xcenter += (cx-m_OrigSize.cx)/2;
            x = xcenter-width/2;
        }
        
        int y = ctrl.place.top;
        int height = ctrl.place.Height();
        if(!ctrl.keep_dy)
        {
            height+=(cy-m_OrigSize.cy);
        }
        if(ctrl.align & DT_BOTTOM)
        {
            // y += cy-m_OrigSize.cy;
            y = ctrl.place.bottom - height + cy-m_OrigSize.cy;
        }
        else if(ctrl.align & DT_VCENTER)
        {
            // ctrl center
            int ycenter = (ctrl.place.top + ctrl.place.bottom)/2;
            // dialog offset
            ycenter += (cy-m_OrigSize.cy)/2;
            y = ycenter - height/2;
        }
        width = max(width, 10);
        height = max(height, 10);
        x = max(0,x);
        y = max(0,y);

        m_pDialog->GetDlgItem(ctrl.idWnd)->MoveWindow(x,y,width, height, FALSE);
        // ::MoveWindow(ctrl.pWnd->GetSafeHwnd(), x,y,width, height, TRUE);
    }
    if(m_pDialog && m_pDialog->GetSafeHwnd() && 
                ::IsWindow(m_pDialog->GetSafeHwnd()))
        m_pDialog->RedrawWindow();
}

Next, I found out that every dialog has an IDD constant that is the resource ID of the dialog. Fine! We can use that ID for storing the dialog's size at closing time in the Registry, I thought, but then I found out that: m_pDialog->GetRuntimeClass()->m_lpszClassName returns a readable string for the dialog class' name, and I took that one.

Using the code

The code is a piece of cake to use. You just include the two files from above in your project. Then, open the mydialog.h file and add these two lines:

include "gfresizehelper.h" // include header

// and add a member variable to your CDialog derived class:
GFResizeDialogHelper    m_Resizer;

The next step is to tell the helper about your child windows and how you want them to behave when resized:

BOOL CMyDialog::OnInitDialog()
{
    // ...
    m_Resizer.Init(this);
    m_Resizer.AddCtrl(IDOK, DT_BOTTOM|DT_RIGHT);
    // ... more controls
    // optionally - read and write position and size to registy
    m_Resizer.ReadWriteToRegistry();
}
/*
// Usage of AddCtrl(int id, int align, BOOL keep_dx, BOOL keep_dy)
int id:
    The ID of the control (IDC_...)
int align:
    The alignment of the control. If you align it
    Horizontally:
        DT_LEFT,    the left-coordinate will stay where it is.
        DT_CENTER,    the center of the control will stay where it was,
                    relative to the dialog.
        DT_RIGHT,    the right border of the control will keep the distance
                    to the right border of the dialog.
    Vertically:
        DT_TOP,        the top position will stay
        DT_VCENTER,    the vertical center will stay where it was relative
                    to the dialog.
        DT_BOTTOM,    the controll will keep the distance to the bottom of
                    the dialog
BOOL keep_dx
    Should the control keep its width?
BOOL keep_dy
    Should the control keep its height?
*/

The call to ReadWriteToRegistry() will try to get the last stored size and position from the Registry, and will store the size from the last WM_SIZE message when the class destructs, which is usually after a OnOK() or OnCancel().

Then, use the application wizard or the dialog properties, get a message handler for OnSize (the WM_SIZE message), and tell the resize helper to take care of your children.

void CMyDialog::OnSize(UINT nType, int cx, int cy)
{
    CDialog::OnSize(nType, cx, cy);
    m_Resizer.OnSize(cx,cy);
}

Finally, you can handle the WM_GETMINMAXINFO message to tell the program not to make the window smaller than you designed it in the dialog editor:

void CMyDialog::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
    m_Resizer.GetMinMaxInfo(lpMMI);
}

I hope this article is of some use for someone. I use this class a lot now, and I really like the new look of my programs with the resizable dialogs.

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