Creating a Simple Control

To create a simple bound control, you simply add a few attributes to the control’s Object Description Language (ODL) file. The .odl file is compiled into the control’s type library and is read by the control’s container. Adding the bindable attribute to a property declaration tells containers that the property can be bound to a data field. Only one bindable property can have the defaultbind attribute, which tells containers that the property may be bound by default if the user specifies no other bindable property for binding. Adding the displaybind attribute to a bindable property tells containers that the property can be displayed in a list that allows a user to select properties for binding at design time. Once the control is loaded, the container exposes additional properties to allow its user to set the source and column from which data will be exchanged.

For example, when the data-binding attributes are set in a control’s type library, Microsoft® Visual Basic® 5.0 exposes the DataSource and DataField extender properties on behalf of the control for the property marked defaultbind. The default bound property is used by the container to exchange data with the data sources. Some containers may provide additional design time UI. This allows users to add bindings for properties with the bindable and displaybind attributes. Visual Basic provides this UI through the DATABINDINGS extender property.

The container transfers the property value to and from the control through standard Automation procedures. The control notifies the container of a change in the property, or requests permission to update the property using IPropertyNotifySink. This notifies the container whether the control is “dirty” and whether it has to retrieve the bound property’s value.

When the control’s bound property is going to change, it can query the container to determine whether the data can be updated by calling IPropertyNotifySink::OnRequestEdit. If it is granted permission by the container, the control signals its acceptance of the new value by calling IPropertyNotifySink::OnChanged. This type of updating is called pessimistic updating.

Note   Although this chapter addresses control creation using the ActiveX Controls Framework, you can modify any control that you create with little effort for use with any control framework.

Adding Attributes to the .odl File

The first step in creating a simple bound OLE control is to add data-binding attribute flags to desired properties in the control’s .odl file. The following table lists the possible .odl data binding attribute flags.

Attribute flag Flag description
Bindable Set on properties supporting the IPropertyNotifySink::OnChanged notification. You must set this when using simple or complex data binding.
Requestedit Set on properties supporting the IPropertyNotifySink::OnRequestEdit notification.
Displaybind Set on those properties that the control recommends should be displayed to the user as bindable at design time. This is a user interface hint to the container. It must be used with the bindable attribute flag. If bindable is used without displaybind, properties will be accessible at run time and will not appear in the IDE.
Defaultbind Indicates the single bindable property that best represents the control as a whole. The default bind property will be accessible at design time. This attribute must be used with the bindable attribute. Use of the displaybind attribute is optional if there is only one bindable property, which is the default bind property.

Your control's .odl file should include code similar to this:

[id(DISPID_TEXT), propget, bindable, requestedit, displaybind, defaultbind] HRESULT get_Text([out, retval] BSTR *pbstrText);
[id(DISPID_TEXT), propput, bindable, requestedit, displaybind, defaultbind] HRESULT put_Text([in] BSTR bstrText);

In Visual Basic 5.0, setting the bindable and defaultbind attributes is enough for Visual Basic to display the DataSource and DataField extender properties. Visual Basic 5.0 also recognizes displaybind to determine whether the control would like to have this property appear in the DataBindings dialog. If Visual Basic 5.0 finds properties marked as bindable and displaybind, it adds the DataBindings extender collection. This allows users to describe property-to-data field associations at design time. If properties are marked bindable without the displaybind attribute, the collection does not appear in the Visual Basic 5.0 property browser but is accessible at run time.

Note   Our sample control’s Text property contains the displaybind attribute. This directs Visual Basic to add the DataBindings collection and allows users to add bindings through the DataBindings dialog box. You may choose to remove this attribute to eliminate the DataBindings collection if you have only one bindable property.

Notifying the Container

The IPropertyNotifySink interface is a property notification interface used by containers that want to be notified of changes made to a property value, or sometimes disallow changes made to a property value. With the bindable and requestedit attribute flags set, the control has the responsibility to call the appropriate notifications of the IPropertyNotifySink interface. To query the container as to whether it allows changes on a specific property, the control calls IPropertyNotifySink::OnRequestEdit. If the property change is allowed, the control changes the property value and then calls IPropertyNotifySink::OnChanged. Without the requestedit attribute set, there is no need to call the IPropertyNotifySink::OnRequestEdit method; however, you must call IPropertyNotifySink::OnChanged if the bindable attribute is set. This ensures that the container detects changes in the bound property and performs data updates accordingly. The following is a description of the IPropertyNotifySink interface and its methods.

IPropertyNotifySink Interface

By specifying the attributes in the .odl file and implementing the correct interfaces (such as IConnectionPoint and IConnectionPointContainer), the control can call the OnRequestEdit and OnChanged notifications. Refer to the COM Programmer’s Reference for more information about connectable objects.

Here is a description of the IPropertyNotifySink interface and its member methods:

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

HRESULT OnChanged(DISPID dispid) 
The OnChanged method is called by the bound control to indicate that the given property has changed its value. It is the responsibility of the bound control to cause an OnChanged notification any time the value of the property changes, regardless of why it changed.

Parameter Description
dispid Indicates the property that has changed. DISPID_UNKNOWN may be passed to represent that an unspecified set of properties has changed (the sink must assume that all have changed). For efficiency, it is recommended that controls generate DISPID_UNKNOWN only when a significant number of properties change simultaneously. Note: Visual Basic 5.0 must have a valid DISPID and doesn't assume all properties have changed when DISPID_UNKNOWN is passed in.

HRESULT OnRequestEdit(DISPID dispid)
OnRequestEdit is called by the bound control to indicate that the given property is about to change value. OnRequestEdit notifications must occur before the property actually changes its value because the return value indicates whether the bound control should actually let the property change its value. This may be used by containers to implement read-only links, but it is more commonly used for pessimistic locking on records in a database.

Parameter/
HResult

Description
dispid Indicates the property that is about to change. DISPID_UNKNOWN may be passed to represent that an unspecified set of properties will change. For efficiency, it is recommended that controls generate DISPID_UNKNOWN only when a significant number of properties change simultaneously.
S_OK Indicates that the property may change the value (for example, the record was successfully locked). Other values indicate that the property change may not occur.

Assuming that you have an interface pointer to the IPropertyNotifySink interface, the C++ code is as follows:

if (pPropNotifySink->OnRequestEdit(dispid) != S_OK)
   // Do some action to indicate that the property cannot change.
else
   {

   // This is where you put code that changes the property.

   // Call OnChanged to notify the container that the property has changed.
   pPropNotifySink->OnChanged(dispid);
   }   

For controls written using the ActiveX Controls Framework, the previous code would change to look as follows:

if (RequestPropertyEdit(dispid))
   // Do some action to indicate that the property cannot change.
else
   {

   // This is where you put code that changes the property.

   //
   // Call PropertyChanged to notify the container that the property has changed.
   PropertyChanged(dispid);
   }

RequestPropertyEdit and PropertyChanged respectively broadcast OnRequestEdit and OnChanged properly to all hosts that have an active IPropertyNotifySink connection to the control.