SAMPLE: Opropsheet.exe Create a Modeless OLE Property Sheet Using MFC

ID: Q234545


The information in this article applies to:


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 file is available for download from the Microsoft Software Library. Click the file name below to download the file:

Opropsheet.exe
Release Date: Jul-23-1999

For more information about how to download files from the Microsoft Software Library, please see the following article in the Microsoft Knowledge Base:
Q119591 How to Obtain Microsoft Support Files from Online Services
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)
        // <B>NOTE</B>: 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 How to 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 How to Create a Modeless CPropertySheet With Standard Buttons
MSDN, see "Inside OLE": The Property Page Site: IPropertyPageSite

© Microsoft Corporation 1999, All Rights Reserved.
Contributions by Mike Francis, Microsoft Corporation

Additional query words: CPropertySheet IPropertyPage IPropertyPageSite WS_EX_CONTROLPARENT ISpecifyPropertyPages


Keywords          : kbfile kbole kbsample kbActiveX kbCtrl kbDlg kbMFC kbVC500 kbVC600 kbDSupport kbGrpMFCATL 
Version           : winnt:5.0,6.0
Platform          : winnt 
Issue type        : 

Last Reviewed: July 23, 1999