WTL CPropertySheet as a Resizable View
- Download source only - 3 Kb
- Download sample project (includes source) - 11 Kb
- Download sample executable - 13 Kb
Introduction
This article describes how to use WTL's CPropertySheetImpl
template within a resizable view. An overridden PropSheetCallback
changes the property sheet style for use as a view. A subclass of the property sheet's tab control handles resizing of the property pages. The property sheet and property pages use CDialogResize
to handle control resizing. CPropertySheetImpl
is in the atldlgs.h
header file while CDialogResize
is in atlframe.h
.
Property Sheet
Our new property sheet class, CPropView
, inherits from both CPropertySheetImpl
and CDialogResize
to acquire its desired behavior. The property sheet template provides an encapsulation of the standard Windows property sheet control while the resize template provides a mechanism for moving or resizing child controls.
However, standard property sheets are designed for use as modal or modeless dialog windows, not views. To overcome the default behavior, override the callback procedure and add a pre-creation message handler to change the window style as shown below.
static int CALLBACK PropSheetCallback(HWND hWnd, UINT uMsg, LPARAM lParam) { // dialog template is available and changeable pre-creation if(uMsg == PSCB_PRECREATE) { LPDLGTEMPLATE lpDT = (LPDLGTEMPLATE)lParam; // remove dialog border styles lpDT->style -= DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU; // add child window and clipping styles lpDT->style |= WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; return 0; } else // call inherited method to handle PSCB_INITIALIZED return CPropertySheetImpl< CPropView >::PropSheetCallback(hWnd, uMsg, lParam); }
One other style change is required to complete the transition from standalone dialog to view but there are two obstacles to overcome. The first obstacle is that the style is an extended style, and it is not possible to change extended styles in precreate. Secondly, the property sheet template does not process the WM_INITDIALOG
message, which is where one might also change extended styles.
Therefore, we created an initialization method. This method, _Init
, modifies the extended style setting for the property sheet by setting WS_EX_CLIENTEDGE
. It also initializes the dialog resize code and subclasses the property sheet's tab control. _Init
is called by the main frame's OnCreate()
handler just after the view is created.
Tab Control
The Windows property sheet uses an ordinary tab control to host property pages. The tab control identifier is ATL_IDC_TAB_CONTROL
. In order to resize the tab control, and subsequently, its property pages, add it to the property sheet resize map as a sizable control. The property sheet buttons, not shown in the example below, are also added to the resize map but as movable controls.
BEGIN_DLGRESIZE_MAP(CPropView) DLGRESIZE_CONTROL(ATL_IDC_TAB_CONTROL, DLSZ_SIZE_X | DLSZ_SIZE_Y) END_DLGRESIZE_MAP()
Once the tab control is placed in the resize map, it will receive sizing messages from the dialog resize code. However, one additional step is required, since the messages are not relayed to the property pages. This is accomplished with a template for the tab control that provides WM_WINDOWPOSCHANGED
message handling. Subclass the tab control in the _Init
method of the property sheet class, CPropView
.
Here is the handler for the position changed message:
LRESULT OnWindowPosChanged(UINT, WPARAM, LPARAM lParam, BOOL&) { // get window position structure from lParam LPWINDOWPOS lpWP = (LPWINDOWPOS)lParam; // initialize a property sheet variable with the parent's handle CPropertySheet m_sheet; m_sheet.m_hWnd = GetParent(); // set the size of the active property page to slightly smaller // than the tab control's new client area size ::SetWindowPos(m_sheet.GetActivePage(), NULL, 0, 0, lpWP->cx - 8, lpWP->cy - 25, SWP_NOMOVE); // release the property sheet handle since we don't own it m_sheet.m_hWnd = NULL; return 0; }
Now the tab control resizes the active property page upon notice from the property sheet resize handler. Other property pages are resized when they become active. (Note: If you wish to control the minimum or maximum size that the view or page can reach, see "Sidebar About Window Sizing" later in this article.)
Property Pages
Property pages also inherit from CDialogResize
so that their child controls are resized. Although you could override the callback procedure to change the page style, it is simpler to just pass in the appropriate window style to the dialog resize initializer like this:
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) { // init resize code. No gripper, no min size tracking, Child window style DlgResize_Init(false, false, WS_CHILD | WS_CLIPCHILDREN); return 0; }
Add whatever controls or groups of controls you wish to resize or move to the resize map, just as you would for any dialog using the dialog resize class.
Reminders
When working with property sheets and pages, remember:
- Create at least one property page in the constructor of the property sheet
- Some parameter changes must occur before the sheet or page is created
Also, please note that groupboxes do not peacefully coexist with the WS_CLIPCHILDREN
style. If you intend to use them, remove the clip children style from the property page's DlgResize_Init()
and be prepared to suffer some screen flicker.
Sidebar About Window Sizing
There are certain window styles that inherently provide the capability to limit minimum and maximum window size. For example, WS_THICKFRAME
and WS_CAPTION
. Compound styles that include either or both of those styles, such as WS_OVERLAPPEDWINDOW
, provide the capability as well. These styles receive the WM_GETMINMAXINFO
message whenever they are moved or sized.
You can enable mimimum (or maximum) size tracking in any window with those styles, such as main frame, by initializing the appropriate member variable in the constructor like this:
POINT m_ptMinTrackSize; CMainFrame() { // initialize minimum size tracking variables m_ptMinTrackSize.x = -1; m_ptMinTrackSize.y = -1; }
Next, add an entry to the message map for WM_GETMINMAXINFO
and add the following message handler:
LRESULT OnGetMinMaxInfo(UINT, WPARAM, LPARAM lParam, BOOL&) { if (m_ptMinTrackSize.x != -1 && m_ptMinTrackSize.y != -1) { LPMINMAXINFO lpMMI = (LPMINMAXINFO)lParam; lpMMI->ptMinTrackSize = m_ptMinTrackSize; } return 0; }
Finally, set m_ptMinTrackSize.x
and m_ptMinTrackSize.y
to your desired minimum values in the OnCreate
handler.
For window styles that do not inherently support minimum size tracking, use a handler for the WM_WINDOWPOSCHANGING
message to achieve similar results, as shown below. Set your x and y track values in the create or initialize handler as appropriate.
LRESULT OnWindowPosChanging(UINT, WPARAM, LPARAM lParam, BOOL&) { // get window position structure from lParam LPWINDOWPOS lpWP = (LPWINDOWPOS)lParam; // don't allow resizing below minimum x if (lpWP->cx <= m_ptMinTrackSize.x) lpWP->cx = m_ptMinTrackSize.x; // don't allow resizing below minimum y if (lpWP->cy <= m_ptMinTrackSize.y) lpWP->cy = m_ptMinTrackSize.y; return 0; }
Terms Of Use
The sample project and property sheet/page classes available with this article are free. Use them however you wish.
THIS SOFTWARE IS DISTRIBUTED AS-IS, WITHOUT WARRANTIES OF ANY KIND.