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:
- Create a new dialog
resource (size 250x62 or 250x110 dialog units) and
using ClassWizard add to your project a new class
derived from COlePropertyPage,
e.g. class COptionsPropPage :
public COlePropertyPage
- 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?)
- 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
}
- 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:
- 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*) CoTucancode.netMemAlloc( 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.
- 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);
}
CoTucancode.netMemFree( (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.
- 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*) CoTucancode.netMemAlloc( 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.
- 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.
CoTucancode.netMemFree((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.