ActiveX controls

Data Binding

You can add data-binding capabilities to an ActiveX control with little more than a few mouse clicks. As is not the case for intrinsic controls, you can create controls that bind multiple properties to database fields. All you have to do is tick the Property Is Data Bound check box in the Data Binding section of the Procedure Attributes dialog box, shown in Figure 17-10, for all the properties that you want to make data aware.

You can create as many data-bound properties as you like, but you must select the This Property Binds To DataField option for one of them only. If no property is bound to the DataField property, the Extender object won't expose all the Dataxxxx properties that are necessary to actually bind the control. Because such properties are exposed by the Extender object, their availability depends on the host environment.


Figure 17-10.
The Procedure Attributes dialog box includes all the options for creating data-aware properties.

PropertyChanged

and CanPropertyChange methods

To support data binding in code, you don't have to do anything more than you already do for persistent properties. In each Property Let procedure, you must call the PropertyChanged method, which informs Visual Basic that the property has changed and that the database field should be updated before the record pointer moves to another record. If you omit this call, the database field won't be updated. You can also update the field immediately if you select the Update Immediate option in the Procedure Attributes dialog box.

Visual Basic also provides the CanPropertyChange method, which queries the data source to determine whether it's safe to update the field. You could use the following code in the Property Let procedure of a property called CustomerName. (The statements that have been added to the code by the wizard are in boldface.)

Public Property Let CustomerName(New_CustomerName As String)
    If CanPropertyChange("CustomerName") Then    
        txtCustomerName.Text = New_CustomerName
        PropertyChanged "CustomerName"
    End If
End Sub

You should be aware, however, that you don't strictly need to call the CanPropertyChange method because under Visual Basic 5 and 6 it always returns True, even if the database field can't be updated. You should use this function only for compatibility with future versions of the language that might implement it. For all the properties that call this method before doing the update, you should also select the Property Will Call CanPropertyChange Before Changing option in the Procedure Attributes dialog box. Again, at this time there's no point in doing that, but it doesn't cause any harm either. The choice is yours.

To correctly support data binding, the constituent controls must update the corresponding bound property when their contents change. Typically this is done in the Change or Click event procedure, as in the following code snippet:

Private Sub txtCustomerName_Change()
    PropertyChanged "CustomerName"
End Sub

The DataBindings collection

As I mentioned before, only one property can be bound to the DataField Extender property. Because you can bind multiple properties, you need to provide developers with a method for associating each bound property to the corresponding database field. This association can be done either at design time or during execution.

For each property that you want to make bindable at design time, you must select the Show In DataBindings Collection At Design Time option in the Procedure Attributes dialog box. If this option is selected for one or more properties, the DataBindings item appears in the Properties window. When you click on it, Visual Basic brings up the dialog box shown in Figure 17-11. Note that it's OK that the property bound to the DataField property also appears in the DataBindingscollection.

Visual Basic 6 permits you to bind properties in the DataBindingscollection to fields in different Data Sources, and you can also select a distinct DataFormat for each one of them. In Visual Basic 5, you could bind properties only to the same Data Source.


Figure 17-11.
The DataBindings dialog box lets developers associate properties with database fields at design time.

All the bound properties appear in the DataBindingscollection at run time, regardless of whether they appear in the collection at design time. You can't add new items to this collection through code, but you can change the database field to which a property is bound:

' Bind the CustomerName property to the CompanyName database field.
Customer1.DataBindings("CustomerName").DataField = "CompanyName"

Another common task for the DataBindings collection is to cancel changes in fields so that the database record won't be updated:

Dim dtb As DataBinding
For Each dtb In Customer1.DataBindings
    dtb.DataChanged = False
Next

For more information about the DataBindingscollection, see the online Visual Basic documentation.

The DataRepeater control

Visual Basic 6 lets you create custom grid-like controls, using the DataRepeater control (contained in the Msdatrep.ocx file). This control works as a container of other ActiveX controls: It can host any type of controls, but it's especially useful with custom ActiveX controls.

