Data Binding Objects (VB6) |
Microsoft Data Binding Collection (../SYSTEM32/MSBIND.DLL)
Apparently, when Microsoft was planning the new release of Visual Basic, they researched how professional developers were using the language. One result which seems to have taken the VB development team by surprise was that very few professional developers use the Data control and data bound controls. The reason for this is quite easy to understand: rightly or wrongly, professional VB developers see the Data control and data bound controls as inflexible and an encroachment on their control over the database. Furthermore, as more and more VB applications follow the n-tier paradigm, in which database access is performed on a remote server, with only properties passed to (or requested by) the client, the usefulness of a Data control was diminishing rapidly.
With this in mind, Microsoft introduced a new object model to give developers control over data mapping without sacrificing the rapid development time offered by more or less central data binding. The binding objects sit between standard form controls and your recordset (which can be wrapped within a class in an ActiveX server), automatically updating the form control as the user navigates through the recordset. Therefore, any form control can now be bound to a database field.
The Binding object model, which is shown in Figure 7.2, consists of a top-level collection to which you add Binding objects, these being the physical binding of data column to form control. The Binding object offers a flexibility that should satisfy most needs. For example, you can bind data to any property on a control, and you can specify at what point the data binding should be updated. A major enhancement over previous data binding technologies is the ability to bind the controls on a form to a VB class object. This can be achieved in both directions; that is to say, a VB class module can now be a data source, or it can be a data consumer.
To introduce the objects involved in both data binding and data formatting, this example demonstrates how the objects can bind data from an ActiveX OLE server to standard VB form controls without using a Data control. This example uses the sample SQL Server pubs database, but it can be easily modified to use an Access database or even the computer's own file system as a source of data. What's important is the relationship between the different objects in the Data Binding and Data Formatting object model.
In this example, an ActiveX DLL class object performs the database access and exposes itself as a data source. The form creates a BindingCollection object that binds various controls on the form to database fields in two different recordsets held in the class. The example shows how to:
Expose a VB class as a data source.
Create a BindingCollection object.
Specify the VB class as the data source for the BindingCollection object.
Add individual Binding objects to the collection, thereby binding controls to database fields held within the data provider class object.
Create DataFormat objects to perform formatting functions on the incoming and outgoing data.
Navigate through the recordset.
Perform simple validation and confirmation before updating the database.
The form includes the following object references, which are selected from the References dialog:
Microsoft Data Binding Collection
Microsoft Data Formatting Object Library
In addition, if the data source class isn't in the same project as the data consumer form, a reference to the data source class needs to be added.
The form contains the following controls:
Control Name | Control Type |
---|---|
chkContract | Checkbox |
cmdMoveNext | Command button |
cmdMovePrevious | Command button |
txtAddress | Text box |
txtContract | Text box |
txtFirstName | Text box |
txtlastName | Text box |
txtPubDate1 | Text box |
txtTitle1 | Text box |
Option Explicit 'declare object variables for the BindingCollection 'objects we'll need and a single Binding object. Private obcAuthors As BindingCollection Private obcTitles As BindingCollection Private oBind As Binding 'declare an object variable referencing our 'BindingSource class Private oSource As BindingSource 'declare object variables for the DataFormat objects Private WithEvents fmtF1 As StdDataFormat Private WithEvents fmtF2 As StdDataFormat Private WithEvents fmtF3 As StdDataFormat Private WithEvents fmtF4 As StdDataFormat Private Sub Form_Load() 'create new instances of the required objects 'our source class Set oSource = New BindingSource 'and the two BindingCollections Set obcAuthors = New BindingCollection Set obcTitles = New BindingCollection 'Set up the required Format Objects 'first to show a short message and automatically 'handle NULL database values Set fmtF1 = New StdDataFormat fmtF1.Type = fmtCustom fmtF1.NullValue = "No Data Found" 'second to handle 0 and 1 values in the database 'displaying a string in the text box instead Set fmtF2 = New StdDataFormat fmtF2.Type = fmtBoolean fmtF2.FalseValue = "No Contract" fmtF2.TrueValue = "Contract Signed" 'third to switch a checkbox on or off automatically 'depending on the database value of 0 or 1 Set fmtF3 = New StdDataFormat fmtF3.Type = fmtCheckbox 'last one to format a date field Set fmtF4 = New StdDataFormat fmtF4.Type = fmtGeneral fmtF4.Format = "long date" 'instruct the BindingCollection not to perform 'an update until the user moves to the next record obcAuthors.UpdateMode = vbUpdateWhenRowChanges 'set the DataMember to the required value - this 'will be passed to the class to obtain a reference 'to the correct recordset obcAuthors.DataMember = "Authors" 'now assign our source class as the datasource for 'the authors bindings collection Set obcAuthors.DataSource = oSource 'use the add method to create the binding between 'form controls and database fields obcAuthors.Add txtFirstName, "Text", "au_fname", _ fmtF1, "fname" obcAuthors.Add txtLastName, "Text", "au_lname", _ fmtF1, "lname" obcAuthors.Add txtContract, "Text", "contract", _ fmtF2, "contract" obcAuthors.Add chkContract, "Value", "contract", _ fmtF3, "chkCont" obcAuthors.Add txtAddress, "Text", "address", _ fmtF1, "address" 'now do the same for the Titles recordset obcTitles.UpdateMode = vbUpdateWhenRowChanges obcTitles.DataMember = "Titles" Set obcTitles.DataSource = oSource obcTitles.Add txtTitle1, "Text", "title", _ fmtF1, "title" obcTitles.Add txtPubDate1, "Text", "pubdate", _ fmtF4, "pubdate" End Sub Private Sub fmtF1_Format(ByVal DataValue As _ StdFormat.StdDataValue) 'the format event is called when a custom type 'is about to be formatted. Just for fun let's set 'the firstname field to uppercase and the 'lastname field to lowercase If DataValue.TargetObject.Name = "txtFirstName" Then fmtF1.Format = ">" Else fmtF1.Format = "<" End If End Sub Private Sub fmtF1_UnFormat(ByVal DataValue As _ StdFormat.StdDataValue) 'the unformat event is only called for custom type 'formats that are just about to be written back to 'the database - so lets convert them back to proper 'case DataValue.Value = StrConv(DataValue.Value, _ vbProperCase) End Sub Private Sub cmdMoveNext_Click() 'the user has finished with the record and wants 'the next one. 'just check this out first.. Call CheckForUpdate 'ok now we'll give them the next record by calling 'the MoveNext method in our source class. oSource.MoveNext 'we need to rebind the titles recordset because 'it's dynamically built Set obcTitles.DataSource = oSource End Sub Private Sub cmdMovePrev_Click() Call CheckForUpdate 'call the MoveBack method in our source class oSource.MoveBack End Sub Private Function CheckForUpdate() As Boolean Dim iResponse As Integer 'before we give them the next record - let's just 'check whether they made any amendments to the 'current record. 'iterate through the Binding objects For Each oBind In obcAuthors 'see if any values have been changed by the user If oBind.DataChanged Then 'indeed they did--was it intentional though? iResponse = MsgBox("The data has changed" _ & vbCrLf & _ "do you wish to update?", _ vbYesNo + vbQuestion) If iResponse = vbNo Then 'obviously not - so cancel the update from ' being written back to the database oBind.DataChanged = False End If 'no point in looking any further... Exit For End If Next End Function
This form is set as the project's startup object.
In the Form_Load event, a reference to the data source class is assigned to the BindingCollection.DataSource property, firing the data source class's GetDataMember event. The GetDataMember event handler assigns a reference to the recordset specified in the BindingCollection.DataMember property to the BindingCollection.
The Binding object's DataChanged property gives you control first, to interrogate the binding and determine if the value has been changed by the user, and second, to prevent the update from being written back to the database.
See the Data Format Objects entry for more information about the stdDataFormat object.
The class references the Microsoft ActiveX Data Objects 2.0 Library, selected from the References dialog. The class also has its DataSourceBehavior property set to vbDataSource.
Option Explicit 'declare the ADO objects Private cn As ADODB.Connection Private WithEvents rsAuthors As ADODB.Recordset Private WithEvents rsTitles As ADODB.Recordset Private Sub Class_GetDataMember(DataMember As String, _ Data As Object) 'this event is called as the datasource is assigned 'to the BindingCollection object. If DataMember = "Authors" Then 'this class provides two data members 'the first is authors, the other is titles 'assign the required recordset back to the 'BindingCollection object Set Data = rsAuthors Else Set Data = rsTitles End If End Sub Private Sub Class_Initialize() Dim sSQL 'create an instance of the ADO Recordset to use 'for the Titles recordset later Set rsTitles = New ADODB.Recordset 'create the connection object Set cn = New ADODB.Connection 'there is a DNS called Test on this machine 'pointing to the Pubs database cn.ConnectionString = "Test" cn.Open 'peform the query to return the data from Authors sSQL = "SELECT * FROM authors" Set rsAuthors = New ADODB.Recordset rsAuthors.Open sSQL, cn, adOpenKeyset, adLockOptimistic 'force the Titles recordset to be created rsAuthors.MoveFirst End Sub Public Sub MoveNext() 'move to the next record rsAuthors.MoveNext If rsAuthors.EOF Then rsAuthors.MoveFirst End If End Sub Public Sub MoveBack() 'move to the previous record rsAuthors.MovePrevious If rsAuthors.BOF Then rsAuthors.MoveLast End If End Sub Private Sub rsAuthors_MoveComplete(ByVal adReason As _ ADODB.EventReasonEnum, _ ByVal pError As ADODB.Error, _ adStatus As ADODB.EventStatusEnum, _ ByVal pRecordset As ADODB.Recordset) 'Move_Complete is an event from the ADO Recordset 'This code allows us to keep the two recordsets 'in synch. Dim sSQL As String sSQL = "SELECT titles.title, titles.pubdate" & vbCrLf _ & " FROM titles, titleauthor" & vbCrLf _ & " WHERE titleauthor.au_id = '" _ & rsAuthors("au_id") & "'" & vbCrLf _ & " AND titles.title_id = titleauthor.title_id" If rsTitles.State = adStateOpen Then rsTitles.Close End If 'you'll need to rebind this recordset - see code in form rsTitles.Open sSQL, cn, adOpenKeyset, adLockOptimistic End Sub
The Class_GetDataMember event handler is automatically placed in the class for you when you set the class's DataSourceBehavior property to vbDataSource.
By declaring the ADO recordset object as WithEvents, you can access all the events in the recordset object (such as WillChangeField, which allows you to perform validation and cancellation prior to updating, if you wish).