Property Pages for ActiveX Controls

I am not going to reinvent the wheel and everything I am going
to talk about is documented here and there. The only reason I am
putting all this in one place is because I spent a week to get
all parts together.

ActiveX introduced a new way to display the propery pages.
Each page is a separate control. You may find the GUID for these
controls in the registry, but you cannot find them using
"Object viewer" or importing the type library of the
main ActiveX control.

How to create a property page for your control? This is not a
big deal and this part is well documented and supplied with
numerous examples. You simply follow these steps:

  1. Create a new dialog resource (size 250×62 or 250×110
    dialog units) and using ClassWizard add to your project a
    new class derived from COlePropertyPage, e.g. class COptionsPropPage : public
    COlePropertyPage
  2. Create two new string resources (add them to your string
    table resource): one – for the caption of your new
    property page and another one – for the property page
    name (do you remember – property page is an object?)
  3. At the top of *.cpp file, created for your property page,
    find the following functions and replace 0-s (otherwise
    regsvr32 will crash:
    /////////////////////////////////////////////////////////////////////////////
    // COptionsPropPage::COptionsPropPageFactory::UpdateRegistry-
    // Adds or removes system registry entries for COptionsPropPage

    BOOL COptionsPropPage::COptionsPropPageFactory::UpdateRegistry(BOOL bRegister)
    {
    // TODO: Define string resource for page type; replace ‘0’ below with ID.

    if (bRegister)
    return AfxOleRegisterPropertyPageClass(AfxGetInstanceHandle(),
    m_clsid, IDS_PPG_OPTIONS);
    else
    return AfxOleUnregisterClass(m_clsid, NULL);
    }

    /////////////////////////////////////////////////////////////////////////////
    // COptionsPropPage::COptionsPropPage – Constructor
    // TODO: Define string resource for page caption; replace ‘0’ below with ID.

    COptionsPropPage::COptionsPropPage() :
    COlePropertyPage(IDD, IDS_PPG_OPTIONS_CAPTION)
    {
    //{{AFX_DATA_INIT(CDJNasdaqLIIOptionsPropPage)
    //}}AFX_DATA_INIT
    }

  4. Add *.h file of your new property page to *.cpp file of
    your control and modify the following macros as follows:
    // TODO: Add more property pages as needed.  Remember to increase the count!
    BEGIN_PROPPAGEIDS(CDoSomethingCtrl, 4)
    PROPPAGEID(COptionsPropPage::guid)
    PROPPAGEID(CLSID_CColorPropPage)
    PROPPAGEID(CLSID_CFontPropPage)
    PROPPAGEID(CLSID_CPicturePropPage)
    END_PROPPAGEIDS(CDJNasdaqLIICtrl)

    Pay attention that last three lines will automatically
    insert stock property pages for color, font and pictures
    respectively (you don’t have to create them separately,
    Microsoft did it for you)

Case 1: Your control wants to display
its own property sheet in runtime mode.

This case is simple one, but is not documented. You have to do
the following:

  1. Add GetPages() method to the implementation file of your
    control:
    // This method returns array of property pages used then by
    // OLE container to bring property pages to the user
    STDMETHODIMP CDoSomethingCtrl::“#630000”>GetPages(CAUUID *pPages)
    {
    GUID *pGUID;
    const unsigned CPROPPAGES = 4;

    pPages->cElems = 0;
    pPages->pElems = NULL;

    pGUID = (GUID*) CoTaskMemAlloc( CPROPPAGES * sizeof(GUID) );

    if( NULL == pGUID )
    {
    return ResultFromScode(E_OUTOFMEMORY);
    }

    // Fill the array of property pages now
    pGUID[0] = COptionsPropPage::guid;
    pGUID[2] = CLSID_CFontPropPage;
    pGUID[3] = CLSID_CColorPropPage;
    pGUID[4] = CLSID_CPicturePropPage;

    //Fill the structure and return
    pPages->cElems = CPROPPAGES;
    pPages->pElems = pGUID;
    return NOERROR;
    }

    Pay attention that you may have different set of pages
    from your original one. You are not comitted to display
    the same property pages in design- and runtime modes.

  2. Add OnShowProperties method to the implementation file of
    your control:
    // This method is usually implemented by container to display
    // the properties of a control at runtime. We need it for the control
    // itself to display property pages at runtime.
    void CDoSomethingCtrl::OnShowProperties()
    {
    CAUUID caGUID;
    HRESULT hr;
    LPDISPATCH pIDispatch = GetIDispatch(TRUE);
    LCID lcid = AmbientLocaleID();

    GetPages(&caGUID);

    hr = OleCreatePropertyFrame(
    m_hWnd,
    10,
    10,
    OLESTR("Do something control"),
    1,
    (IUnknown**) &pIDispatch,
    caGUID.cElems,
    caGUID.pElems,
    lcid,
    0L,
    NULL );
    if( FAILED(hr) )
    {
    ErrorMsg(IDS_FAILED_DISPLAY_PROPERTY_PAGES, MB_ICONERROR);
    }

    CoTaskMemFree( (void*) caGUID.pElems );
    return;
    }

    A few comments:
    – OleCreatePropertyFrame is the method to display the
    property pages you selected in GetPages() method before;
    – m_hWnd
    member you have because your control is
    derived from CWnd;
    – The fourth parameter (OLESTR string) is just the
    caption of your property sheet and you may type whatever
    you want there;
    – I use GetIDispatch(TRUE) of CCmdTarget to get a pointer
    to IDispatch interface of my control, but actually all
    you need is a pointer to IUnknown. If you already have a
    pointer to IUnknown of your control, just use it.

    That’s it, folks! Simple, isn’t it? I have no idea why
    it is so difficult to compile this information from
    multiple sources 🙂

Case 1: Your container wants to display
property sheet of your control in runtime mode.

Actually, as I said before, this is documented in "Inside
OLE" of Brockschmidt (pp795+.) Unfortunately, Brockschmidt
assumes that you create your OLE control from scratch without
COleControl class. If you have already derived your control from
COleControl class (as you normally do), you already have a train
of interfaces which you can see in OLE Object Viewer. In this
case the following steps demonstrate how to display the property
pages in runtime mode in your container.

  1. Add GetPages() method to the implementation file
    of your control
    this way:
    // This method returns array of property pages used then by
    // OLE container to bring property pages to the user
    STDMETHODIMP CDoSomethingCtrl::“#630000”>XSpecifyPropertyPages::GetPages(CAUUID *pPages)
    {
    GUID *pGUID;
    const unsigned CPROPPAGES = 4;

    pPages->cElems = 0;
    pPages->pElems = NULL;

    pGUID = (GUID*) CoTaskMemAlloc( CPROPPAGES * sizeof(GUID) );

    if( NULL == pGUID )
    {
    return ResultFromScode(E_OUTOFMEMORY);
    }

    // Fill the array of property pages now
    pGUID[0] = COptionsPropPage::guid;
    pGUID[2] = CLSID_CFontPropPage;
    pGUID[3] = CLSID_CColorPropPage;
    pGUID[4] = CLSID_CPicturePropPage;

    //Fill the structure and return
    pPages->cElems = CPROPPAGES;
    pPages->pElems = pGUID;
    return NOERROR;
    }

    Pay attention to this strange class
    XSpecifyPropertyPages: there is no place to declare this.
    Where does it come from? From COleControl, of course.
    Declaration of COleControl class includes the following
    lines:

    // ISpecifyPropertyPages
    BEGIN_INTERFACE_PART(SpecifyPropertyPages, ISpecifyPropertyPages)
    INIT_INTERFACE_PART(COleControl, SpecifyPropertyPages)
    STDMETHOD(GetPages)(CAUUID*);
    END_INTERFACE_PART(SpecifyPropertyPages)

    where BEGIN_INTERFACE_PART is further decoded to

    #define BEGIN_INTERFACE_PART(localClass, baseClass)
    class X##localClass : public baseClass
    {
    public:
    STDMETHOD_(ULONG, AddRef)(); and so on

    This is where XSpecifyPropertyPages comes from.

  2. Add OnShowProperties method to the implementation file of
    your container (this is taken as is from
    the Inside OLE of BrockSchmidt with a note below):
    void CApp::OnShowProperties(void)
    {
    ISpecifyPropertyPages *pISPP;
    CAUUID caGUID;
    HRESULT hr;
    LCID lcid = AmbientLocaleID();

    if (FAILED(m_pIDispatch->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pISPP)))
    {
    AfxMessageBox("Object has no property pages.");
    return;
    }

    hr=pISPP->GetPages(&caGUID);
    pISPP->Release();

    if (FAILED(hr))
    {
    AfxMessageBox("Failed to retrieve property page GUIDs.");
    return;
    }

    hr=OleCreatePropertyFrame(m_hWnd, 10, 10, OLETEXT("Beeper")
    , 1, (IUnknown **)&m_pIDispatch, caGUID.cElems
    , caGUID.pElems, lcid, 0L, NULL);

    if (FAILED(hr))
    AfxMessageBox("OleCreatePropertyFrame failed.");

    //Free GUIDs.
    CoTaskMemFree((void *)caGUID.pElems);
    return;
    }

    Notes: I changed a few minor things
    to get this code compiled imeediately without any further
    changes. I changed Message method of BrockSchmidt to the
    standard AfxMessageBox(). Then, I replaced the 9th
    parameter from m_lcid to 0L because I am not interested
    in locale information (of course you have to take care of
    it if you support multiple languages!!!)

The theory of property pages, property pages browsing and
notifications is slightly more difficult than that. I sincerely
encourage you to read the sixteenth chapter of "Inside
OLE" of BrockSchmidt to get the full picture of OLE property
pages.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read