How to design a resizable MFC property sheet in Visual C++ .NET or Visual C++ 2005 (325613)



The information in this article applies to:

  • Microsoft Visual C++ .NET (2002)
  • Microsoft Visual C++ .NET (2003)
  • Microsoft Visual C++ 2005 Express Edition

This article was previously published under Q325613

Notice

Note Microsoft Visual C++ .NET (2002) and Microsoft Visual C++ 2005 support both the managed code model that is provided by the Microsoft .NET Framework and the unmanaged native Microsoft Windows code model. The information in this article applies only to unmanaged Visual C++ code.

SUMMARY

This step-by-step article shows you how to add a new property sheet class to an existing Microsoft Foundation Classes (MFC) project. It then shows you how to customize the sheet by handling resize requests and by providing a menu bar for the sheet.

Sometimes a program requires more flexibility from the property sheets that it uses than the default MFC offers. These steps demonstrate how to write a base class, CMyPropertySheet, that you can use to provide this functionality. This new class is a drop-in replacement for the MFC CPropertySheet class. It gives the additional functionality that is described in the following sections.

Create a dialog box

  1. In any MFC-based project, add a new CPropertySheet derived class. To do this, follow these steps:
    1. On the Project menu, click Add Class.
    2. In the Add Class dialog box, click MFC in the Categories tree, and then click MFC Class in the Templates pane. Click Open.
    3. In the MFC Class Wizard, in the Class name text box, type CMyPropertySheet, and then click to select CPropertySheet in the Base class drop-down list box. Click Finish to create the class.
  2. Repeat step 1 to add two CPropertyPage derived classes to the project. Name them CMyPropertyPage1 and CMyPropertyPage2.
  3. In the CPropertySheet derived class (CMyPropertySheet), add two public class member variables of type CMyPropertyPage1 and CMyPropertyPage2. To do this, follow these steps:
    1. In the Class View window, right-click the CMyPropertySheet node, point to Add, and then click Add Variable.
    2. In the Add Member Variable Wizard, type CMyPropertyPage1 in the Variable type text box, and then type m_Page1 in the Variable name text box.
    3. Click Finish to generate the CMyPropertySheet::m_Page1 member variable.
    4. Repeat steps a through c to create a variable of type CMyPropertyPage2 named m_Page2 (CMyPropertySheet::m_Page2).
  4. In the two CMyPropertySheet constructors, call the AddPage method to add the two PropertyPage members that you created in step 3 to the PropertySheet. In each CMyPropertySheet::CMyPropertySheet constructor, add the following method body code:
    CMyPropertySheet::CMyPropertySheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
    	:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
    {
        AddPage(&m_Page1);
        AddPage(&m_Page2);
    }
  5. Create a new Menu Item for the CMainFrame derived window menu. You will use this menu item to create the CMyPropertySheet dialog box at run time. To create the menu item, follow these steps:
    1. In the Resource View window, open the Menu folder in the resource tree view.
    2. Double-click the IDR_MAINFRAME menu to open the main frame menu in the menu editor.
    3. Click the View menu item, and then type a new menu item named Properties at the bottom of the existing View menu.

      Note If your MFC project is based on an MDI application type, you must first close the default child window that appears before you can view the IDR_MAINFRAME and get the Properties menu item.
  6. Add a menu item event handler for the new Properties menu. To do this, follow these steps:
    1. Right-click Properties in the menu editor, and then click Add Event Handler.
    2. In the Event Handler Wizard, verify that COMMAND is selected in the Message type list box, and then click CMainFrame in the Class list list box.
    3. Verify that the Function handler name edit control contains an appropriate name, and then click Add and Edit to create the Properties menu item event handler in the CMainFrame class.
    4. In the code editor window that appears, paste the following code method body in the event handler:
      void CMainFrame::OnViewProperties()
      {
          CMyPropertySheet propSheet("Property Sheet");
          propSheet.DoModal();
      }
    Note Make sure that you #include the header file that contains the CMyPropertySheet class definition to your CMainFrame source file.
  7. Build and then run the program.
Click View, and then click Properties to display the CMyPropertySheet dialog box. The following steps add a menu and resize features to the CMyPropertySheet dialog box.

