Per-Property Browsing: IPerPropertyBrowsing and IPropertyPage2

The functionality described in the previous sections provides the capability to work with properties on an object-by-object basis. Sometimes, however, a client might want to display the same user interface for a specific property, which means that it must have a way to tell the property frame to go to a particular page in the property sheet and to tell the page to highlight a specific property.

This is the purpose of the dispIDInitialProperty field in the OCPFIPARAMS structure used with OleCreatePropertyFrameIndirect. When the frame is created in this manner, it will query the objects specified in the structure's lplpUnk field for the interface IPerPropertyBrowsing:


interface IPerPropertyBrowsing : IUnknown
{
HRESULT GetDisplayString(DISPID dispid, BSTR *pbstr);
HRESULT MapPropertyToPage(DISPID dispid, LPCLSID pClsID);
HRESULT GetPredefinedStrings(DISPID dispid
, CALPOLESTR *pcaStringsOut, CADWORD *pcaCookiesOut);
HRESULT GetPredefinedValue(DISPID dispid, DWORD dwCookie
, VARIANT *pVarOut);
};

typedef struct tagCALPOLESTR
{
ULONG cElems;
LPOLESTR *pElems;
} CALPOLESTR;

typedef struct tagCADWORD
{
ULONG cElems;
DWORD *pElems;
} CADWORD;

In particular, the frame will call IPerPropertyBrowsing::MapPropertyToPage. The object will then decide whether it supports the property in question. If the property is not available, the object returns CTL_E_NOPAGEAVAILABLE. Otherwise, the object stores the proper page CLSID in *pClsID and returns S_FALSE. (S_OK has a special meaning, as we'll see in a moment.) Also, when multiple objects are being affected, asking one object in the set is as good as asking all of them: because only property pages common to all the objects are being used, all the objects will specify the same page for the same property. This means the frame needs to ask only the first object.

When the frame successfully retrieves the property page CLSID for the desired property, it queries that page for IPropertyPage2 instead of IPropertyPage:


interface IPropertyPage2 : IPropertyPage
{
HRESULT EditProperty(DISPID dispid);
};

If this interface is not available, the frame will still activate this page first, and the focus will be on the first control in that page. If the interface is available, the frame passes the property's dispID to EditProperty to tell the page to set the focus to the control containing that property. If the page returns an error, the first control on the page will get the focus.

The other member functions of IPerPropertyBrowsing support a client's ability to display an object's properties in some sort of user interface other than a property page. The S_OK return code from IPerPropertyBrowsing::MapPropertyToPage indicates not only that a specific page is available for this property (so that the client can invoke a frame with only this page showing) but that the property can also be manipulated outside the property page altogether, for example in a property sheet of the client's own design that lists client-specific and object-specific properties together.

The function IPerPropertyBrowsing::GetDisplayString provides the property label to show in such a list, and the function GetPredefinedStrings allows the client to retrieve what should appear in a drop-down list box for this property. The counted arrays involved here are again filled by the object using CoTaskMemAlloc and become the client's responsibility. The array of DWORDs from GetPredefinedStrings specifies the values attached to each string when the property itself is an integer type. Finally, GetPredefinedValue supplies the client with a default value for the property in question.

You can see that per-property browsing, combined with the general Property Pages technology, allows for a very complete and detailed user interface surrounding the direct end-user manipulation of an object's properties. A client (or the object) can choose to show all properties in all pages at once, a single property page by itself, or simply a single property outside property pages altogether.