Displaying Property Pages

Playing with AutoCli2, you will notice a Properties menu item. When you select this item, AutoCli2 asks the Beeper object within it for a list of property page CLSIDs and then calls OleCreatePropertyFrame to display those property pages. As we saw earlier in this chapter, property pages involve the client, one or more objects, and the property page objects themselves. AutoCli2 and Beeper6 supply the first two pieces of the picture, and the sample BeepProp (CHAP16\BEEPPROP) contains the property page implementation, which we'll cover in the next section. (Before attempting to ask AutoCli2 to display the property pages, be sure to compile and register BeepProp.)

When we select the Properties menu item in AutoCli2, we end up in the function CApp::ShowProperties in AUTOCLI2.CPP:


void CApp::ShowProperties(void)
{
ISpecifyPropertyPages *pISPP;
CAUUID caGUID;
HRESULT hr;

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

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

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

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

if (FAILED(hr))
Message(TEXT("OleCreatePropertyFrame failed."));

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

Here we execute the steps described earlier: query for ISpecifyPropertyPages, call ISpecifyPropertyPages::GetPages, pass those page CLSIDs and the object pointers to OleCreatePropertyFrame, and on return from that function free the CLSID array allocated in GetPages. That's it. Barring failure at one point or another, you'll see the Beeper object's private property page. (See Figure 16-7 on page 799.)

In AutoCli2, you'll notice that the Properties menu item is always enabled and that we first check for the presence of ISpecifyPropertyPages in this series of operations. A slightly more sophisticated client would query for this interface within WM_INITMENUPOPUP processing to enable or disable the menu item accordingly. If the client also allowed the user to select multiple objects, it would query each selected object and enable the menu item only when everything in the selection supports property pages. A sophisticated client could also retrieve the property page CLSIDs from each object at the same time and verify that at least one property page exists in common before enabling the menu item. As stated earlier, a client should display only the set of property pages common to every object in a selection so as not to display pages that could send unexpected information to unsuspecting objects.

There are, of course, many algorithms through which the client determines the common subset of property pages for multiple objects. If you need to determine this in your own work, realize that you don't need to compare an entire CLSID to initially determine equality. You can make a significant optimization by comparing only the first DWORD of any two CLSIDs (the Data1 field in the GUID structure). Even sequentially allocated GUIDs differ in that first DWORD, although they are identical everywhere else. If those DWORDs are different, the CLSIDs must be different. You will usually eliminate most of the unmatched CLSIDs in this manner. Only when a match exists in the first DWORD should you worry about comparing the entire structure for perfect equality.

As for Beeper6's implementation of ISpecifyPropertyPages, it needs only to allocate an array of GUIDs, store the CLSIDs of the pages it wants, and return as shown in the following code, from BEEPER.CPP:


STDMETHODIMP CImpISpecifyPP::GetPages(CAUUID *pPages)
{
GUID *pGUID;

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

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

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

//Fill array now that we allocated it.
pGUID[0]=CLSID_BeeperPropertyPage;

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

No great challenges here. CLSID_BeeperPropertyPage is defined in INC\BOOKGUID.H. BeepProp is the server for that CLSID, as we'll see shortly.

If you are creating a client that supports per-property browsing, you will skip all this business with arrays of CLSIDs. Instead, you'll query the object in question for IPerPropertyBrowsing and pass the dispID of the single property to its MapPropertyToPage. You'll stuff the single CLSID that you get back into an OCPFIPARAMS structure along with the dispID and then throw the entire structure at OleCreatePropertyFrameIndirect.

As you can see, the responsibilities of both a client and an object displaying property pages are minimal, even when per-property browsing is involved. The bulk of the work happens between the property frame and the property page itself.


The Standard Font, Color, and Picture Property Pages

The run-time library for OLE Controls (shipped with the OLE Control Development Kit) provides three standard property pages: one for font selection, one for color selection, and one for manipulating picture types. The Font and Color pages were shown in Figures 16-2 and 16-3. The predefined CLSIDs for these pages are CLSID_CFontPropPage, CLSID_CColorPropPage, and CLSID_CPicturePropPage, which are defined in the CDK header file OLECTLID.H. These property pages are designed to be generic implementations for any objects that might have such properties. As standard pages, they send changes to the affected objects through IDispatch::Invoke, using standard (negative) dispIDs to identify the various properties involved. This means that you can use these pages for your own objects as long as you support these standard dispIDs. For more information on the specific values involved, see the documentation provided with the CDK.