DOCUMENT:Q234545 03-MAY-2001 [visualc] TITLE :Opropsheet.exe Modeless OLE Property Sheet Using MFC PRODUCT :Microsoft C Compiler PROD/VER:winnt:5.0,6.0 OPER/SYS: KEYWORDS:kbsample kbActiveX kbCOMt kbCtrl kbDlg kbMFC kbPropSheet kbVC500 kbVC600 kbDSupport kbG ====================================================================== ------------------------------------------------------------------------------- The information in this article applies to: - The Microsoft Foundation Classes (MFC), used with: - Microsoft Visual C++, 32-bit Enterprise Edition, version 5.0 - Microsoft Visual C++, 32-bit Professional Edition, version 5.0 - Microsoft Visual C++, 32-bit Enterprise Edition, version 6.0 - Microsoft Visual C++, 32-bit Professional Edition, version 6.0 - Microsoft Visual C++, 32-bit Learning Edition, version 6.0 ------------------------------------------------------------------------------- SUMMARY ======= The OLE API OleCreatePropertyFrame() is used to create a modal property sheet displaying the property pages supported by a given object. This API does not support modeless creation. There is no equivalent API for creating a modeless OLE property sheet. This article explains how to create one using MFC. Opropsheet.exe is a self extracting archive that contains a Visual C++ 6.0 workspace (Opropsheet.dsw). This workspace contains two projects, CTRL and PROPCLI. The CTRL project is an ATL full control. This control supports two OLE property pages, the system-provided stock color and a custom property page called PropPageCustom. The PROPCLI project is an MFC dialog box-based client application for the CTRL control. This project also contains the code for the modeless property sheet. After invoking the property sheet, you can change properties and see the control get updated; because it is modeless, you can leave it active and return to the main UI. MORE INFORMATION ================ The following files are available for download from the Microsoft Download Center: Opropsheet.exe For additional information about how to download Microsoft Support files, click the article number below to view the article in the Microsoft Knowledge Base: Q119591 How to Obtain Microsoft Support Files from Online Services Microsoft used the most current virus detection software available on the date of posting to scan this file for viruses. Once posted, the file is housed on secure servers that prevent any unauthorized changes to the file. To create a modeless OLE Property Sheet, you need to write your own property frame and property page site. This is not a trivial task because you also need to implement the rest of what OleCreatePropertyFrame gives you, that is, a parent dialog box with a tab control (the property sheet), display and hiding of property pages, navigation between pages and the property sheet, creation and destruction of the property page objects, and implementation of IPropertyPageSite. Fortunately, the MFC classes CPropertySheet and CPropertyPage help with this by handling all of the page creation, display, hiding, navigation, and modeless creation. The following outline will help you get started: 1. Implement IPropertyPageSite. Because an instance of IPropertyPageSite is required for each page, this can be done in the derived CPropertyPage class. Use the ClassWizard to create a class derived from CPropertyPage and add automation support. Even though you are not implementing automation, this gives you many of the macros necessary for implementing interfaces. See "TN038: MFC/OLE IUnknown Implementation" and "Tom's Handy Dandy MFC/COM/MIDL Recipe Book for Creating Custom Interfaces," in the Visual C++ documentation for details on implementing custom interfaces in MFC. For implementation details of IPropertyPageSite, see "Inside OLE" on MSDN at: The Property Page Site: IPropertyPageSite 2. In the Create() call for your CPropertySheet class, called for modeless creation, ISpecifyPropertyPages::GetPages() is called to retrieve the count and CLSID of the pages supported by the server. Create your CPropertyPage objects on the heap and add them to the property page collection via AddPage(), as follows: BOOL COlePropSheet::Create(CWnd* pParentWnd, DWORD dwStyle, DWORD dwExStyle) { ASSERT(m_pUnkServer != NULL); // Get number of property pages that reside in object. ISpecifyPropertyPages * lpSPP; HRESULT hRes = m_pUnkServer->QueryInterface(IID_ISpecifyPropertyPages, (LPVOID*)&lpSPP); if( FAILED( hRes ) ) AfxThrowOleException( hRes ); CAUUID cauuid; lpSPP->GetPages(&cauuid); m_iPageCnt = cauuid.cElems; lpSPP->Release(); for (UINT i = 0; i < m_iPageCnt; i++) { // Create CPropertyPages and Property page sites. COlePropPage * pPage = new COlePropPage(cauuid.pElems[i], m_pUnkServer); if (pPage == NULL) return FALSE; m_pageArray.Add(pPage); AddPage((COlePropPage*)m_pageArray[i]); } return CPropertySheet::Create(pParentWnd, dwStyle, dwExStyle); } 3. Create the COM property pages as children of MFC property pages. The idea is that in the constructor of your derived CPropertyPage, you call CoCreateInstance() to create the COM property page and parent it to a placeholder property page that your CPropertyPage provides. The setting of the page site and the number of objects the page supports is also done here. Additionally, IPropertyPage::GetPageInfo() is used to retrieve the title of the property page. This will be used as the text for the tab of the property page. The "parenting" of the placeholder page to the COM page takes place in the OnInitDialog handler of the property page (see below). The COM object is released in the page destructor. Your property page class should have data members for the CLSID of the COM property page, IProperyPage pointer, and an LPUNKNOWN pointer back to the server object. For example: COlePropPage::COlePropPage(CLSID clsidPage, LPUNKNOWN lpUnkPage) : CPropertyPage(COlePropPage::IDD), m_clsidPage(clsidPage), m_pObject(lpUnkPage) { HRESULT hRes = E_FAIL; m_pPropPage=NULL; try { // Create COM Property page and get IPropertyPage interface. hRes = CoCreateInstance( m_clsidPage, NULL, CLSCTX_INPROC, IID_IPropertyPage, (void**)&m_pPropPage ); if( FAILED( hRes ) ) AfxThrowOleException( hRes ); hRes = m_pPropPage->SetPageSite( (IPropertyPageSite*) GetInterface( &IID_IPropertyPageSite ) ); if( FAILED( hRes ) ) AfxThrowOleException( hRes ); hRes = m_pPropPage->SetObjects( 1, &m_pObject ); if( FAILED( hRes ) ) AfxThrowOleException( hRes ); IMalloc *pIMalloc; if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc))) AfxThrowOleException(E_FAIL); PROPPAGEINFO* pPPI = (PROPPAGEINFO*) pIMalloc->Alloc(sizeof(PROPPAGEINFO)); pPPI->cb = sizeof(PROPPAGEINFO); hRes = m_pPropPage->GetPageInfo(pPPI); m_strCaption.Format("%S", pPPI->pszTitle); m_psp.pszTitle = m_strCaption; m_psp.dwFlags |= PSP_USETITLE; pIMalloc->Free(pPPI); pIMalloc->Release(); } catch (COleException * e) { throw (e); } file://{{AFX_DATA_INIT(COlePropPage) // NOTE: The ClassWizard adds member initialization here file://}}AFX_DATA_INIT } 4. One problem with using the COM property pages as children of MFC property pages is that it creates a nested dialog-box scenario. In this case, the COM property page must have the WS_EX_CONTROLPARENT style to allow the user to use mnemonics and the dialog box navigation keys to move the focus to and from the controls in the COM property page. Adding this style is done in the OnInitDialog handler of the page, as follows: // Parent COM Property page to placeholder page, // add WS_EX_CONTROLPARENT to COM property page. BOOL COlePropPage::OnInitDialog() { CPropertyPage::OnInitDialog(); HRESULT hRes = E_FAIL; try { // Activate COM property page and parent to placeholder page. CRect pgrect; GetWindowRect(&pgrect); ScreenToClient(pgrect); hRes = m_pPropPage->Activate( GetSafeHwnd(), pgrect, TRUE ); if( FAILED( hRes ) ) AfxThrowOleException( hRes ); hRes = m_pPropPage->Show( SW_SHOW ); if( FAILED( hRes ) ) AfxThrowOleException( hRes ); } catch (COleException * e) { throw (e); } // Add WS_EX_CONTROLPARENT style to property page // necessary to allow tabbing from page to sheet. // Get COM Prop Page. CWnd * pWnd = GetWindow(GW_CHILD); CString str; ::GetClassName(pWnd->GetSafeHwnd(), str.GetBuffer(128), 128); str.ReleaseBuffer(); if (str == (CString)"#32770") // #32770 is dialog class name pWnd->ModifyStyleEx(0,WS_EX_CONTROLPARENT,0); return TRUE; // Return TRUE unless you set the focus to a control. } 5. The creation and destruction of the modeless property sheet follows the same rules for a modeless dialog box. For additional information, please see the following articles in the Microsoft Knowledge Base: Q103788 Creating a modeless dialog box with MFC libraries Q117500 Using Accelerators with MFC Modeless Dialog 6. The Create() call to create the property sheet, COM property pages, and MFC property pages resembles the following: m_pModelessSheet = new CModelessOPS(m_ctrl.GetControlUnknown(), _T("Modeless Ole Property Sheet")); m_pModelessSheet->Create(); With the exception of the additional parameter in the property sheet constructor, this is exactly how you would create a "normal" modeless property sheet. 7. The default MFC implementation of modeless property sheets does not include the display of the Apply, OK, or Cancel button. Normally, when the Apply or OK button is clicked, the properties for the object are updated. How are the changes made in the property pages applied to the object for this sample? In IPropertyPageSite::OnStatusChanged (implemented in the page class) and the OnKillFocus handler for the page, the IPropertyPage::Apply method is called. For additional information about how to display the Apply, OK, and Cancel buttons, please see the following article in the Microsoft Knowledge Base: Q146916 HOWTO: Create a Modeless CPropertySheet With Standard Buttons REFERENCES ========== Visual C++ documentation: TN038: MFC/OLE IUnknown Implementation. Visual C++ documentation: Tom's Handy Dandy MFC/COM/MIDL Recipe Book for Creating Custom Interfaces. For additional information, please see the following articles in the Microsoft Knowledge Base: Q103788 Creating a modeless dialog box with MFC libraries Q117500 Using Accelerators with MFC Modeless Dialog Q146916 HOWTO: Create a Modeless CPropertySheet With Standard Buttons MSDN, see "Inside OLE": The Property Page Site: IPropertyPageSite (http://msdn.microsoft.com/library/books/techlang/inole/D1/S1277.HTM) (c) Microsoft Corporation 1999, All Rights Reserved. Contributions by Mike Francis, Microsoft Corporation Additional query words: CPropertySheet IPropertyPage IPropertyPageSite WS_EX_CONTROLPARENT ISpecifyPropertyPages ====================================================================== Keywords : kbsample kbActiveX kbCOMt kbCtrl kbDlg kbMFC kbPropSheet kbVC500 kbVC600 kbDSupport kbGrpDSMFCATL Technology : kbAudDeveloper kbMFC Version : winnt:5.0,6.0 ============================================================================= THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY. Copyright Microsoft Corporation 2001.