Introduction
Dealing with CPropertySheet
objects can be quite a pain: they don't allow resizing below a width and height established by Microsoft, and extended styles don't work with them. Here I present a hack to be able to do both things. This hack involves finding and retrieving dialog templates from a resource file, dealing with DIALOG
and DIALOGEX
data and resizing several windows.
CPropertySheet
and CPropertyPage
are implemented in the file dlgprops.cpp, that file is a bunch of weirdos, winbug notes and dirty hacks (use of thread local storage instead of data members, etc.), if I may say so.
Anyway, the worst of its problems are:
- You cannot make property sheets narrower than 214 dialog units.
- If you set extended styles like
WS_EX_TOOLWINDOW
when creating the CPropertySheet
, the extended style is completely lost and the CPropertySheet
doesn't show the caption as a mini-caption.
I'm trying to make a modeless property sheet, to make something like a toolbar with several tabs, so I wanted the CPropertySheet
to have a toolbar mini-caption (WS_EX_TOOLWINDOW
) and to have a small size, in order not to hide the main canvas.
The hack step by step
The hack resides in creating a descendant of CPropertySheet
, for example CPropertySheetEnh
(for Enhanced CPropertySheet
) overriding OnInitDialog
method:
- Call the inherited
OnInitDialog
.
CPropertySheet::OnInitDialog();
- Set the extended style with
ModifyStyleEx
method. This could also be done in OnNCCreate
message handler, and you can modify any extended style here. ModifyStyleEx(0, WS_EX_TOOLWINDOW);
- Retrieve the active property page of the property sheet. There must be at least one, otherwise MFC wouldn't show the dialog.
CPropertyPage* pppg = GetActivePage();
- Find the original dialog resource of the selected
CPropertyPage
. The resource could have been set by name (LPSZ) or by resource identifier (LONG), so check this issue with the field pszTemplate
of PROPSHEETPAGE
structure stored in CPropertyPage::m_psp
. HRSRC hrsrc;
if (AfxIsValidString(pppg->m_psp.pszTemplate))
hrsrc = FindResource(pppg->m_psp.hInstance,
pppg->m_psp.pszTemplate, RT_DIALOG);
else
hrsrc = FindResource(pppg->m_psp.hInstance,
MAKEINTRESOURCE(pppg->m_psp.pszTemplate),
RT_DIALOG);
- Load the resource and lock it to be able to access its data.
HGLOBAL hgbl = LoadResource(pppg->m_psp.hInstance,
hrsrc);
LPDLGTEMPLATE pdlgtpl;
pdlgtpl = (LPDLGTEMPLATE) LockResource(hgbl);
- Access to the dialog template of the selected
CPropertyPage
, bearing in mind that the resource could be a DIALOGEX
or a DIALOG
, so we have to check its signature and discern accordingly. DLGTEMPLATEEX
is an undefined structure, you can find its definition in MSDN Library help. Once we have a reference to the template, we can get its real size. DLGTEMPLATEEX* pdlgtplex = (DLGTEMPLATEEX*) pdlgtpl;
if (pdlgtplex->signature == 0xFFFF) {
rcOriginal.SetRect(pdlgtplex->x,
pdlgtplex->y,
pdlgtplex->x + pdlgtplex->cx,
pdlgtplex->y + pdlgtplex->cy);
} else {
rcOriginal.SetRect(pdlgtpl->x,
pdlgtpl->y,
pdlgtpl->x+pdlgtpl->cx,
pdlgtpl->y+pdlgtpl->cy);
}
- Calculate the difference between the size of the original dialog resource and the size of the one modified by MFC.
pppg->GetClientRect(rcModified);
int dcx = rcModified.Width() - rcOriginal.Width();
int dcy = rcModified.Height() - rcOriginal.Height();
- Once we have that difference, we resize the
CPropertyPage
, CTabControl
and CPropertySheetEnh
to match our original dialog resource.
rcModified.DeflateRect(0,0,dcx,dcy);
pppg->SetWindowPos(NULL, 0,0,
rcModified.Width(), rcModified.Height(),
SWP_NOMOVE | SWP_NOOWNERZORDER |
SWP_NOZORDER | SWP_NOACTIVATE);
GetTabControl()->GetWindowRect(rcModified);
rcModified.DeflateRect(0,0,dcx,dcy);
GetTabControl()->SetWindowPos(NULL, 0,0,
rcModified.Width(), rcModified.Height(),
SWP_NOMOVE | SWP_NOOWNERZORDER |
SWP_NOZORDER | SWP_NOACTIVATE);
GetWindowRect(rcModified);
rcModified.DeflateRect(0,0,dcx,dcy);
SetWindowPos(NULL, 0,0,
rcModified.Width(), rcModified.Height(),
SWP_NOMOVE | SWP_NOOWNERZORDER |
SWP_NOZORDER | SWP_NOACTIVATE);
- And that's it, we only have to deallocate the allocated resources as best as we can (just read the comments in the source code to understand this affirmation).
UnlockResource(hgbl);
GlobalFree(hgbl);
There's no need of doing this process for each page in the sheet, given that MFC only resizes the property sheet before OnInitDialog
method, but never resizes it again.
The full hack
And here is the full CPropertySheetEnh::OnInitDialog
method, well commented and ready to be copy-pasted into your code.
typedef struct {
WORD dlgVer;
WORD signature;
DWORD helpID;
DWORD exStyle;
DWORD style;
WORD cDlgItems;
short x;
short y;
short cx;
short cy;
} DLGTEMPLATEEX;
BOOL CPropertySheetEnh::OnInitDialog() {
CPropertySheet::OnInitDialog();
ModifyStyleEx(0, WS_EX_TOOLWINDOW);
CPropertyPage* pppg = GetActivePage();
HRSRC hrsrc;
if (AfxIsValidString(pppg->m_psp.pszTemplate))
hrsrc = FindResource(pppg->m_psp.hInstance,
pppg->m_psp.pszTemplate,
RT_DIALOG);
else
hrsrc = FindResource(pppg->m_psp.hInstance,
MAKEINTRESOURCE(pppg->m_psp.pszTemplate),
RT_DIALOG);
if (hrsrc) {
HGLOBAL hgbl = LoadResource(pppg->m_psp.hInstance,
hrsrc);
if (hgbl) {
LPDLGTEMPLATE pdlgtpl;
pdlgtpl = (LPDLGTEMPLATE) LockResource(hgbl);
if (pdlgtpl) {
DLGTEMPLATEEX* pdlgtplex =
(DLGTEMPLATEEX*) pdlgtpl;
CRect rcOriginal;
if (pdlgtplex->signature == 0xFFFF) {
rcOriginal.SetRect(pdlgtplex->x,
pdlgtplex->y,
pdlgtplex->x + pdlgtplex->cx,
pdlgtplex->y + pdlgtplex->cy);
} else {
rcOriginal.SetRect(pdlgtpl->x,
pdlgtpl->y,
pdlgtpl->x+pdlgtpl->cx,
pdlgtpl->y+pdlgtpl->cy);
}
pppg->MapDialogRect(rcOriginal);
CRect rcModified;
pppg->GetClientRect(rcModified);
int dcx =
rcModified.Width() - rcOriginal.Width();
int dcy =
rcModified.Height() - rcOriginal.Height();
if (dcx || dcy) {
rcModified.DeflateRect(0,0,dcx,dcy);
pppg->SetWindowPos(NULL, 0,0,
rcModified.Width(),
rcModified.Height(),
SWP_NOMOVE | SWP_NOOWNERZORDER |
SWP_NOZORDER | SWP_NOACTIVATE);
GetTabControl()->GetWindowRect(rcModified);
rcModified.DeflateRect(0,0,dcx,dcy);
GetTabControl()->SetWindowPos(NULL,0,0,
rcModified.Width(),
rcModified.Height(),
SWP_NOMOVE | SWP_NOOWNERZORDER |
SWP_NOZORDER | SWP_NOACTIVATE);
GetWindowRect(rcModified);
rcModified.DeflateRect(0,0,dcx,dcy);
SetWindowPos(NULL, 0,0,
rcModified.Width(),
rcModified.Height(),
SWP_NOMOVE | SWP_NOOWNERZORDER |
SWP_NOZORDER | SWP_NOACTIVATE);
}
UnlockResource(hgbl);
}
GlobalFree(hgbl);
}
}
return TRUE;
}