Add a new menu

To add a new Menu resource for the CMyPropertySheet dialog box, follow these steps:
  1. On the Project menu, click Add Resource. In the Add Resource dialog box, click Menu for the Resource type, and then click New.
  2. Double-click this new menu resource in the Resource View to open the menu editor for the new menu. Type some text in the text box as a placeholder menu item that will appear when the menu is created.

Add resize features

To turn on resizable borders, you must override DoModal, and then give a callback function that will set the appropriate styles for the property sheet. To do this, you must provide the following two functions (in both the header file and the source file for the CMyPropertySheet class):
  • The static property sheet callback function, XmnPropSheetCallback:
    // This function must be a STATIC method. 
    // Callback to allow you to set the default window styles 
    // for the property sheet.
    int CALLBACK CMyPropertySheet::XmnPropSheetCallback(HWND hWnd, UINT message, LPARAM lParam)
    {
       extern int CALLBACK AfxPropSheetCallback(HWND, UINT message, LPARAM lParam);
       // XMN: Call MFC's callback.
       int nRes = AfxPropSheetCallback(hWnd, message, lParam);
    
       switch (message)
       {
       case PSCB_PRECREATE:
          // Set your own window styles.
          ((LPDLGTEMPLATE)lParam)->style |= (DS_3DLOOK | DS_SETFONT
             | WS_THICKFRAME | WS_SYSMENU | WS_POPUP | WS_VISIBLE | WS_CAPTION);
          break;
       }
       return nRes;
    }
    The header file declaration for the callback function:
    static int CALLBACK XmnPropSheetCallback(HWND hWnd, UINT message, LPARAM lParam);
    					
  • The DoModal override:
    // By overriding DoModal, you can hook the callback to
    // the prop sheet creation.
    INT_PTR CMyPropertySheet::DoModal(void)
    {
       // Hook into property sheet creation code
       m_psh.dwFlags |= PSH_USECALLBACK;
       m_psh.pfnCallback = XmnPropSheetCallback;
       
       return CPropertySheet::DoModal();
    }
    Set the header file declaration:
    INT_PTR DoModal(void);

Create an override for OnInitDialog

In the previous procedures, you created a property sheet that you can drag to resize by using the mouse. To add a menu, you must provide an override for OnInitDialog. To do this, follow these steps:
  1. In the Class View window, click to select the CMyPropertySheet node.
  2. Click the Overrides icon at the top of the Properties dialog box to show the overridable methods of the CMyPropertySheet class.
  3. Select the OnInitDialog method override, click the drop-down arrow, and then click the <Add> OnInitDialog option to create the class method override.
  4. When the code editor window opens, paste the following sample code in the code editor to complete the CMyPropertySheet::OnInitDialog method body.

    Note The IDR_MENU1 resource ID shown is the ID of the new menu for the CMyPropertySheet dialog box that you created earlier.
    BOOL CMyPropertySheet::OnInitDialog() 
    {
       BOOL bResult = CPropertySheet::OnInitDialog();
        
       // Add a new menu.
       CMenu *pMenu = new CMenu; 
       pMenu->LoadMenu(IDR_MENU1);
       SetMenu(pMenu);
       pMenu->Detach();
       delete pMenu;
    
       // Adjust property sheet size to account for the new menu.
       CRect r;  GetWindowRect(&r);
       r.bottom += GetSystemMetrics(SM_CYMENU);
       MoveWindow(r);
    
       return bResult;
    }

Create a resize policy

You must create a resize policy. The clearest way to do so is to resize the embedded tab control according to the resize request, and then move the buttons present on the sheet. To do this, follow these steps:
  1. Add the following data members to the CMyPropertySheet class header file:
    protected:
       BOOL   m_bNeedInit;
       CRect  m_rCrt;
       int    m_nMinCX;
       int    m_nMinCY;
  2. Initialize these new data members, and then set m_bNeedInit to TRUE in the two CMyPropertySheet constructors (one constructor is shown here):
    CMyPropertySheet::CMyPropertySheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
    	:CPropertySheet(pszCaption, pParentWnd, iSelectPage)	 
    	 , m_bNeedInit(TRUE)
    	 , m_nMinCX(0)
    	 , m_nMinCY(0)
    {
       AddPage(&m_Page1);
       AddPage(&m_Page2);
    }
  3. Add the following lines of sample code to the end of OnInitDialog:
    BOOL CMyPropertySheet::OnInitDialog() 
    {
       // ...
       // Init m_nMinCX/Y
       m_nMinCX = r.Width();
       m_nMinCY = r.Height();
       // After this point, the resize code runs.
       m_bNeedInit = FALSE;
       GetClientRect(&m_rCrt);
    
       return bResult;
    }

