Implementing Property Pages

Property pages are an invaluable addition to most ActiveX controls’ design-time functionality. Fortunately, it’s easy to implement them. To do so, you merely need to declare an object that inherits CPropertyPage.

Working with a Property Page

The control's property page is declared in the header file using the DEFINE_PROPERTYPAGE macro, which puts it into the g_ObjectInfo table. The framework supports the creation of the property page object, but you are required to implement the static Create function, which the control wizard generates for you.

The property page is created much like a standard Windows dialog box; use your favorite resource editor to create a DIALOG resource, and then cut and paste it into your control's resource (.rc) file.

The most important method you have to implement is the DialogProc method, which is where all the work takes place. In addition to the regular Windows messages that one would expect in a DialogProc, there are a few additional messages that others working with this framework expect.

Message Description
PPM_NEWOBJECTS The control has been given some new objects. The control is expected to populate its page's controls with information from this object. Using the FirstControl and NextControl methods from CPropertyPage, the control can get the relevant information.
PPM_APPLY The control now has to apply any changes that have occurred. Use the FirstControl and NextControl methods to loop through all the objects for which the property pages were visible and apply the values; it's possible to have more than one object for which a property page is being displayed.
PPM_EDITPROPERTY The control is expected to set the focus to the control instance, which represents the property of the given dispid. This message is called only if you implement IPerPropertyBrowsing and return a value in MapPropertyToPage.
PPM_FREEOBJECTS The control frees saved pointers received in PPM_NEWOBJECTS; the objects are no longer valid.  This can be called from the property page's destructor, so be careful not to make too many assumptions about the property page.

See any of the sample controls for exact details about these messages.

Navigating Through Associated Objects

Your property pages can operate on one or more controls. When initializing, you typically receive some values from the first control. You can use the FirstControl method to get the object pointer for this control. You can then use QueryInterface for your primary dispatch interface to get properties with which to populate the page.

When told to apply the values (PPM_APPLY), apply them to all objects, which means you have to loop using FirstControl and NextControl, as follows:

for (pUnk = FirstControl(&dwCookie) ; pUnk; pUnk = NextControl(&dwCookie)) {
   hr = pUnk->QueryInterface(IID_IButton, (void **)&pButton);
   if (FAILED(hr)) continue;

   GetDlgItemText(hwnd, IDC_CAPTION, szTmp, 128);
   bstr = BSTRFROMANSI(szTmp);
   ASSERT(bstr, "Operation Failed");
   pButton->put_Caption(bstr);
   SysFreeString(bstr);
   pButton->Release();
}

You do not have to run Release on the return values of FirstControl and NextControl.

Marking Your Page as Dirty

It is moderately important to manually mark the control's property page as dirty at the appropriate times. Typically, the control does this in response to a Windows notification message, such as EN_CHANGE or BN_CLICKED. When the control wants to mark its page as dirty, it should call MakeDirty. This causes the Apply button to be enabled, if it was previously unavailable, and tells the host that the state should be saved before destroying the page.

The following code causes the page to mark itself as dirty when the user changes the text in a Text box in the property page:

case WM_COMMAND:
   switch (LOWORD(wParam)) {
      case IDC_CAPTION:
         if (HIWORD(wParam) == EN_CHANGE)
            MakeDirty();
         break;
   }
   break;