Say that you want to display a table of records, but you don't want to use a standard Visual Basic grid control-such as the DataGrid or Hierarchical FlexGrid control-because you need maximum flexibility for interaction with the user or because you want to display information that can't be embedded in a regular grid (images, for example). Figure 17-12 shows a custom grid built on the DataRepeater control that displays the Publisher table from the Biblio.mdb database. To create such a custom grid, you must execute these basic steps:

  1. Create an AddressOCX control that contains all the fields you need; this is the object that will be replicated in the DataRepeater control.

  2. For all the properties that you want to expose in the DataRepeater control-that is, Name, Street, City, Zip, and State-make the property data bound and have it appear in the DataBindings collection at design time.

  3. Save the project, compile it into a stand-alone OCX file, and load the client application where you want to display the custom grid.

  4. Drop an ADO Data control on the client form, and then set its ConnectionString and RecordSource properties to point to the table in the database that provides the data. (You can also use any other ADO data source, including a DataEnvironment object.)

  5. Drop a DataRepeater control on the form, have its DataSource property pointing to the ADO Data control, and select the AddressOCX ActiveX control from the list that appears when you click on the RepeatedControlName. (This list includes all the OCXs that are registered in your system.)

  6. Bring up the DataRepeater control's custom property page, switch to the RepeaterBindings tab, and associate the bound properties exposed by the inner ActiveX control with the database fields. You can also set in the Format tab the DataFormat property for each field.


Figure 17-12.
The DataRepeater control lets you create custom views of your database tables.

The complete source code of the demonstration program is on the companion CD.

The DataRepeater control has some rough edges, and you must pay attention to many details to have it working properly:

  • The UserControl must be compiled into an OCX file; otherwise, it can't be hosted in the DataRepeater control. You can't use an intrinsic Visual Basic control with a DataRepeater.

  • All the bound properties in the inner ActiveX control should return String values; you can then format these values using the DataFormat options offered by the DataRepeater control. Moreover, all the properties must be visible in the DataBindings collection at design time; otherwise, the DataRepeater control won't see them.

  • The constituent controls on the child form should call the PropertyChanged method whenever the user changes their values; otherwise, the database won't be updated correctly.

  • The DataRepeater control creates only one instance of the control; this control is used to let the user edit values for the current record, whereas all other rows are just images of the control. You might notice some incorrect repaints every now and then.

The DataRepeater control exposes several properties, methods, and events that augment its potential and flexibility. For example, you can directly access the active instance of the child control to set additional properties (RepeatedControl property), find the line number of the current record (ActiveRow property), change the DataRepeater's appearance (by assigning the Caption, CaptionStyle, ScrollBars, RowIndicator, and RowDividerStyle properties), get or set a bookmark to the current or the visible records (using the CurrentRecord and VisibleRecords properties), and so on. You can also monitor users' actions-for example, when they scroll the contents of the list (ActiveRowChanged and VisibleRecordsChanged events) or select another row (CurrentRecordChanged event).

Interestingly, it's even possible to load a different child ActiveX control at run time by assigning a new value to the RepeatedControlName property. In this case, you must associate the bound property with fields by using the properties of the RepeaterBindings collection. (You can provide the user with a list of bindable properties using the PropertyNames property.) Whenever a new child control is loaded at run time, the DataRepeater fires a RepeatedControlLoaded event, which the programmer can use to correctly initialize the new control.

What's missing

The data binding mechanism offered by Visual Basic is fairly complete, although a few features aren't directly supported and you have to implement them yourself.

For example, there's no direct support for controls that bind a list of values to a secondary Data source, as the DataList and DataCombo controls do. You can implement this feature by exposing a custom property-such as RowSource-to which developers can assign the secondary Data control (or another ADO-compliant data source). Here the problem to solve is: You can't display a custom list in the Properties window, so how do you let the developer select the data source at design time? The answer is based on custom property pages, which are described in the next section.