Create a handler for the WM_SIZE event

To create a handler for the WM_SIZE event, follow these steps:
  1. In the Class View window, click to select the CMyPropertySheet node. In the Properties dialog box, click the Messages icon button to show the Windows messages that this class can handle.
  2. In the Properties dialog box, click to select the WM_SIZE message, click the drop-down arrow, and then click the <ADD> OnSize option to create the WM_SIZE message handler method.
  3. A default OnSize message handler appears in a code editor window. Paste the following sample code in the code editor window to complete the WM_SIZE message handler:
    // Handle WM_SIZE events by resizing the tab control and by 
    // moving all of the buttons on the property sheet.
    void CMyPropertySheet::OnSize(UINT nType, int cx, int cy) 
    {
       CRect r1; 
       CPropertySheet::OnSize(nType, cx, cy);
    
       if (m_bNeedInit)
          return;
    
       CTabCtrl *pTab = GetTabControl();
       ASSERT(NULL != pTab && IsWindow(pTab->m_hWnd));
        
       int dx = cx - m_rCrt.Width();
       int dy = cy - m_rCrt.Height();
       GetClientRect(&m_rCrt);
    
       HDWP hDWP = ::BeginDeferWindowPos(5);
    
       pTab->GetClientRect(&r1); 
       r1.right += dx; r1.bottom += dy;
       ::DeferWindowPos(hDWP, pTab->m_hWnd, NULL,
                        0, 0, r1.Width(), r1.Height(),
                        SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);
    
       // Move all of the buttons with the lower and right sides.
       for (CWnd *pChild = GetWindow(GW_CHILD);
            pChild != NULL;
            pChild = pChild->GetWindow(GW_HWNDNEXT))
    
       {
    
          if (pChild->SendMessage(WM_GETDLGCODE) & DLGC_BUTTON)
          {
             pChild->GetWindowRect(&r1); ScreenToClient(&r1); 
             r1.top += dy; r1.bottom += dy; r1.left+= dx; r1.right += dx;
             ::DeferWindowPos(hDWP, pChild->m_hWnd, NULL,
                              r1.left, r1.top, 0, 0,
                              SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOZORDER);
          }
          // Resize everything else.
          else
          {
             pChild->GetClientRect(&r1); 
    	 r1.right += dx; r1.bottom += dy;
    	 ::DeferWindowPos(hDWP, pChild->m_hWnd, NULL, 0, 0, 
                   r1.Width(), r1.Height(),
                   SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);
          }
    
       }
    
       ::EndDeferWindowPos(hDWP);
    }

Create a handler for the WM_GETMINMAXINFO message

You must create a handler for the WM_GETMINMAXINFO message. Follow the procedure in the "Create a Handler for the WM_SIZE Event" section to create a message handler method for the WM_GETMINMAXINFO message in the CMyPropertySheet class. Use the following sample code for this method:
void CMyPropertySheet::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) 
{
   CPropertySheet::OnGetMinMaxInfo(lpMMI);
   lpMMI->ptMinTrackSize.x = m_nMinCX;
   lpMMI->ptMinTrackSize.y = m_nMinCY;
}

REFERENCES

For more information, click the following article numbers to view the articles in the Microsoft Knowledge Base:

143291 How to resize CPropertyPages at run time in Visual C++

146916 How to create a modeless CPropertySheet with standard buttons

300606 How to design a resizable MFC property sheet


Modification Type:MajorLast Reviewed:12/30/2005
Keywords:kbHOWTOmaster kbProgramming KB325613 kbAudDeveloper