Click here to Skip to main content
Click here to Skip to main content

Free size and extended styles in CPropertySheets

, 23 Feb 2000
Rate this:
Please Sign up or sign in to vote.
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

Share

About the Author

No Biography provided

Comments and Discussions

 
GeneralCrashes when... Pinmemberweimer18-Mar-05 13:30 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 24 Feb 2000
Article Copyright 2000 by Antonio Tejada Lacaci
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid