Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Free size and extended styles in CPropertySheets

0.00/5 (No votes)
23 Feb 2000 1  
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