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 NoticeNote 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- In any MFC-based project, add a new CPropertySheet derived class. To do this, follow these steps:
- On the Project menu, click Add Class.
- In the Add Class dialog box, click MFC in the Categories tree, and then click MFC Class in the Templates pane. Click Open.
- 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.
- Repeat step 1 to add two CPropertyPage derived classes to the project. Name them CMyPropertyPage1 and CMyPropertyPage2.
- In the CPropertySheet derived class (CMyPropertySheet), add two public class member variables of type CMyPropertyPage1 and CMyPropertyPage2. To do this, follow these steps:
- In the Class View window, right-click the CMyPropertySheet node, point to Add, and then click Add Variable.
- 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.
- Click Finish to generate the CMyPropertySheet::m_Page1 member variable.
- Repeat steps a through c to create a variable of type CMyPropertyPage2 named m_Page2 (CMyPropertySheet::m_Page2).
- 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);
} - 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:
- In the Resource View window, open the Menu folder in the resource tree
view.
- Double-click the IDR_MAINFRAME menu to open the main frame menu in the menu editor.
- 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.
- Add a menu item event handler for the new Properties menu. To do this, follow these steps:
- Right-click Properties in the menu editor, and then click Add Event Handler.
- 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.
- 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.
- 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. - 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: - On the Project menu, click Add Resource. In the Add Resource dialog box, click Menu for the Resource type, and then click New.
- 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: - In the Class View window, click to select the CMyPropertySheet node.
- Click the Overrides icon at the top of the Properties dialog box to show the overridable methods of the CMyPropertySheet class.
- Select the OnInitDialog method override, click the drop-down arrow, and then click the <Add> OnInitDialog option to create the class method override.
- 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: - Add the following data members to the CMyPropertySheet class header file:
protected:
BOOL m_bNeedInit;
CRect m_rCrt;
int m_nMinCX;
int m_nMinCY; - 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);
} - 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: - 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.
- 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.
- 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: | Major | Last Reviewed: | 12/30/2005 |
---|
Keywords: | kbHOWTOmaster kbProgramming KB325613 kbAudDeveloper |
---|
|