Free size and extended styles in CPropertySheets






4.91/5 (10 votes)
Feb 24, 2000

187801
How to use extended styles and make Property Sheets of any size.
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 theCPropertySheet
, the extended style is completely lost and theCPropertySheet
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
.// Inherited call CPropertySheet::OnInitDialog();
- Set the extended style with
ModifyStyleEx
method. This could also be done inOnNCCreate
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 fieldpszTemplate
ofPROPSHEETPAGE
structure stored inCPropertyPage::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 aDIALOGEX
or aDIALOG
, 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) { // DIALOGEX resource rcOriginal.SetRect(pdlgtplex->x, pdlgtplex->y, pdlgtplex->x + pdlgtplex->cx, pdlgtplex->y + pdlgtplex->cy); } else { // DIALOG resource 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
andCPropertySheetEnh
to match our original dialog resource.// Resize PropertyPage 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.
// The exact definition for this struct can be found at // MSDN's DLGTEMPLATEEX help topic typedef struct { WORD dlgVer; WORD signature; DWORD helpID; DWORD exStyle; DWORD style; WORD cDlgItems; short x; short y; short cx; short cy; // The structure has more fields // but are variable length } DLGTEMPLATEEX; BOOL CPropertySheetEnh::OnInitDialog() { // Inherited call CPropertySheet::OnInitDialog(); // Modifying exstyle in create method // doesn't seem to work :-?, I guess // DIALOGEX property sheets are not // very well supported (if at all) // We cannot access here the exstyle // passed at creation time (dlgprop.cpp // stores temporaly in // AfxGetThreadState()->m_dwPropStyle, // but when OnInitDialog // is called that variable can // have been overwritten ModifyStyleEx(0, WS_EX_TOOLWINDOW); // Resize property sheet and tab ctrl // to exactly fit the page, as by default // PropertyPages have a non-sense // minimum width of 214DLUs (MS dixit) // Height has also a minimum size. // There must be at least one property // page, so safe accessing it is not needed // (if it has no pages MFC will not // show the PropertySheet) CPropertyPage* pppg = GetActivePage(); // Get the resource for first page // (all pages are assumed to be of // the same dimensions). 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 found, we can resize the page (which // was resized at creation time by MFC's), // It suffices modifying the layout here, // as once the first page is added, MFC never // resizes the dialog again to fit MS's minimum // sizes of PropertyPages (even if you add // new pages with AddPage() ) 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; // Support for DIALOGEX PropertyPages, // although those aren't very well supported // either if (pdlgtplex->signature == 0xFFFF) { // DIALOGEX resource rcOriginal.SetRect(pdlgtplex->x, pdlgtplex->y, pdlgtplex->x + pdlgtplex->cx, pdlgtplex->y + pdlgtplex->cy); } else { // DIALOG resource rcOriginal.SetRect(pdlgtpl->x, pdlgtpl->y, pdlgtpl->x+pdlgtpl->cx, pdlgtpl->y+pdlgtpl->cy); } // Okay, let's retrieve original // size of PropertyPage pppg->MapDialogRect(rcOriginal); CRect rcModified; pppg->GetClientRect(rcModified); // If our original PropertyPage was // not modified, the follwing code // will make dcx = 0 and dcy = 0, // so it works even for pages bigger // than the minimum property page int dcx = rcModified.Width() - rcOriginal.Width(); int dcy = rcModified.Height() - rcOriginal.Height(); // We could deflate the pages by // 0 and it would work, but just // to be proper if (dcx || dcy) { // Resize PropertyPage rcModified.DeflateRect(0,0,dcx,dcy); pppg->SetWindowPos(NULL, 0,0, rcModified.Width(), rcModified.Height(), SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOACTIVATE); // Resize TabControl 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); // Resize PropertySheet 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); } // We are not supposed to call GlobalFree // on hgbl (see the topic on LoadResource), // but MFC code does it this way (dlgprop.cpp) // Anyway the other alternative is to call // FreeResource and that's a 16bit func // that doesn't seem to do anything on win32 GlobalFree(hgbl); } } return TRUE; }