Using and Implementing IPropertyNotifySink

An interface as simple as IPropertyNotifySink is not that complicated to work with in code. In fact, the bulk of the code within this interface exists for the purpose of working with IConnectionPointContainer and IConnectionPoint, as we saw in Chapter 4. The samples AutoCli2 (CHAP16\AUTOCLI2) and Beeper6 (CHAP16\BEEPER6) provide a quick demonstration of this interface. (Be sure to register Beeper6 before running AutoCli2.) Beeper6 is a modification of Beeper2 from Chapter 14. The first change is to include a few of the necessary flags in type information, as we can see in BEEP0000.ODL, where the interface IBeeper is defined:


.
.
.
interface IBeeper : IUnknown
{
[propget, helpstring("The current sound")
, bindable, defaultbind, displaybind, requestedit
]
long Sound(void);

[propput
, bindable, defaultbind, displaybind, requestedit
]
void Sound([in] long lSound);

[helpstring("Play the current sound")]
long Beep(void);
}

When you define an interface in which specific member functions represent properties, you must mark both propget and propput members with the same attributes. MKTYPLIB will complain that you've defined a duplicate property name otherwise.

Supporting a bindable or request edit property from within the object itself is first a matter of implementing the necessary connection point support. Beeper6 uses the same sort of code that we've seen in other samples (such as CHAP05\POLYLINE). When Beeper6 does have an IPropertyNotifySink pointer in hand, it calls OnRequestEdit and OnChanged as appropriate from within its IBeeper::put_Sound method (BEEPER.CPP):


STDMETHODIMP_(void) CBeeper::put_Sound(long lSound)
{
if (MB_OK!=lSound && MB_ICONEXCLAMATION!=lSound
&& MB_ICONQUESTION!=lSound && MB_ICONHAND!=lSound
&& MB_ICONASTERISK!=lSound)
{
m_pImpIDispatch->Exception(EXCEPTION_INVALIDSOUND);
return;
}

if (NULL!=m_pIPropNotifySink)
{
//If we didn't get permission, stop now.
if (NOERROR!=m_pIPropNotifySink->OnRequestEdit(PROPERTY_SOUND))
return;
}

m_lSound=lSound;

if (NULL!=m_pIPropNotifySink)
m_pIPropNotifySink->OnChanged(PROPERTY_SOUND);

return;
}

Because the Sound property is marked requestedit, we must first call OnRequestEdit for permission to change. Because the request returns S_OK or S_FALSE, we have to compare the return value against NOERROR directly. If the request is denied, we stop processing at this point and do not change the property at all. Otherwise, we make the change and follow it up with a call to OnChanged.

For Beeper6, this is all there is to it. This sample, of course, is simple—more complex components that support IPropertyNotifySink will have many more places where they must make requests and send change notifications. The process of sending the request before changing a property and then sending a notification after changing it, however, is always the same. Remember also that the object can send the request and notification with DISPID_UNKNOWN to specify multiple objects.

On the client side, AutoCli2 provides a sink object with the necessary interface. Its two specific member functions are implemented in AUTOCLI2.CPP as follows, using the class CPropertyNotifySink:


STDMETHODIMP CPropertyNotifySink::OnChanged(DISPID dispID)
{
TCHAR szTemp[200];

wsprintf(szTemp
, TEXT("OnChanged notification received for DISPID=%lu")
, dispID);

m_pApp->Message(szTemp);
return NOERROR;
}

STDMETHODIMP CPropertyNotifySink::OnRequestEdit(DISPID dispID)
{
TCHAR szTemp[200];

wsprintf(szTemp
, TEXT("OnRequestEdit received for DISPID=%lu"), dispID);
m_pApp->Message(szTemp);

return ResultFromScode(m_pApp->m_fReadOnly ? S_FALSE : S_OK);
}

AutoCli2 displays a message in its client area within OnChanged to show that it received the notification. When you change the Beeper object's Sound property from AutoCli2's menu, you'll see change notifications come through. The return value for OnRequestEdit is controlled through another menu item, named Enforce Read-Only, which toggles the value of the m_fReadOnly flag in the code above. If you activate this option, OnRequestEdit will deny changes, so setting a new sound value in the Beeper object will not actually change anything, and no OnChanged notifications will occur.