Introduction
Recently I've been reading up on WTL, and I came across a rather interesting class that I hadn't seen mentioned
anywhere, CDialogResize
. Given the large number of MFC implementations of resizable dialogs, it's
great that WTL provides its own, so you only have to learn one class and one way of specifying what gets resized.
In this article, I will outline WTL's resizing support and provide a sample program to illustrate some of the features.
You should already be familiar with WTL and how to install it. If you need help with this, there are articles on
those subjects in the WTL section.
Using CDialogResize
The basics
As with many other WTL features, you begin by adding CDialogResize
to the inheritance list of your
dialog class. So, if you make a dialog-based app with the WTL AppWizard, you would add the line listed here in
red:
class CMainDlg : public CAxDialogImpl<CMainDlg>,
public CDialogResize<CMainDlg>
CDialogResize
is declared in atlframe.h, so add that header to your includes if it isn't there
already.
The next step is to initialize the CDialogResize
code in your dialog's OnInitDialog
handler:
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DlgResize_Init();
...
}
DlgResize_Init()
has a few optional parameters, which I'll cover later.
Next, add an entry in the dialog's message map that passes sizing messages to CDialogResize
:
BEGIN_MSG_MAP(CMainDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
...
CHAIN_MSG_MAP(CDialogResize<CMainDlg>)
END_MSG_MAP()
Finally, add a new map to the dialog class that lists which controls in the dialog will be resized:
class CMainDlg : public CAxDialogImpl<CMainDlg>, public CDialogResize<CMainDlg>
{
...
public:
BEGIN_DLGRESIZE_MAP(CMainDlg)
END_DLGRESIZE_MAP()
...
};
I'll describe how to fill in this map in the "Setting up the resize map" section.
Initializing CDialogResize
CDialogResize
is initialized by calling to DlgResize_Init()
. Its prototype is:
void CDialogResize::DlgResize_Init (
bool bAddGripper = true,
bool bUseMinTrackSize = true,
DWORD dwForceStyle = WS_THICKFRAME | WS_CLIPCHILDREN );
The parameters are:
bAddGripper
: This controls whether CDialogResize
adds a size box to the bottom-right corner
of the dialog. Pass true
to add the size box, or false
to not add it.
bUseMinTrackSize
: This parameter controls whether CDialogResize
restricts the minimum size
of the dialog. If you pass true
, the dialog is not allowed to be sized smaller than its initial size
(as stored in the resource file). Pass false
if you don't want to restrict the dialog's minimum size.
dwForceStyle
: Specifies window styles to apply to the dialog. The default value is usually sufficient.
Setting up the resize map
The dialog resize map tells CDialogResize
which controls to move or size. An entry looks like this:
DLGRESIZE_CONTROL(ControlID, Flags)
ControlID
is the ID of the dialog control. The possible flags and their meanings are:
DLSZ_SIZE_X
: Resize the width of the control as the dialog resizes horizontally.
DLSZ_SIZE_Y
: Resize the height of the control as the dialog resizes vertically.
DLSZ_MOVE_X
: Move the control horizontally as the dialog resizes horizontally.
DLSZ_MOVE_Y
: Move the control vertically as the dialog resizes vertically.
DLSZ_REPAINT
: Invalidate the control after every move/resize so it repaints every time.
Note that you cannot move and size a control in the same dimension. If you specify, say DLSZ_MOVE_X
and DLSZ_SIZE_X
together, the size flag is ignored.
You can also group controls together so that they move and size proportionally to each other. I will cover this
subject later.
Sample Project
The sample project included with this article is a simple dialog-based app that functions as a browser (using
the WebBrowser ActiveX control). The control IDs are listed below; you should refer back to this diagram later
when I discuss moving and resizing these controls.
The controls will move and size according to these rules:
- The Location edit box will resize horizontally.
- The Go, Exit, and About buttons will move horizontally.
- The Back, Forward, Stop, and Refresh buttons will resize horizontally in a group.
- The browser control will resize horizontally and vertically
The browser's OnInitDialog()
function initializes CDialogResize
like this:
LRESULT OnInitDialog(UINT , WPARAM , LPARAM ,
BOOL& )
{
DlgResize_Init();
...
}
This uses the default parameters to DlgResize_Init()
, which results in a size box being added.
Also, the dialog cannot be sized smaller than its initial size. Here's what the dialog looks like on startup:
Here is the resize map, which lists the moving and sizing behavior for the controls. Note the new macros -
BEGIN_DLGRESIZE_GROUP()
and END_DLGRESIZE_GROUP()
- that put the four browser control
buttons into a resizing group.
BEGIN_DLGRESIZE_MAP(CMainDlg)
DLGRESIZE_CONTROL(IDC_URL, DLSZ_SIZE_X)
DLGRESIZE_CONTROL(IDC_GO, DLSZ_MOVE_X)
DLGRESIZE_CONTROL(IDC_EXIT, DLSZ_MOVE_X)
DLGRESIZE_CONTROL(ID_APP_ABOUT, DLSZ_MOVE_X)
BEGIN_DLGRESIZE_GROUP()
DLGRESIZE_CONTROL(IDC_BACK, DLSZ_SIZE_X)
DLGRESIZE_CONTROL(IDC_FORWARD, DLSZ_SIZE_X)
DLGRESIZE_CONTROL(IDC_STOP, DLSZ_SIZE_X)
DLGRESIZE_CONTROL(IDC_REFRESH, DLSZ_SIZE_X)
END_DLGRESIZE_GROUP()
DLGRESIZE_CONTROL(IDC_BROWSER, DLSZ_SIZE_X|DLSZ_SIZE_Y)
END_DLGRESIZE_MAP()
Here is the dialog after being resized:
Notice how the edit box is wider, and the browser control is wider and taller. The behavior of the four grouped
buttons is a bit tough to put into words, and the WTL code offers little guidance since there are few comments.
But the basic idea is: imagine a bounding rectangle that surrounds all four buttons. That rectangle resizes like
any other control, and all the buttons are sized proportionally so they remain within the bounding rectangle. If
the buttons were to be moved, instead of resized, they would be positioned to always be evenly spaced apart. Note
that all the controls in a group should have the same DLSZ_*
flags to produce meaningful behavior.
Bugs and Problems with CDialogResize
So far, I've only seen two problems. One is that there seems to be an off-by-one bug somewhere, because the
first time you resize the dialog, some of the controls shift the wrong direction by one pixel. The other, more
serious problem, is a bug in the WTL AppWizard that is exposed when you add CDialogResize
as a base
class of your dialog. The AppWizard-generated code that displays the dialog looks like this:
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE ,
LPTSTR lpstrCmdLine, int nCmdShow)
{
CMainDlg dlgMain;
int nRet = dlgMain.DoModal();
_Module.Term();
::CoUninitialize();
return nRet;
}
The trouble with that is the CMainDlg
destructor is called after _Module.Term()
. This
causes a crash in a release build if it is built with the _ATL_MIN_CRT
symbol defined. The solution
is to put the CMainDlg
object in an enclosing block:
int nRet;
{
CMainDlg dlgMain;
nRet = dlgMain.DoModal();
}