Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / MFC
Article

Free size and extended styles in CPropertySheets

Rate me:
Please Sign up or sign in to vote.
4.91/5 (10 votes)
23 Feb 2000 186K   73   21
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 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:

  1. Call the inherited OnInitDialog.
    // Inherited call
    CPropertySheet::OnInitDialog();
  2. 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);
  3. 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();
  4. 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);
  5. 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);
  6. 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) {
       // 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);
    }
  7. 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();
  8. Once we have that difference, we resize the CPropertyPage, CTabControl and CPropertySheetEnh 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);
  9. 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;
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Spain Spain
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralYou haven't done your research before writting this article Pin
Mizan Rahman25-Feb-11 2:57
Mizan Rahman25-Feb-11 2:57 
GeneralRe: You haven't done your research before writting this article Pin
Andrew Phillips23-Jun-11 20:59
Andrew Phillips23-Jun-11 20:59 
GeneralGreat code ! two things ... Pin
nt117-Apr-09 22:19
nt117-Apr-09 22:19 
GeneralSimple way of getting the page original size Pin
EricMarcil3-Jun-08 18:26
EricMarcil3-Jun-08 18:26 
Generalproblem with japanes window Pin
emmi28-Jul-05 21:08
emmi28-Jul-05 21:08 
GeneralCrashes when... Pin
weimer18-Mar-05 12:30
weimer18-Mar-05 12:30 
GeneralGreat article! VC7 mod Pin
Bill Heitler25-Nov-04 4:32
Bill Heitler25-Nov-04 4:32 
QuestionHow do I show CProeprtySheet A in CPropertySheet B as if pages of A are seamless part of B? Pin
snowshoe16-Jul-04 8:36
snowshoe16-Jul-04 8:36 
Generalcan't i change the sheet size according to page Pin
sk92-Nov-03 18:55
sk92-Nov-03 18:55 
Generalcan't i change the sheet size according to page Pin
sk92-Nov-03 18:55
sk92-Nov-03 18:55 
hi,
i want to know how can i change the sheet size according to each page size(pages are of different sizes), in the aboe example once the sheet is created of first active page size it remains of that size only.

if you use the setActive virtual member of pages to call the function containing same code as in initdialog then also it has problem of resizing other pages!

sk9
GeneralSimpler method... Pin
R. Giskard Reventlov24-Aug-02 6:04
R. Giskard Reventlov24-Aug-02 6:04 
GeneralHelp I'm beginner... Pin
27-May-02 5:10
suss27-May-02 5:10 
GeneralRe: Help I'm beginner... Pin
5-Jun-02 23:20
suss5-Jun-02 23:20 
GeneralRe: Help I'm beginner... Pin
Anonymous27-Dec-02 8:55
Anonymous27-Dec-02 8:55 
GeneralBug inside?! Pin
sport28-Apr-02 0:26
sport28-Apr-02 0:26 
GeneralMoving the divider bar for the Property sheet... Pin
coriordan5-Apr-01 3:33
coriordan5-Apr-01 3:33 
GeneralRe: Moving the divider bar for the Property sheet... Pin
Mal Ross27-Jun-01 22:53
Mal Ross27-Jun-01 22:53 
GeneralRe: Moving the divider bar for the Property sheet... Pin
Anna-Jayne Metcalfe26-Nov-01 22:22
Anna-Jayne Metcalfe26-Nov-01 22:22 
GeneralRe: Moving the divider bar for the Property sheet... Pin
Wolfram Steinke15-Mar-02 14:54
Wolfram Steinke15-Mar-02 14:54 
GeneralOrder of calls Pin
28-Feb-01 2:02
suss28-Feb-01 2:02 
QuestionMoving the propertysheet buttons? Pin
Jeremy Davis17-Jul-00 5:22
Jeremy Davis17-Jul-00 5:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.