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:
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);
}
if(ctrl.align & DT_RIGHT)
{
x = ctrl.place.right - width + cx-m_OrigSize.cx;
}
else if(ctrl.align & DT_CENTER)
{
int xcenter = (ctrl.place.left+ctrl.place.right)/2;
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 = ctrl.place.bottom - height + cy-m_OrigSize.cy;
}
else if(ctrl.align & DT_VCENTER)
{
int ycenter = (ctrl.place.top + ctrl.place.bottom)/2;
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);
}
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"
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);
m_Resizer.ReadWriteToRegistry();
}
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.