Previous Page TOC Next Page



- 9 -
Using Advanced Data-Aware OLE Controls


Chapter 3, "Using Visual Basic's Data Control," introduced you to the Data control and to bound (data-aware) TextBox controls. Visual Basic 3.0's Standard Edition included bound CheckBox, Image, Label, PictureBox, and TextBox controls; the Professional Edition added VBXs for the MaskEdBox, and the 3D SSCheck and SSPanel controls. If your Visual Basic 3.0 application needed more sophisticated bound controls, you had to purchase VBXs from third-party control vendors. The Professional and Enterprise versions of Visual Basic 4.0 now include a complete set of advanced bound controls implemented as 16-bit and 32-bit OLE Controls. To compensate for the loss of revenue brought about by Microsoft supplying DBGrid, DBCombo, and DBList controls, third-party OCX suppliers offer a variety of enhanced data-aware OLE Controls.

A controversy exists among professional database developers and administrators regarding the use of bound controls for updating tables in production multiuser applications and especially in client/server front-ends. The primary issue relates to users having a "live" connection to multiple database tables that, under some conditions, might jeopardize referential integrity or, to a lesser extent, data consistency. Jet 3.0's intrinsic transaction processing of action queries, combined with automatic enforcement of referential integrity by Jet 2.x and 3.0 databases and client/server RDBMSs, lessen the risks previously associated with Visual Basic's (and Access's) bound controls. The primary advantage of advanced data-aware controls is rapid application development (RAD). Thus, many developers use bound controls to develop application prototypes, then convert the prototypes to production applications that substitute unbound for bound controls. Passing values from unbound controls through VBA code to Edit. . .Update or AddNew. . .Update operations, or to executable SQL statements lets you "wrap" update operations within explicit transactions.

Regardless of your stand (or your clients' or employer's position) on the bound-versus-unbound issue, every Visual Basic developer should understand the workings of data-bound controls. Data-bound controls let you quickly create prototypes of database front-ends to which you later can add VBA code to upgrade the front-ends to production specifications. This chapter covers use of the basic DBGrid, DBCombo, and DBList controls plus specialized applications for the Image and PictureBox controls. Applications for the bound OLE container control are described in the "Using Visual Basic 4.0's OLE Container Control" section of Chapter 14, "Integrating Database Front-Ends with OLE 2.1." Use of the DBGrid and DBCombo controls with client/server databases is one of the subjects of Chapter 20, "Creating Front-Ends for Client/Server Databases," and Chapter 22, "Using the Remote Data Object."

Displaying and Updating Recordset Objects with the DBGrid Control


The graphic design of the DBGrid control (Dbgrid32.ocx and DBGRID16.OCX) is related closely to Access 95's datasheet view of a subform control. When porting an Access application to Visual Basic, the similarity in the appearance and behavior of the DBGrid control to Access 95's datasheet view is advantageous. Figure 9.1 shows a DBGrid control whose Data control is bound to the Customers table of Access 95's Northwind.mdb sample database. Figure 9.2 illustrates the datasheet view of an Access 95 subform, created by the Control Wizard, which also is bound to the Customers table of Northwind.mdb.

Figure 9.1. Emulating an Access 95 subform with a DBGrid control bound to a Data control.

Figure 9.2. The Access 95 subform that serves as a model for the Visual Basic form of Figure 9.1.



The preceding form and the following code examples are incorporated in the DBGrid1.vbp project in the \DDG_VB4\32_bit\Chaptr09 folder of the accompanying CD-ROM. An abbreviated version of Access 95's Northwind.mdb containing only the Customers table is included in this folder.

Adding the total number of records in the Data control's Recordset object to a label and displaying the record number corresponding to the current record requires adding the following variable declaration and simple event-handling procedures for the Data control:




Dim fLoaded As Boolean



Private Sub dtcCustomers_Reposition()



   If fLoaded Then



      dtcCustomers.Caption = dtcCustomers.Recordset.AbsolutePosition



   End If



End Sub



Private Sub Form_Activate()



   dtcCustomers.Recordset.MoveLast



   lblRecCount.Caption = "of " & dtcCustomers.Recordset.RecordCount



   dtcCustomers.Caption = 1



   dtcCustomers.Recordset.MoveFirst



   fLoaded = True



End Sub

To bind a DBGrid control to a Data control you set the value of the DataSource property to the Name property value of the Data control. Unfortunately, the DataSource property of all bound controls is read-only in run mode, so you must change the RecordSource property (and the DatabaseName property, if necessary) of the bound Data control if you want to use a single DBGrid control to display data from several sources. Like Access's datasheet view, an updatable Recordset object lets you add, edit, and delete records with the DBGrid control. However, you must explicitly enable updating of the table(s) that underlie the Recordset by setting the value of the AllowAddNew and AllowDelete properties of the DBGrid to True in design or run mode. (The default value of the AllowUpdate property is True.)



Visual Basic 4.0's DBGrid control does not support the built-in lookup feature of Access 95's datasheet view. As an example, the CustomerID and EmployeeID fields of the Orders table of Northwind.mdb (run with Access 95) look up the corresponding CompanyName and LastName plus FirstName field values of the Customers and Employees tables, respectively. When viewing a Jet 3.0 database table with the lookup feature enabled for one or more foreign key fields, the foreign key value (not the lookup value) appears in the DBGrid control's column(s).

The following sections describe how to use the bound DBGrid control in simple Visual Basic 4.0 database applications. You also can use the DBGrid as an unbound control, a subject that is beyond the scope of this chapter. (Search online help for "DBGrid, unbound" to display a list of the properties of the unbound DBGrid control in the Topics Found dialog.)

Formatting the DBGrid Control


The Properties window for the DBGrid control does not provide access to individual columns for formatting purposes. When you add a DBGrid control to a form, the default appearance of the control in design mode consists of two empty column headers and two empty rows (see Figure 9.3). To format the DBGrid control, right click the surface of the control to display the pop-up menu, then choose Retrieve Fields to populate the column headers in design mode. Figure 9.4 shows the headings retrieved for the first five columns of a DBGrid bound to a Data control with its RecordSource property set to the Titles table of the Biblio.mdb sample database.

Figure 9.3. Right-clicking the DBGrid control opens the pop-up menu that provides the Retrieve Fields choice.

Figure 9.4. Once retrieved, table field or query column names appear in the DBGrid's column header at run time.

To add a caption to the DBGrid control, change the column headings, or set the font attributes for the column headings and/or the remainder of the grid, then right-click the DBGrid control to open the pop-up menu and choose Properties. The Apex Data Bound Grid Control Properties sheet provides the following four pages to format the DBGrid control:

Figure 9.5. The General page of the DBGrid's properties sheet lets you add a caption bar and set selected properties of the control.

Figure 9.6. The Columns page provides design-time access to members of the DBGrid control's Columns collection.

Figure 9.7 shows the first four columns and first 12 rows of the Titles table of the Biblio.mdb database displayed by the dbgTitles control in run mode. The bold attribute is applied to the caption bar and the column headers font. The column width of the Title and Year Published columns is adjusted in run mode for improved readability, but column width values are not persistent. Once you've adjusted column widths for readability, you can determine the new Width property value in twips for each Column object using the Debug Window, as illustrated by Figure 9.8. Copy the entries in the Debug Window to the Form_Activate event handler to make the column width settings persistent.

Figure 9.7. The formatted DBGrid control in run mode.

Figure 9.8. Determining the adjusted width of DBGrid columns in the Debug Window.



When you paste the entries from the Debug Window to the Form_Activate event handler, ? becomes Print and the Width values appear in red below the Print statement. Only minor editing is needed to convert the Debug Window text to valid VBA code.

Following is an example of code to set the Width property of columns 0 through 2 and 4 through 7 for the dbgTitles control:




Private Sub Form_Activate()



   'Set width of columns for readability



   dbgTitles.Columns(0).Width = 3345



   dbgTitles.Columns(1).Width = 570



   dbgTitles.Columns(2).Width = 1170



   dbgTitles.Columns(4).Width = 1850



   dbgTitles.Columns(5).Width = 1500



   dbgTitles.Columns(6).Width = 3000



   dbgTitles.Columns(7).Width = 3000



End Sub

Special Properties, Methods, and Events of the DBGrid Control


The DBGrid control shares many properties, methods, and events with the unbound Grid control. The DBGrid control, however, offers a number of features that reduce the amount of VBA code necessary to alter the display of data or modify the displayed data. The following sections describe how to sort grid data by column values, hide individual columns, and validate edits to the underlying Recordset.

Using the HeadClick Event to Sort by Columns

Users of applications with DBGrid controls are likely to want to sort the underlying Recordset on fields other than the primary key field (if a primary key field exists). Visual Basic's online help suggests use of the HeadClick event to sort the rows of the database in accordance with the values in the clicked column. A quick and easy method of sorting the Recordset of the Data control would appear to be use of the Sort property of the Recordset object of the Data control. (The Sort property, which is valid only for Recordset objects of the Dynaset and Snapshot types, uses as its value the ORDER BY clause of an SQL statement, without the ORDER BY prefix.)

Unfortunately, changing the value of the Sort property doesn't work with bound controls; instead, you must change the value of the RecordSource property of the Data control and then apply the Refresh method. (If you apply the Refresh method to the Data control after setting the Sort property value, the Sort property value is reset to Null.) The following event-handling subprocedure sorts the records in dbgTitles when you click a column header:




Private Sub dbgTitles_HeadClick(ByVal intColIndex As Integer)



   'The following statement doesn't work



   'dtcTitles.Recordset.Sort = "[" & _



_  '   dbgTitles.Columns(intColIndex).DataField & "] ASC"



   'The following two statements do work



   dtcTitles.RecordSource = "SELECT * FROM Titles ORDER BY [" & _



      dbgTitles.Columns(intColIndex).DataField & "] ASC"



   dtcTitles.Refresh



End Sub

Figure 9.9 shows the result of clicking the column header of the Title field. If you click the column header of the Comments field, you receive a Can't sort on Memo or OLE object ([Comments]) error message. You can disable sorting on Memo and OLE Object fields by checking the field data type with the following code:




Private Sub dbgTitles_HeadClick(ByVal intColIndex As Integer)



   'Sort on fields other than OLE Object (11) or Memo (12) types



   If dtcTitles.Recordset.Fields(intColIndex).Type < 11 Then



      dtcTitles.RecordSource = "SELECT * FROM Titles ORDER BY [" & _



         dbgTitles.Columns(intColIndex).DataField & "] ASC"



      dtcTitles.Refresh



   Else



      'Add "nocando" message box, if desired



   End If



End Sub

Figure 9.9. The result of using the HeadClick event to sort the DBGrid's Recordset by clicking a column header.

Changing Columns Displayed in Run Mode

You can control the number of columns displayed in run mode by manipulating the Columns collection of the DBGrid control. The simplest method of hiding one or more columns is to set the value of the Visible property of the appropriate Column object to False. Hiding columns is useful in situations where the display area is limited and users must scan specific columns to find a particular record. The following two event-handling subprocedures hide selected columns and display all columns in response to clicking the Some Columns and All Columns command buttons (see Figure 9.10).




Private Sub cmdAllCols_Click()



   'Display all columns



   Dim intCol As Integer



   cmdSomeCols.Enabled = True



   For intCol = 1 To dbgTitles.Columns.Count - 1



      dbgTitles.Columns(intCol).Visible = True



   Next intCol



   cmdAllCols.Enabled = False



End Sub



Private Sub cmdSomeCols_Click()



   'Hide unneeded columns



   cmdSomeCols.Enabled = False



   dbgTitles.Columns(1).Visible = False



   dbgTitles.Columns(4).Visible = False



   dbgTitles.Columns(5).Visible = False



   dbgTitles.Columns(6).Visible = False



   dbgTitles.Columns(7).Visible = False



   cmdAllCols.Enabled = True



End Sub

Figure 9.10. Hiding columns of a DBGrid control.

An alternative to hiding and showing columns is to use the Remove and Add methods of the Columns collection. Removing and adding Column objects is a cumbersome and slower process that requires application of the Rebind and Refresh methods to ensure that the proper columns appear. When adding a Column object to the Columns collection, you must set the value of the DataField property to the name of the corresponding field of the underlying Recordset and set the Visible property to True. (The default value of the Visible property of an added column is False.)



Unfortunately, the DBGrid control does not support mixing bound and unbound columns. You can add an unbound Column to the Columns collection, but you cannot use the Value or Text property of the Column to display calculated values.


Using DBGrid Events to Validate Updates

The DBGrid control has a variety of events that are triggered when you edit the underlying Recordset object. Table 9.1 lists the events that are triggered when you update a column value or delete, insert, or update a row in a DBGrid control. You add validation code to the Before. . . events to ensure data consistency and to preserve referential integrity. (The "Validating Data with Visual Basic Code" section of Chapter 3 provides an example of typical validation code.) The Before. . . events include a Cancel argument that, by setting Cancel = True, lets you abort the action in case the entry fails the validation test.

Table 9.1. Before. . . and After. . . events of the DBGrid control.

Event When Triggered
BeforeColUpdate After altering the value of a cell and moving to a new cell, but before the content of the DBGrid buffer changes
AfterColUpdate After changing the content of the DBGrid buffer for the updated column
BeforeDelete After selecting a row and pressing the Delete key, but before the row is deleted from the Recordset
AfterDelete After deletion of the row from the Recordset
BeforeInsert After entering at least one character in a column of the tentative append record, but before the row is added to the Recordset
AfterInsert After the row is added to the Recordset
BeforeUpdate After changing the value in any column and moving to a new record, but before the Recordset is updated
AfterUpdate After updating the row of the Recordset

The code in Listing 9.1 shows examples that display message boxes to identify the point at which each event listed in Table 9.1 is triggered.

Listing 9.1. Code to create message boxes identifying the occurrence of Before. . . and After. . . events of the DBGrid control.




Private Sub dbgTitles_BeforeColUpdate(ByVal intColIndex As Integer, _



      varOldValue As Variant, intCancel As Integer)



   Dim strWarn As String



   strWarn = strMsg & "update the column value from '" & _



      varOldValue & "' to '" & _



      dbgTitles.Columns(intColIndex).Value &  "'?"



   If MsgBox(strWarn, vbYesNo + vbQuestion, "Confirm Update") = _



         vbNo Then



      intCancel = True



   End If



End Sub



Private Sub dbgTitles_AfterColUpdate(ByVal intColIndex As Integer)



   MsgBox "Column " & intColIndex + 1 & " updated.", _



      vbInformation, "Update Confirmation"



End Sub



Private Sub dbgTitles_BeforeDelete(intCancel As Integer)



   Dim strWarn As String



   strWarn = strMsg & "delete this row?"



   If MsgBox(strWarn, vbYesNo + vbQuestion, "Confirm Delete") = _



         vbNo Then



      intCancel = True



   End If



End Sub



Private Sub dbgTitles_AfterDelete()



   MsgBox "Row deleted.", vbInformation, "Delete Confirmation"



End Sub



Private Sub dbgTitles_BeforeInsert(intCancel As Integer)



   Dim strWarn As String



   strWarn = strMsg & "add this new row?"



   If MsgBox(strWarn, vbYesNo + vbQuestion, "Confirm Insert") = _



         vbNo Then



      intCancel = True



   End If



End Sub



Private Sub dbgTitles_AfterInsert()



   MsgBox "New row added.", vbInformation, "Insert Confirmation"



End Sub



Private Sub dbgTitles_BeforeUpdate(intCancel As Integer)



   Dim strWarn As String



   strWarn = strMsg & "update this row?"



   If MsgBox(strWarn, vbYesNo + vbQuestion, "Confirm Update") = _



         vbNo Then



      intCancel = True



   End If



End Sub



Private Sub dbgTitles_AfterUpdate()



   MsgBox "Row updated.", vbInformation, "Update Confirmation"



End Sub


The preceding code examples are incorporated in the DBGrid2.vbp project in the \DDG_VB4\32_bit\Chaptr09 folder of the accompanying CD-ROM. A Jet 3.0 version of Biblio.mdb also is included in this folder. Data type prefixes have been added to names of arguments of the Before. . . events in accordance with the variable naming conventions used in this book.


Picking Items with DBCombo and DBList Boxes


DBCombo controls, and to a lesser extent, DBList controls, are useful for creating picklists that select a particular set of records to populate a DBGrid control. The following three sections describe how to use a DBCombo to supply the criterion for a SQL WHERE clause that determines the content of a DBGrid control, a dual DBCombo approach to selecting a particular record for populating a set of bound text boxes, and using a DBCombo control to set the value of a foreign key field.

Using a DBCombo Selection to Populate a DBGrid Control


In the majority of cases, a DBCombo control's list is populated by a field of a base table. Unlike Access 95's ComboBox control, you can't specify more than one field to appear in a DBCombo list, nor can you set multiple field values as items in a DBList control. Therefore, you must take advantage of the special properties of the DBCombo or DBList control that give you access to a value that doesn't appear in the control's list. Table 9.2 lists the special properties you use for a picklist created from a DBCombo or DBList control.

Table 9.2. Properties of the DBCombo or DBList control used for picklists.

Property Purpose
RowSource The name of the Data control that provides the Recordset used to populate the control's list.
ListField The name of the field of the Recordset whose members appear in the selection list.
BoundColumn The name of the field of the Recordset that is used in a SQL WHERE clause to specify the row(s) returned to another Data control or to a dbName.OpenRecordset statement in VBA code.
BoundText The value of the BoundColumn returned when the user makes a selection.
Style The type of list (drop-down combo, simple combo, or drop-down list); the Style property applies only to combo boxes. The drop-down list is most commonly used for picklists.

The Publishers table of the Biblio database is typical of base tables that use a primary key field (PubID) to join related tables and a descriptive field (Name or Company Name) for visual identification of the record. The DBCombo box displays the descriptive field in its list element, but usually you must provide the primary key value for use in the SQL WHERE clause. To use a DBCombo control to select a set of records, follow these general steps:

  1. Add two Data controls to the form: The first to provide the RowSource value for the DBCombo control and the second to serve as the DataSource for the bound control(s) that display the data.

  2. Set the values of the DatabaseName property for both Data controls. (In most cases, all controls use the same database.)

  3. Set the RecordSource value of the first data control to an SQL statement of the general form: SELECT BoundFieldName, ListFieldName FROM Table1Name ORDER BY ListFieldName. Specifying only the fields you need improves performance. The ORDER BY clause causes the list items to appear in alphabetical order by name.

  4. In the Click event handler for the DBCombo control, set the value of the RecordSource property of the second Data control to an SQL statement of the general form: SELECT * FROM Table2Name WHERE ForeignKeyName = [']BoundFieldValue[']. BoundFieldValue is obtained from the BoundText property value of the DBCombo control.

Figure 9.11 shows an example of a simple form that displays in a DBGrid control records from the Titles table of the Biblio database corresponding to the publisher's name selected from the combo box list. In this case, ListFieldName is the Name field and BoundFieldName is the PubID field of the Publishers table.

Figure 9.11. Displaying in a DBGrid control titles of books published by the firm whose name is selected in the combo box.



You can use an unbound ComboBox control that you populate with code in the Form_Load or Form_Activate event handler. Examples of uses of unbound ComboBox controls for populating grids appear in Chapter 22.

Listing 9.2 shows the code required to initialize the DBCombo control and populate the DBGrid control based on the selection made in the DBCombo control. The intArea (Area) argument of the Click event for a DBCombo control isn't documented in the online help topic for the event. A value of dbcAreaList (2) corresponds to a click on the list element of the DBCombo control. See the help topic for "DBList and DBCombo Controls Constants" for other allowable values of the argument.

Listing 9.2. Code to use a DBCombo control to select records that populate a DBGrid control.




Option Explicit



Dim fWidthSet As Boolean



Private Sub dbcPublishers_Click(intArea As Integer)



   If intArea = dbcAreaList Then



      'Area in list clicked



      Dim strSQL As String



      dtcTitles.Caption = "Titles from " & dbcPublishers.Text



      strSQL = "SELECT * FROM Titles WHERE PubID = " _



         & dbcPublishers.BoundText



      dtcTitles.RecordSource = strSQL



      dtcTitles.Refresh



      dtcTitles.UpdateControls



      If Not fWidthSet Then



         'Set the column widths once



         Call SetColumnWidths



         fWidthSet = True



      End If



   End If



End Sub



Private Sub SetColumnWidths()



   'Set widths of columns for readability



   dbgTitles.Columns(0).Width = 3500



   dbgTitles.Columns(1).Width = 570



   dbgTitles.Columns(2).Width = 1170



   dbgTitles.Columns(4).Width = 1850



   dbgTitles.Columns(5).Width = 1500



   dbgTitles.Columns(6).Width = 3000



   dbgTitles.Columns(7).Width = 3000



End Sub



Private Sub Form_Activate()



   'Initialize the DBCombo control



   dbcPublishers.SetFocus



   SendKeys "{F4}{Down}{Enter}", True



   Call dbcPublishers_Click(2)



End Sub


The preceding code example is incorporated in the DBCombo1.vbp project in the \DDG_VB4\32_bit\Chaptr09 folder of the accompanying CD-ROM.


Cascading Two DBCombo Controls to Select a Specific Record


You can use two DBCombo controls in tandem to select a specific record for display and editing with bound text boxes. The form shown in Figure 9.12 is created with Visual Basic 4.0's Data Form Designer with two DBCombo controls added. The left DBCombo control selects the publisher; selecting a publisher populates the right DBCombo control with the titles from the publisher.

Figure 9.12. Using two DBCombo controls to select a single record for display by bound text boxes.

Listing 9.3 shows only the Click event-handling subprocedures for the two DBCombo controls that appear in Figure 9.12. Note that single quotes (') surround the BoundText String value to match entries in the ISBN primary key field of the Titles table. The single quotes are not required for the numeric (Long Integer) PubID field of the Publishers table.

Listing 9.3. Click event handlers for two DBCombo controls that select a single record for display and editing.




Private Sub dbcPublishers_Click(intArea As Integer)



   If intArea = dbcAreaList Then



      'Area in list clicked



      Dim strSQL As String



      strSQL = "SELECT Title, ISBN FROM Titles WHERE PubID = " & _



         dbcPublishers.BoundText



      dtcTitles.RecordSource = strSQL



      dtcTitles.Refresh



      dtcTitles.UpdateControls



      If dtcTitles.Recordset.RecordCount > 0 Then



         'Display the first selection



         dbcTitles.SetFocus



         SendKeys "{F4}{Down}{Enter}", True



         Call dbcTitles_Click(2)



      End If



   End If



End Sub



Private Sub dbcTitles_Click(intArea As Integer)



   'Area in list clicked



   If intArea = dbcAreaList Then



      Dim strSQL As String



      strSQL = "SELECT * FROM Titles WHERE ISBN = '" & _



         dbcTitles.BoundText & "'"



      dtcBook.RecordSource = strSQL



      dtcBook.Refresh



      dtcBook.UpdateControls



   End If



End Sub


The preceding code example is incorporated in the DBCombo2.vbp project in the \DDG_VB4\32_bit\Chaptr09 folder of the accompanying CD-ROM.


Using the DBCombo Box to Update a Second Recordset


Another common application for the DBCombo control is to supply a valid entry for a foreign key field during the editing process. Picking from a list of valid entries, rather than typing a value, ensures a match between primary and foreign key values. Matching foreign to primary key values prevents creation of "orphan" records and maintains referential integrity when you use a DBCombo control of the drop-down list style. (Specifying the drop-down list style is equivalent to setting to True the LimitToList property of Access 95's bound ComboBox control.)

The first three steps to add a DBCombo to update a field value are the same as for a DBCombo used as a picklist, but are repeated for completeness:

  1. Add a Data control and a DBCombo control to a form with an existing Data control and bound control(s) that display values that include at least one foreign key field. In the example of this section, the foreign key field of the Recordset underlying the existing Data and DBGrid controls is the PubID field of Biblio's Titles table.

  2. Set the RecordSource property of the new Data Control to the base table or a query that provides a set of values of the corresponding primary key field. In this case, the PubID field of the Publishers table is the primary key field.

  3. Set the new DBCombo's RowSource property value to the name of the new Data control, the Style property to 2 - Dropdown List, the ListField property to the name of a descriptive field (Name, in this case), and the BoundColumn property to the primary key field (PubID).

  4. Set the DataSource property value to the name of the existing Data control. This binds the output of the new DBCombo control to the Data control and provides the list of fields from which to choose in the DataField property's drop-down list.

  5. Set the DataField property to the name of the foreign key field (PubID). This binds the output of the new DBCombo control to the foreign key field.

  6. Set the Visible property of the new DBCombo control to False.

Figure 9.13 illustrates a sample form with a DBCombo control added by following the preceding steps, with the exception of step 6. The RecordSource property of the Title Data control (dtcTitlePub) is qryTitlePub, a query created in Access 95 with the following SQL property value:




SELECT DISTINCTROW Titles.ISBN, Titles.Title, Titles.PubID,



      Publishers.Name, Titles.[Year Published], Titles.Subject,



      Titles.Description, Titles.Notes, Titles.Comments



   FROM Publishers INNER JOIN Titles



      ON Publishers.PubID = Titles.PubID;

The preceding updatable query simply adds the Name field of the Publishers table for verification when the PubID value is changed. The DBCombo is populated by the qryPublishers query, the RecordSource property of the dtcPublishers control, that adds the sort order on the Name field of the Publishers table with the following Jet SQL statement:




SELECT DISTINCTROW Publishers.PubID, Publishers.Name



   FROM Publishers



   ORDER BY Publishers.Name;

Figure 9.13. Updating the publisher ID value from a selection in a DBCombo control.



The DBCombo3.vbp project that includes the form shown in Figure 9.13 is located in the \DDG_VB4\32_bit\Chaptr09 folder of the accompanying CD-ROM. The underlying queries are added to the Biblio95.mdb database that is located in the same folder.

As you select successive rows in the DBGrid control, the name of the publisher of the selected title appears in the text box element of the DBCombo control. If you select a different publisher name, the entry in the selected row doesn't change to correspond with the selection until you move the record pointer to another row. The lack of a visual clue to a change in the PubID and Name cells of the edited row is likely to confuse data entry operators. Add the following code to update the record immediately upon making a selection in the DBCombo control:




Private Sub dbcPublishers_Click(intArea As Integer)



   If intArea = dbcAreaList Then



      'The following code updates the record immediately



      If dtcTitlePub.Recordset.AbsolutePosition = _



            dtcTitlePub.Recordset.RecordCount - 1 Then



         dtcTitlePub.Recordset.MovePrevious



         dtcTitlePub.Recordset.MoveNext



      Else



         dtcTitlePub.Recordset.MoveNext



         dtcTitlePub.Recordset.MovePrevious



      End If



   End If



End Sub


A more logical method for forcing an immediate update is application of the UpdateRecord method to the Data control. Alternatively, you might consider applying the Edit and Update methods in succession to the Recordset of the Data control. Either of these two approaches results in an Object Doesn't Support this Property or Method message. Interestingly, the UpdateRecord method works as advertised in online help, but you can't eliminate the error message with an On Error Resume Next instruction. (The error message isn't trappable.)


Saving and Displaying Images with the PictureBox and Image Controls


Visual Basic 4.0 includes bound PictureBox and Image intrinsic controls that you can use as an alternative to the bound OLE container control, described in the "Using Visual Basic 4.0's OLE Container Control" section of Chapter 14. Although neither the PictureBox nor the Image control technically is an "advanced OLE control," the subject of this chapter, both controls store their data in fields of Jet's OLE Object field data type, called a LongBinary field by Visual Basic. The advantage of PictureBox and Image controls is that these controls consume fewer system resources than the OLE container control and its associated OLE full server, usually Windows 95 Paint for bitmap images. Both PictureBox and Image controls accommodate bitmap (.bmp, .dib, and .ico) and vector image (.wmf) files. (A .wmf file also can include a bitmapped image.)

The Image control is a "lightweight" object; it consumes fewer resources than a PictureBox control and paints faster, too. An Image control provides the ability to scale the image to fit the confines of the control by setting the value of the Stretch property to True; the PictureBox control does not offer a Stretch property. Thus, the sample video image database application discussed in the following two sections uses the Image control.

A Visual Basic Video Logging System


There are a variety of applications for image databases, ranging from simple photographic cataloging systems to full-fledged workflow applications. One of the more interesting applications for image controls is capturing and storing picons, which are still images representing the beginning (and often the ending) frame of a live video image. Very small picons often are used in video editing applications to identify video clips (an individual segment of recorded video content) on a timeline. Adobe Premiere, as an example, uses picons to identify clips added to either of the two video timelines of its Construction window. The VidLog16.vbp application, whose sole form appears in Figure 9.14, is a simple 16-bit Visual Basic 4.0 application that logs the beginning and ending times of Hi8 analog video clips to produce a batch (.pbl) file that automates Adobe Premiere 4.2's digital video capture process. VidLog is the subject of an article titled "Use Jet for Realtime Device Control" that appeared in the April 1996 issue of Visual Basic Programmer's Journal.

Figure 9.14. The VidLog form displaying a picon for a video clip.

VidLog is designed to control the operation of a Sony Hi8 videotape player called the Vdeck, which uses the Sony ViSCA (Video Systems Control Architecture) protocol. Sony discontinued production of the two primary ViSCA products, the CVD-1000 Vdeck and the VI-1000 Vbox last September, but tens of thousands of these devices remain in use today. (The XV-D1000 video mixer and special effects generator has an undocumented ViSCA interface.) ViSCA lets you connect to a COM port up to seven Vdecks and/or Vboxes in a daisy chain arrangement. The Vbox converts the ViSCA command set to Sony's consumer LANC (Local Application Numeric Code, also called Control-L) protocol that's standard on most Sony camcorders and high-end VCRs. The Vdeck and the Sony EV-S5000 and EV-S7000 Hi8 VCRs can read and record RC timecode, which is a necessity for accurate logging of videotapes. You need a Vbox to control Hi8 camcorders, the new DCR-VX1000 consumer Digital Video (DV) camcorder, or an EV-S5000/EV-S7000 VCR with ViSCA. Videotape timecode is based on the SMPTE (Society of Motion Picture and Television Engineers) HH:MM:SS:FF format, where FF represents video or movie frames.

Windows 95 includes a built-in ViSCA driver (MCIVISCA.DRV) for the Media Control Interface (MCI), so you can use the 16-bit mciSendString or 32-bit mciSendStringA functions to issue simple English-language commands to ViSCA devices, which MCI calls the vcr device type. A full description of the MCI command string syntax for the vcr device type is included in the "Media Control Interface" section of the Win32 SDK and Chapter 15, "MCI VCR Services," of the Video for Windows 1.1 SDK on the MSDN Development Library CD-ROM. Before you can use either function, you must declare two function prototypes (mciSendString and mciGetErrorString) contained in 32-bit WINMM.DLL or 16-bit MMSYSTEM.DLL; both of these libraries are installed during Windows 95 setup. Listing 9.4 shows the prototype declarations for the two functions needed for 32-bit and 16-bit applications that use MCI command strings.

Listing 9.4. Function prototype declarations required to use MCI command strings.




Option Explicit



#If Win32 Then



   'Windows 95 MCI functions



   Declare Function mciSendString Lib "winmm.dll" _



      Alias "mciSendStringA" _



     (ByVal lpstrCommand As String, _



      ByVal lpstrReturnString As String, _



      ByVal uReturnLength As Long, _



      ByVal hWndCallback As Long) As Long



   Declare Function mciGetErrorString Lib "winmm.dll" _



      Alias "mciGetErrorStringA" _



     (ByVal dwError As Long, _



      ByVal lpstrBuffer As String, _



      ByVal uLength As Long) As Long



#Else



   Declare Function mciSendString Lib "mmsystem" _



     (ByVal lpstrCommand As String, _



      ByVal lpstrReturnString As String, _



      ByVal uReturnLength As Integer, _



      ByVal hWndCallback As Integer) As Long



   Declare Function mciGetErrorString Lib "mmsystem" _



     (ByVal wError As Long, _



      ByVal lpstrBuffer As String, _



      ByVal uLength As Integer) As Integer



#End If



Public mciCommand   As String       'Command



Public mciReturn    As String * 256 'Return string



Public mciErrString As String * 256 'Error string



Public mciError     As Long         'Error number



#If Win32 Then



   Public mciReturnLen As Long  'Length of return string



   Public mcihWnd      As Long  'Callback window handle



   Public mciErrLen    As Long  'Length of error string



   Public mciErrResult As Long  'Return value of error



#Else



   Public mciReturnLen As Integer



   Public mcihWnd      As Integer



   Public mciErrLen    As Integer



   Public mciErrResult As Integer



#End If


The VidLog16.vbp project is located in the \DDG_VB4\16_bit\Chaptr09 folder of the accompanying CD-ROM. VidLog16.mdb, located in the same folder, includes samples of 320- by 240-pixel live video captures using the Play Inc.'s Snappy video capture device. If you have a Snappy, you can download SNAPPY.VBX, a component of SNAP_SDK.EXE, from Play, Inc.'s Web site, http://www.play.com. (Code to copy to the Clipboard images captured by the Snappy is commented in VidLog16.frm.) To use the VidLog application, you need at least one ViSCA device connected to a COM port. If run VidLog without a ViSCA device, click OK when the Please insert a tape in the drive message appears.

You set up the vcr device (alias Vdeck in VidLog) in the Form_Load event handler of the main VidLog form, as shown in Listing 9.5.

Listing 9.5. Typical initialization code for the MCI vcr device type.




Private Sub Form_Load()



   'Set up initial conditions



   Me.Left = (Screen.Width - Me.Width) / 2



   Me.Top = (Screen.Height - Me.Height) / 2



   fMarkIn = True



   txtIn.Font.Bold = True



   fMarkOut = True



   txtOut.Font.Bold = True



   dtcLog.EOFAction = 0



   'Open the ViSCA device



   Call SendCommand("Open vcr1 alias Vdeck")



   'Check for presence of tape in drive



   Call SendCommand("Status Vdeck media present")



   If txtReturn.Text <> "true" Then



      'No tape in drive



      MsgBox "Please insert a tape in the drive.", 0, "Drive Not Ready"



   End If



   'Turn on ViSCA RCTC detection



   Call SendCommand("Set Vdeck time mode detect")



   'Set the time code to SMPTE drop-frame (29.97 fps)



   Call SendCommand("Set Vdeck time format smpte 30 drop")



   'Set screen display to timecode



   Call SendCommand("Set Vdeck index timecode")



   'Turn on timecode display, if available



   Call SendCommand("Index Vdeck on")



   'Set the preroll time to 20 frames



   Call SendCommand("Set Vdeck preroll 00:00:00:20")



   'Clear the Clipboard



   Clipboard.Clear



End Sub

VidLog's behind-the-scenes data acquisition activity consists of a Timer control that interrogates the Vdeck device to determine the tape position. Every 100 milliseconds (about 3 video frames), the timSMPTE_Timer event handler interrogates the Vdeck, which returns the current tape position as a SMPTE HH:MM:SS:FF string to the txtReturn text box. If the in and/or out points of the video clip aren't set, the current tape position is continuously written to the bound txtIn and/or txtOut text boxes with the following code:




Private Sub timSMPTE_Timer()



   'Load timecode for in and out point text boxes



   'Timer interval is set at 100 ms (3+ frames)



   Call SendCommand("Status Vdeck Position")



   'Don't overwrite existing clip in and out points



   If Not fMarkIn Then



      txtIn.Text = txtReturn.Text



   End If



   If Not fMarkOut Then



      txtOut.Text = txtReturn.Text



   End If



End Sub

Marking the in and out points with the Mark In and Mark Out command buttons while the tape is moving can cause up to a four-frame offset. The tape position buttons at the bottom of the form are the primary means to obtain frame-accurate clips; you use the Pause, Forward, and Reverse buttons to move to a specific frame with the Step command, as shown in Listing 9.6. Alternatively, you can type the timecodes in the In and Out text boxes. Clicking the Test Clip button plays the clip so you can visually and aurally check your in and out points.

Listing 9.6. Event handlers for controlling operation of a MCI vcr device.




Private Sub cmdTest_Click()



   #If Win32 Then



      'Cue the deck to play from in point to out point



      Call SendCommand("Cue Vdeck from " & txtIn.Text & _



         " to " & txtOut.Text & " preroll wait")



      Call SendCommand("Play Vdeck")



   #Else



      'Cue doesn't work in 16-bit mode, so play the clip



      Call SendCommand("Play Vdeck from " & txtIn.Text _



         & " to " & txtOut.Text)



   #End If



End Sub



Private Sub cmdLoad_Click()



   'Rewind to 2 minute standard start position



   Call SendCommand("Set Vdeck time mode detect wait")



   Call SendCommand("Seek Vdeck to 00:02:00:00")



End Sub



Private Sub cmdReverse_Click()



   If fMarkIn And fMarkOut Then



      'Rewind in 10X mode



      Call SendCommand("Play Vdeck reverse scan")



   Else



      'Single step reverse



      Call SendCommand("Step Vdeck by 1 reverse")



   End If



End Sub



Private Sub cmdStop_Click()



   'Stop the drive



   Call SendCommand("Stop Vdeck")



End Sub



Private Sub cmdPause_Click()



   'Put the deck in pause mode



    Call SendCommand("Pause Vdeck")



End Sub



Private Sub cmdPlay_Click()



   'Put the deck in play mode



   Call SendCommand("Play Vdeck")



End Sub



Private Sub cmdForward_Click()



   If fMarkIn And fMarkOut Then



      'Play in 10X forward mode



      Call SendCommand("Play Vdeck scan")



   Else



      'Single step forward



      Call SendCommand("Step Vdeck by 1")



   End If



End Sub



Private Sub cmdEject_Click()



   'Stop the tape drive, if running



   Call SendCommand("Stop Vdeck wait")



   'Rewind to the beginning of the tape



   Call SendCommand("Seek Vdeck to start wait")



   'Eject the tape



   Call SendCommand("Set Vdeck door open")



End Sub

Capturing, Displaying, and Saving Video Picons


If you have a video overlay card, you can use the MCI overlay device to save the content of the video buffer to a .bmp or .dib file, then use Visual Basic's LoadPicture function to transfer the data from the file to the Picture property of the Image control. A more elegant (and usually less expensive) approach is to use Play, Inc.'s Snappy video capture device and the SNAPPY.VBX custom control for picon capture. Snappy, which has a street price of about $180, plugs into your PC's printer port and has RCA connectors for composite analog video input from a VCR or camcorder and optional loop-through output to a TV monitor. Installation of the Snappy takes no more than five minutes and, unlike higher-priced video overlay cards, you don't need an open expansion slot or a connection to your graphic card's feature connector. If you use SNAPPY.VBX, you're limited to 16-bit Visual Basic 4.0 and a Jet 2.5 (Access 2.0) .MDB file to store the clip data and images.



Jet tables that contain full-color (15-bit or 24-bit color depth) images become very large, especially if you add images of 320- by 240-pixel or larger size. As an example, a 320- by 240-pixel image with 24-bit color depth requires 320 * 240 * 3 or more than 230KB of storage space.

You set the Snappy control's SnapFileType property to 1 - DIB on Clipboard to place a copy of the captured device-independent bitmap on the Clipboard. Set the value of the SnapSize property to 1 - Still, SnapSource to 0 - Tape, SnapState to 0 - Off, Stretch to True, VideoThru to False (unless you connect a TV monitor to the Snappy's video output connector), and Visible to False. The following code example from VidLog captures a 320- by 240-pixel image, pastes the image into the data-bound 180- by 120-pixel imgPicon control (with the Stretch property set to True), then saves the data from imgPicon in a LongBinary field of the VidLog16.mdb database:




Private Sub cmdSnap_Click()



   'Place the deck in pause mode for capture



   Call SendCommand("Pause Vdeck wait")



   'Copy a DIB to the Clipboard with the Snappy



   snp320x240.SnapState = 2



   'Paste the data to the Image control



   imgPicon.Picture = Clipboard.GetData()



   'Update the record in place



   dtcLog.Recordset.Edit



   dtcLog.Recordset.Update



End Sub


Don't try adding code to the PicSnapped event-handler to paste the DIB to the Image control; the PicSnapped event triggers when Snappy grabs the video data, long before the capture process is complete.

There are a variety of commercial applications for Play's Snappy in real-time video image acquisition. Examples include creating employee identification badges with low-cost color printers, capturing images for industrial quality control, automating surveillance systems (including high-resolution blowups of surveillance tapes), and publishing semi-live still pictures on the Internet. For Internet applications, use Snappy's variable-quality JPEG format (SnapFileType = JPEG) and save the image to the .jpg file that's linked to your home page. You can use MCI commands to start and stop the recording process (if your camcorder has a LANC connector) and take advantage of the camera-direct output to produce sharper images. Snappy's rendering of video content, even from low-cost camcorders, offers substantially better quality than still pictures shot with today's $1000 consumer-grade electronic cameras. Images captured from a Sony DCR-VX1000 DV camcorder in Snappy's maximum-resolution (1500- by 1125-pixel) mode with the 4 - Highest value for the SnapQuality property rival pictures shot on 35-mm film.

Creating a Batch Capture Log for Use by Adobe Premiere


The objective of VidLog is to create an ASCII batch capture file for Adobe Premiere from the Log table of VidLog16.mdb. Premiere's batch capture file import format requires a tab-separated text file with Reel Name (tape number), In, Out, and File Name, with optional Comment and Settings fields (see Figure 9.15). Controls are bound to the Recordset object of the dtcLog Data control, so a Recordset clone is used to create the required text file (see Listing 9.7.) A drive letter prefix, determined by the estimated accumulated size of the clip files, is added to the .AVI file name.

Figure 9.15. Adobe Premiere 4.2's Batch Capture window displaying part of a capture log created with VidLog16.

Listing 9.7. Code to create a formatted text file from a RecordsetClone object.




Private Sub cmdExport_Click()



   'Export the log in Adobe Premiere batch format



   Dim rsdLog As Recordset



   Dim strLog As String



   Dim strID As String



   Screen.MousePointer = 11



   'Create a recordset clone



   Set rsdLog = dtcLog.Recordset.Clone



   'Delete the existing file



   On Error Resume Next



   Kill "VidLog.txt"



   On Error GoTo 0



   'Open the batch file



   Open "VidLog.txt" For Append As #1



   rsdLog.MoveFirst



   Do Until rsdLog.EOF



      'Create padded four-character clip ID string



      Select Case rsdLog(0)



         Case Is > 999



            strID = CStr(rsdLog(0))



         Case Is > 99



            strID = "0" & CStr(rsdLog(0))



         Case Is > 9



            strID = "00" & CStr(rsdLog(0))



         Case Else



            strID = "000" & CStr(rsdLog(0))



      End Select



      'Reel name



      strLog = "Tape " & rsdLog(1) & Chr$(9)



      'In point



      strLog = strLog & rsdLog(2) & Chr$(9)



      'Out point



      strLog = strLog & rsdLog(3) & Chr$(9)



      '.AVI file name (no extension)



      strLog = strLog & "Clip" & strID & Chr$(9)



      'Scene



      strLog = strLog & rsdLog(7)



      'Append the record to the batch file



      Print #1, strLog



      rsdLog.MoveNext



   Loop



   'Close everything



   Close #1



   rsdLog.Close



   Set rsdLog = Nothing



   Screen.MousePointer = 0



End Sub

Summary


This chapter described the use of the data-bound DBGrid for browse-mode display and editing of desktop database tables. Although the examples use Jet 3.0 .mdb files, the code included in this chapter is equally applicable to foreign file types, such as dBASE, FoxPro, and Paradox table files. The use of DBCombo controls to create picklists and set foreign-key values also was covered. The chapter concluded with an example of the use of the native Image control to store bitmaps in the LongBinary fields of Jet databases.

The remaining two chapters of Section III, "An Introduction to Database Front-End Design," show you how to graph summarized (rolled-up) data and how to print reports using VBA code and the 32-bit Crystal Reports OLE Control that's included with the Professional and Enterprise versions of Visual Basic 4.0.

Previous Page Page Top TOC Next Page