Property Change Notification

In our discussion about property pages, the client has usually been left out of the picture entirely: interaction between a property page and the affected objects is direct. However, a client will often want to know when changes occur to properties so it can update the properties' internal state. For example, imagine a case in which a client is using an object, say an OLE control, as a sort of Font/Color control panel. The client could create this control with its CLSID and display its property pages easily enough when necessary. The client would then want to know when the user applies changes in the property frame so that the client could alter its own color scheme and change its current font. To do this, we need a means through which an object can inform a client of changes to both individual properties and multiple properties.2 In all cases, the properties involved are called bindable because the client can bind its own specific behavior to change notifications.

In addition, having an object send a request to a client asking whether a property is allowed to change is also widely useful. It gives the client an opportunity not only to save the old state of a property but also to enforce a read-only state. In other words, the object sends a request for permission to change a property and uses the client's response to control what happens afterward. The properties for which an object supports this protocol are called request edit properties.

To support bindable and request edit properties, OLE provides specific flags that appear in type information as well as the interface IPropertyNotifySink, which contains an on changed notification and a can edit request. A dispID identifies the property in question, as shown in the following:


interface IPropertyNotifySink : IUnknown
{
HRESULT OnChanged(DISPID dispid);
HRESULT OnRequestEdit(DISPID dispid);
};

When a client wants to connect to an object so that the client can receive these notifications and requests, it must go through the connection point mechanism described in Chapter 4. If the object does not support IConnectionPointContainer, or if the object does not support IPropertyNotifySink as an outgoing interface, the client cannot receive these calls from the object at all.

When support is present, a change in a bindable property will generate a call to the sink's OnChanged member with the dispID of the property. If dispID happens to be DISPID_UNKNOWN, the object is saying that more than one bindable property changed. The client must then retrieve the properties it cares about from the object and check for changes itself. The only defined return code for OnChanged is NOERROR.

When changes are about to occur to a request edit property, the object must pass the dispID of that property to OnRequestEdit. The object can also ask whether it is allowed to change anything at all by calling OnRequestEdit with DISPID_UNKNOWN. The client can do whatever it wants inside this call—save existing states, for example—before it returns. If the client returns NOERROR, it tells the object that changes are allowed. Otherwise, S_FALSE or an error code tells the object that it is not allowed to change anything. An object should not mark a property as "request edit" unless it can fulfill this contract—that is, the object must not change properties when the client denies permission. Of course, if a request edit property is also bindable, the object must call OnRequestEdit before any call to OnChanged.

Keep in mind that when the client receives the call to OnRequestEdit, the property has not yet changed. This means that the client cannot ask the object for the new value and that OnRequestEdit cannot be used to perform data validation, which requires the new value. At the time of writing, validation is considered to be a feature that an object supports through its own custom events or requests.

Marking a property as "bindable" and "request edit" is accomplished through an object's type information. In Chapter 3, we saw four related attributes for properties: bindable, requestedit, defaultbind, and displaybind. The defaultbind attribute indicates the one bindable property that best represents the bindable state of the object overall. This is useful for clients whose user model is based on object binding rather than on property binding. The displaybind attribute, on the other hand, can be used to mark any number of otherwise bindable properties as suitable for showing to an end user. This gives the object a means of differentiating between programmatic properties and end-user–oriented ones.

A sophisticated client, automation controller, or OLE control container that exploits type information can use the presence of the bindable and requestedit attributes to allow a programmer or user to determine what should happen when changes or requests occur. This can be as simple as having Update On Change and Read-Only menu items or as complex as letting the user attach code to execute whenever these events occur for whatever properties are important.

2 Imagine how much simpler it would be for applications to deal with changes in the Windows Control Panel if Control Panel told only interested applications only about the changes to those properties that have changed, instead of broadcasting WM_WININICHANGE to every application, whether it cares or not. In response to this, applications have to recheck any property of interest, a tedious and time-consuming process.