One thing that at first seems to be impossible is to decide at run time which property binds to the DataField Extender property. In this situation, the solution is actually simpler than it might appear: Create an additional property that binds to DataField and that delegates to one of the other properties exposed by the control. This mechanism can be made extremely flexible by means of the new CallByName function. For example, let's say that you want to give developers the ability to bind any property among those exposed by the Customer control. You need to create two additional properties: BoundPropertyName, which holds the name of the bound property, and BoundValue, which does the actual delegation. This is the code in the Property Get and Let procedures for the latter property:

' BoundValue binds directly to DataField, but the value actually stored
' in the database depends on the BoundPropertyName property.
Public Property Get BoundValue() As Variant
    BoundValue = CallByName(Me, BoundPropertyName, vbGet)
End Property

Public Property Let BoundValue (New_BoundValue As Variant)
    CallByName Me, BoundPropertyName, vbLet, New_BoundValue
End Property

You should make BoundValue hidden so that developers are discouraged from using it directly.

Property Pages

The majority of ActiveX controls that you find in the Visual Basic package or buy from third-party vendors are equipped with one or more custom property pages. In this section, you'll see how easy it is to create property pages for your own ActiveX controls.

Even if the Visual Basic's Properties window is usually sufficient to enter property values at design time, there are at least three reasons why you should create custom property pages. First, they greatly simplify the job of the programmers that are using your control because all properties can be grouped in a logical way. Second, and more important, property pages give you much greater influence over how properties are set at design time. For example, you can't show a combo box in the Properties window with a list of values built dynamically, nor can you let developers drop down a mini-editor to enter multiple values (as they do when editing the List property of ListBox and ComboBox controls). These restrictions are easily overcome with property pages. Third, property pages permit you to localize the design-time user interface of your controls for different languages.

So that you can see property pages in action, I created a SuperListBox ActiveX control, an expanded ListBox that exposes an AllItems property (which returns all the items separated by a carriage return character) and allows you to enter new items at run time using a pop-up menu. My control also gives the programmer the ability to bind either the Text property or the ListIndex property to the DataField, thus overcoming one of the few limitations of the data binding mechanism in Visual Basic. This control employs a number of interesting programming techniques-such as API functions to implement a columnar format-and you might want to browse its source code on the companion CD.

Running the Property Page Wizard

You can add a property page to an ActiveX Control project with the Add Property Page command from the Project menu, but you can save a lot of work and time using the Property Page Wizard. (You have to install this add-in from the Add-In Manager dialog box.) In the first step of the wizard, you can create custom property pages, select their order, and decide whether you want to keep standard property pages. (See Figure 17-13.) Visual Basic automatically adds the StandardColor, StandardFont, and StandardPicture pages (for properties that return OLE_COLOR, StdFont, and StdPicture values, respectively), but you can also decide to deactivate them if you want.


Figure 17-13. The first step of the Property Page Wizard is the point at which you create new pages and change the order of selected pages.

In the second step of the wizard, you decide on which page each custom property will be displayed. All the properties that you leave in the leftmost list box (as shown in Figure 17-14) won't be displayed on any property page.


Figure 17-14.
In the second step of the Property Page Wizard, you decide which properties will be shown on which page.

When you click on the Finish button, the wizard creates one or more PropertyPage modules. For each property that you assigned to the page, the wizard generates a Label control (whose Caption is the name of the property) and a TextBox control that holds the value of the property, or a CheckBox control if the property returns a Boolean value. If you want a fancier user interface—for example, ComboBox controls for enumerated properties—you have to modify what the wizard has produced. Figure 17-15 shows the General property page for the SuperListBox control after I rearranged the controls and converted a couple of TextBox controls into ComboBox controls.


Figure 17-15. The property page generated by the Property Page Wizard, after some retouching.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Programs must be written for people to read, and only incidentally for machines to execute.”