Visual Basic for Windows is a highly successful programming language; beginning programmers can use it to create an operating Windows application with little or no Visual Basic code. To carry this feature into the realm of database applications,
Microsoft has added the Data control to Visual Basic 3.0, but they offer only a few controls that you can bind to the Data control. Therefore, developers of commercial VBX custom controls have created a variety of useful data-bound (also called
data-aware) VBXs that connect to the Data control. Visual Basic 4.0 includes several, new data-bound OLE controls, which Microsoft has developed or licensed from established control developers.
Visual Basic 2.0 had very limited database connectivity; users were restricted to the ODBC API if they wanted to create data access objects. As well, few commercial ODBC drivers were available when Visual Basic 2.0 was released. The addition of the
Data Access Object (DAO) and the Jet database engine to Visual Basic 3.0 gave Visual Basic developers the wide-ranging database connectivity of Access 1.x and, subsequently, Access 2.0. Virtually all client/server database suppliers now offer ODBC
drivers for their products, though not all suppliers have released 32-bit versions of their ODBC drivers. The upgraded 16-bit and 32-bit Data control of Visual Basic 4.0 and the Jet 3.0 DAO combined with Visual Basic 4.0's new controls, which you can bind
to the Data control, enable you to quickly create simple Windows database applications for desktop, client/server, and even mainframe databases with a minimum amount of code.
This chapter introduces you to the Data control and demonstrates how to use the Data control with simple data-bound controls. You use these controls to display and update the data in the fields of your database's tables.
The Data control automates the process of opening a database and creating a Recordset object by creating an instance of the Jet 2.5 (16-bit) or the Jet 3.0 (32-bit) DAO. The Recordset object is a virtual table containing the set of data elements you
need for your application. The simplest instance of a Recordset object is an image of a table, similar to a VIEW that you create with the SQL statement CREATE VIEW name AS SELECT * FROM table. The difference between the Recordset object and a
conventional VIEW of a table is that the data in a Recordset object is updatable if you have read-write access to the database and the table; a VIEW created with an SQL statement is read-only. The following sections describe the properties of and methods
applicable to the Data control as well as show you how to create a simple form that uses the bound text box control to display and update data in a database table.
Table 3.1 lists the name, type of access (in design and run mode), description, and use of each of the data-related properties of the Data control. The remaining properties of the Data control, such as Caption, Enabled, and Visible, are shared with the
majority of the other control objects of Visual Basic. The properties are listed in the order you ordinarily would enter them in the Properties window of the Data control.
Property Name | Design Mode | Run Mode | Description and Use |
Database | NA | RO | This is a reference (pointer) to the Database object that underlies the Data control. You use the value of the Database property to address the Database object of the Data control in your Visual Basic code. The Database property is automatically set to reference the Data control's base table when you set the DatabaseName property. |
DatabaseName | RW | RW | This is the well-formed path to the directory that contains any of the supported desktop databases, plus the filename and .MDB extension of a Jet database or the FILES.DDF data definition file of a Btrieve database. You leave DatabaseName empty if you specify the data source name in the value of the Connect property or if you want the ODBC Data Source dialog to appear. You can use the UNC syntax (\\ServerName\Folder[\FileName.ext]) to supply the path to files located on file servers that support the uniform naming convention (UNC). |
Connect | RW | RW | This is the connect string used to determine the type of desktop database. When using a Jet database, assign a single semicolon placeholder to the Connect property (;). Type ODBC; with no additional parameters if you want the ODBC Data Source dialog to appear, listing all of the currently registered ODBC databases. To connect to dBASE, FoxPro, Paradox, Excel, Text, or Btrieve files, you type the standard connect name followed by a semicolon: |
Paradox 3.x;, Paradox 4.x;, dBASE III;, dBASE IV;, FoxPro 2.0;, FoxPro 2.5;, FoxPro 2.6;, Excel 3.0;, Excel 4.0;, Excel 5.0;, Text;, or Btrieve;. | |||
To connect a database with the ODBC API, type this: | |||
ODBC; DATABASE=DefaultDatabase; DSN=DataSourceName; UID=UserID; PWD=Password;. | |||
(Some client/server databases require additional connect string arguments.) | |||
RecordSource | RW | RW | This specifies the underlying data source for the Recordset object. The RecordSource property can be the name of a table in the database, a QueryDef object of a Jet database, or a valid Jet database engine SQL statement. If you set the Options property's vbDataSQLPassThrough flag, you can send ANSI SQL statements to databases connected with the ODBC API. |
Recordset | NA | RO | This is a Recordset object defined by the DatabaseName, Connect, RecordsetType, and RecordSource properties of the Data control. The specific type of the Recordset created for the Data control is determined by the RecordsetType property setting. |
RecordsetType | RW | RW | This is a numeric flag that indicates the type of Recordset object you want the Data control to create. This value may be set to one of the following: vbRSTypeTable, vbRSTypeDynaset, or vbRSTypeSnapshot. |
If you don't specify a RecordsetType, the Data control attempts to create a Dynaset-type Recordset. | |||
EOFAction | RW | RW | This determines if clicking the Next Record button when on the last record of the Recordset causes the current record to remain the last record (vbEOFActionMoveLast, 0, the default) or displays the tentative append record (vbEOFActionEOF, 1) for an updatable Recordset. |
BOFAction | RW | RW | This determines if clicking the Previous Record button when on the first record of the Recordset causes the current record to remain the first record (vbBOFActionMoveFirst, 0, the default) or positions the record pointer at BOF (vbBOFActionBOF, 1). |
Options | RW | RW | This is a Long flag that sets and returns the values of the flags that determine your application's access as well as the access of other applications to the table(s) included in the Recordset object. (See Table 3.2.) The default value is 0. |
Exclusive | RW | RW | This is an Integer flag that determines the locking of the entire database specified by DatabaseName or Connect. True is for exclusive access, which denies others the right to open the database while your application has the database open; False (the default) is for shared access. |
ReadOnly | RW | RW | This is an Integer flag that determines whether your application can update the data values of table(s) in the open database. True is for read-only access; False (the default) is for read-write access. |
If you change the value of the Database, RecordSource, Options, Exclusive, or ReadOnly property with Visual Basic code, you need to apply the Refresh method to the Data control to make the new values effective.
If your database application is designed for stand-alone (single-user) use, you can improve the performance of your application by opening the database with the Exclusive property of the Data control set to True. In either a stand-alone or multiuser environment, setting the ReadOnly property to True improves the performance of decision-support applications that, by definition, can't modify data values in the underlying tables. Exclusive and read-only access to databases and tables eliminates the overhead associated with record- or page-locking operations needed to maintain concurrency in a multiuser environment. Read-only and exclusive access eliminates the requirement for a Jet database lock (Database.ldb) file so that you can open a Jet database stored on a CD-ROM.
Table 3.2 specifies the values of the Options flag used to control access to the table(s) that participate in creating the Recordset object of your Data control. Enter the decimal value of the flag in the Options text box of the Properties window for a
Data control. You can use the symbolic global constants, shown in Table 3.2, to make your Visual Basic code more readable. The symbolic Options constants are defined in the 32-bit Microsoft DAO 3.0 object library, supplied with Visual Basic 4.0. To use
these symbolic constants, your Visual Basic project must have a reference to the DAO Object Library; by default, the 32-bit version of Visual Basic 4.0 inserts a reference to this DAO Library for all new projects. You can substitute the Microsoft DAO
2.5/3.0 Compatibility Library to maintain compatibility with existing Visual Basic applications that use db. . . constants defined in the DATACONS.TXT file that accompanied Visual Basic 3.0. (DATACONS.TXT is not included with Visual Basic 4.0.) Use the
Object Browser (described in Chapter 2, "Understanding the Jet 3.0 Data Access Object") to view the available global symbolic constants; use the Tools | References command to add or delete references to a VB project.
Options Constant | Value | Purpose |
vbDataDenyWrite | 1 (&H1) | Prevents other applications from making changes to data values contained in tables that participate in the open Recordset object of your application. |
vbDataDenyRead | 2 (&H2) | Prevents other applications from reading data values contained in tables that are part of the open Recordset of your application. |
vbDataReadOnly | 4 (&H4) | Creates a Recordset that is not updatable by any user. |
vbDataAppendOnly | 8 (&H8) | Lets your application add a new record to the Recordset but does not read or update existing records. |
vbDataInconsistent | 16 (&H10) | Enables you to modify the value of a field of a Recordset object that will cause changes to be made to the values of other fields of the Recordset. Enables you to make changes to the "one" side of tables joined in a one-to-many relationship. See Chapter 4, "Optimizing the Design of Relational Databases," for the definition of consistency with joined tables. |
vbDataConsistent | 32 (&H20) | The opposite of vbDataIconsistent. Prevents you from modifying the value of any field of a Recordset object that will cause changes to be made to the values of other fields of the Recordset. (The default value 0 also specifies consistent updates.) |
vbDataSQLPassThrough | 64 (&H40) | Enables you to send SQL statements directly to the ODBC driver manager, and from the driver manager, to any database that you connect with the ODBC API. A client/server RDBMS processes the SQL statement on the server and returns only the records that your SQL statement specifies. Using the SQL passthrough method is described in Chapter 5, "Learning Structured Query Language," and in Chapter 20, "Creating Front-Ends for Client/Server Databases." |
A read-only Recordset is not the same as a snapshot-type Recordset object, nor is it the same as a Snapshot object. Read-only Recordset objects reflect changes made to the underlying tables by other applications in a multiuser environment.
In Visual Basic 4.0, the methods applicable to Dynaset-type Recordset objects in general also apply to Recordset objects belonging to a Data control (refer to Table 2.12 in Chapter 2). A Data control's Recordset object is accessible through the
Recordset property (refer to Table 3.1). The Data control, however, has two methods that aren't included in Table 2.12 (because the methods apply only to the Data control itself, not to Recordset objects). Also, the Data control uses the Refresh method in
a special way. Table 3.3 lists the three data-related methods that apply to the Data control.
Method | Purpose |
UpdateRecord | This method saves the values of all bound Data controls to the corresponding fields of tables in the database. Applying the UpdateRecord method has an effect similar to applying the Edit method. The methods change the focus to a control bound on another field and then apply the Update method; however, no Validate events are generated during the process. It's a common practice to invoke the UpdateRecord method when the user clicks OK, Save, or Update. |
UpdateControls | The reverse of the UpdateRecord method. The UpdateControls method replaces values in bound controls with the field values of the current record. The UpdateControls method is usually invoked as a result of clicking Cancel or Undo. The UpdateControls method has the same effect as making the current record current again, but it does not cause any events to be generated. |
Refresh | This method opens a new database (or reopens an existing database) and recreates the Data control's Recordset object. |
Statements that contain the UpdateRecord and UpdateControls methods are only allowed within Validate event-handling procedures. Examples of using UpdateRecord and UpdateControls methods are provided later in this chapter. When the
control is instantiated, in conjunction with opening the containing form object, the Refresh method automatically is applied to the Data control. As mentioned previously, after changing the value of any properties of the Data control, you need to apply the
Refresh method. The Refresh method recreates the Recordset object's virtual table. So, when your application is designed for a multiuser environment, you can use the Refresh method to ensure that the data values in your Data control's Recordset reflect the
current values of the data in tables.
The other methods applicable to the Data control are Drag, Move, and ZOrder; they act the same way on the Data control as they do on other control objects that support them.
The Validate event is the only data-aware event that the Data control object can trigger. The purpose of the Validate event is to let you use event-handling code to test any or all changes to the values of bound controls. You can only test changes
before they are made permanent in the database table(s) that underlie the Data control's Recordset. The Validate event is triggered under the following conditions:
The Validate event requires that your event-handling procedure includes parameters to accept the arguments returned by the Validate function. (This is the reason for the empty parentheses in the event's name. In this book, functions are identified by a
pair of parentheses after the function name.) The syntax of the event-handling procedure stub for the Validate event is this:
Sub DataControl_Validate([iaintIndex As Integer,] intAction As Integer, fSave As Integer) ... End Sub
If the control is a member of a control array, the optional iaintIndex parameter specifies the value of the index of the control. (The iaint prefix indicates that the variable is an index (i) into an array of the Integer (int) data type. (See
Appendix B, "Naming Conventions for Visual Basic Objects and Variables," for details of the variable naming conventions used in this book.)
The required intAction parameter specifies the method that triggers the Validate event; Table 3.4 lists the values of the intAction parameter. The declarations for the global symbolic constants, listed in Table 3.4, that supply the values
of the intAction parameters are part of the Visual Basic Object Library and are listed in the Object Browser. To use the values, just type them into your VB program code; if you wish, you may paste these constants into your code from the Object
Browser.
The fSave flag determines whether data in bound controls has changed (in which case the data will be saved to the tables of the database) when your Validate procedure terminates. True indicates changed values, and False indicates
that no change in data values has occurred. You can prevent saving changes by setting the value of the intAction parameter (cancel the event) or the fSave parameter to 0 (False).
This book uses the formal programming syntax for the terms arguments and parameters. When you call a function or procedure in your code, you pass arguments to it. The parameters of a function or procedure receive the values of the arguments. By default, Visual Basic passes references to the values of arguments, not the variable itself (except when you assign a literal value to an argument). Your function or procedure can change the value of variable arguments passed by reference. If you pass a literal value or precede the name of the variable passed as an argument with the By Val keyword pair, the actual value is passed to the function or procedure. You can't change the value of literal arguments or arguments passed by value.
Symbolic Global Constant | Value | Method Triggering Event |
vbDataActionCancel | 0 | Cancels the action that triggered the event upon termination of the procedure |
vbDataActionMoveFirst | 1 | MoveFirst |
vbDataActionMovePrevious | 2 | MovePrevious |
vbDataActionMoveNext | 3 | MoveNext |
vbDataActionMoveLast | 4 | MoveLast |
vbDataActionAddNew | 5 | AddNew |
vbDataActionUpdate | 6 | Update |
vbDataActionDelete | 7 | Delete |
vbDataActionFind | 8 | One of the Find. . . methods |
vbDataActionBookmark | 9 | Bookmark property was set |
vbDataActionClose | 10 | Close |
vbDataActionUnload | 11 | The Unload statement (method) is being executed |
You can't invoke methods, such as the Move. . . methods, that act on the Recordset object in a Validate event-handling procedure. Doing so causes recursion, a condition that happens when a function calls itself during its course of execution. Recursive functions, which are useful in arithmetic operations, are very difficult to write correctly and even harder to debug successfully.
You can use the events for individual bound controls to execute event-handler methods that enforce domain integrity at the field level, but individual event-handlers only execute if the controls receive the focus at some point during the update
process. Events for validating individual control values are discussed later in this chapter. Data entry operators won't likely want to tab through every text box on a transaction processing form when they have altered the value of only one field.
Therefore, the bound controls for some fields might never receive the focus. The Validate event is your only opportunity to ensure that domain integrity is enforced on all the fields of record.
Visual Basic's Validate event is similar to the BeforeUpdate event of Access 1+, except that the Validate event is triggered regardless of whether the data in the record is changed. (Access 1+ triggers the BeforeUpdate event only if the data in at least one field of a Recordset object is altered.)
When you add a Data control to a form, all you get is a set of VCR buttons that control the position of the record pointer relative to the records that make up your Recordset. To create a Data control connected to a Jet database, follow these steps:
The preceding step-by-step instructions are intended for xBase, PAL, Pascal, and C programmers who are just beginning to convert to Visual Basic 4.0. (Changing programming languages is akin to a religious conversion and usually causes similar effects on the convert.) Those readers who are accomplished Visual Basic programmers can be assured that, with a few minor exceptions, this is the only chapter of the book that includes beginning-level steps.
Figure 3.1 illustrates the design view of a Data control object connected to the Biblio.mdb sample database. The Publishers table is specified as the value of the RecordSource property.
Figure 3.1. Adding a Data control object to a Visual Basic form.
If your application includes a form that contains a Data control, Visual Basic 4.0 adds two additional properties. DataSource and DataField are added to the text boxes of the Properties window for the controls that can be bound to fields of a Recordset
object. Visual Basic 4.0 offers the following fourteen bound controls:
TextBox | This control is the basic control element for Data controls. You can display and edit data of any field data type (not just text) in a bound text box control. |
Label | This control displays the data in a field, but it doesn't allow editing of the data. A label control doesn't receive focus from Windows; therefore, it is useful in decision support applications, when read-only access to the database is the norm. |
Image | This control displays a graphic image contained in a LargeBinary field of a database that support the long varbinary (Image) or OLE Object field data type. The bound image control supports only .bmp, .dib, .ico, and .wmf; OLE objects stored in OLE Object fields are not supported. The image control enables you to use the Stretch property to scale the image to fit the size of the control. |
PictureBox | This control serves the same purpose as the image control, but it has additional properties and events. You can't scale images to fit the size of a bound picture box, but you can make the picture box automatically fit the size of the image. Use a picture box if you want to provide editing access to the image. |
CheckBox | This control displays or updates data in fields of the Boolean field data type (called Yes/No fields in Access 1.x or 2.0, and Logical fields in xBase). The Null value is represented by a gray check box. |
ListBox | This control displays a list of items from which the user can pick one item. You can use the list box control to provide read/write access to a text data field selected from the list. You must manually populate a list box control with its AddItem method. |
ComboBox | This control creates a combination list and a text box. Users may either pick an item from the list or enter an item in the text box. You can use the combo box control to provide read/write access to a text data field selected from the list. You must manually populate a list box control with its AddItem method. |
OLE container | This control lets you display and edit OLE 1.0 and 2+ objects contained in or linked to OLE Object fields of Jet databases. Using the OLE container control is one of the subjects of Chapter 14, "Integrating Database Front-Ends with OLE 2.1." |
MaskEdBox | This custom control masked edit control text box lets you determine the formatting of the data displayed with placeholders for data characters. The bound masked edit control is equivalent to a dBASE GET FieldName command followed by a PICT(URE) clause, but the method of specifying the data entry mask differs. |
3D panel (SSPanel) | This custom control adds a three-dimensional appearance to standard and bound controls contained within the 3D panel. The Caption property of the 3D panel is set to the value of the field that serves as the DataSource property of the 3D panel. |
3D check box | This custom control creates three-dimensional |
(SSCheck) | versions of the check box control. |
DBList | This custom control is similar to a list box control, but it is automatically filled from the Data control to which it is bound. In addition to a reference to the DAO 3.0 library, you must also reference the Microsoft Databound List Controls Library in order to use this control. (Use the Tools | References command to add or remove references to external libraries.) |
DBCombo | This custom control is similar to a combo box control, but it is automatically filled from the Data control to which it is bound. In addition to a reference to the DAO 3.0 (or 2.5/3.0) Library, you must also reference the Microsoft Databound List Controls Library in order to use this control. |
DBGrid | This custom control provides read/write access to a Recordset by drawing a multi-record grid to display records from a Data control. Users can either choose items from the grid or enter new records into the gridsimilar to the Access datasheet view of a table or query result set. The DBGrid control is filled automatically by the Data control to which it is bound. You must reference the Apex Databound Grid Library in order to use this control. |
Data binding of the ListBox, ComboBox, and OLE container controls in the preceding list is new with Visual Basic 4.0. Only the Professional Edition of Visual Basic 4.0 offers the last six controls in the preceding list, each of which
is an OLE control (.ocx). Chapter 9, "Using Advanced Data-Aware OLE Controls," shows you how to get the most out of the DBList, DBCombo, and DBGrid controls. The standard Visual Basic 4.0 controls now offer the Windows 95 3D look when you set the
Appearance property to 3D (the default).
The Publishers table has a total of ten fields. For this exercise, you add just four sets of bound labels and text boxes to display the names and values, respectively, of the PubID, Name, CompanyName, and Comments fields of the Publishers table. To do
this, follow these steps:
When you paste controls onto a form that already has controls with the same name, VB asks you to confirm adding the control, and then offers to create a control array. If you confirm adding the control (regardless of whether you create a control array), Visual Basic 4.0 renames the pasted control so there is no longer a name conflict, but it does not change the control's Text property. Therefore, if you paste a control named Text1 onto a form that already has a control named Text1, VB will rename the pasted control as Text2; the caption that you see in the text control will still say "Text1." Each text control in Figure 3.2 has had its Text property altered for clarity.
Users of Microsoft Access will notice that the behavior of labels and text boxes in Visual Basic's design mode differs from that of Access's design mode. Visual Basic doesn't offer labels that are associated with text boxes, or that automatically
assign the field name to their Caption property. When you double-click an Access label, you can edit the Caption property. When you double-click any Visual Basic control object in design mode, the code editing window for the default event-handler procedure
stub (or the first event-handling procedure you wrote, in alphabetic order) appears.
Unlike Access, Visual Basic enables you to create label controls that are bound to a field of Recordset. Thus, you can add the ultimate read-only control to a Visual Basic forma control that can't receive Windows focus. Many Access developers would trade their eyeteeth for the equivalent of a bound label control.
The design of the data display and editing form (the one you created in the previous steps) is illustrated by Figure 3.2.
When you choose the Run | Start command, your form appears similar to the one shown in Figure 3.3. (Although there is an entry for Sams Publishing in the Biblio.mdb sample database supplied with Visual Basic 4.0, it doesn't contain the
comments shown here.) The VCR buttons of the Data control manipulate the record pointer to determine the current record. Here are the VCR buttons from left to right:
First record | This is the equivalent to applying the MoveFirst method to the Recordset (rewind). |
Next record | This is the equivalent to applying the MoveNext method (forward or play). |
Previous record | This is the equivalent to applying the MovePrevious method (reverse or review). |
Last record | This is the equivalent to applying the MoveLast method (fast forward). |
Figure 3.3. A very simple data display and editing application in run mode.
Access displays a "record number" between the two sets of its record selector VCR buttons, but Visual Basic's Data control does not. The record number displayed by Access isn't the same as the record number returned by the xBase function RECNO. RECNO returns a number that specifies the physical position of the record in the .DBF file, relative to the first record, which is numbered 1. The Access record number is a contrivance that represents the logical position of a record within an object of the Recordset class. Therefore, the number that Access associates with a record depends on the sort order of the Recordset. The concept of a record number doesn't exist in SQL databases, nor is record number an element of the relational algebra used to describe relational database structures. Both Access and Visual Basic use the Bookmark property to identify a specific record; therefore, you can return to the identified record after moving the record pointer to another location. Both Access and Visual Basic provide the RecordCount property, which you can use to determine the number of records in a Recordset object. Jet 3.0 also includes the new AbsolutePosition property that identifies each record in a Recordset (in the sort order of the Recordset) by number, starting with 0 for the first record. The new PercentPosition property specifies a record that corresponds to a particular percentage of the number of records in the Recordset.
A form can include more that one Data control. Figure 3.4 illustrates the form from Figures 3.2 and 3.3 with an added Data control that's connected to the Titles table of Biblio.mdb, together with the labels and bound text boxes necessary to display
some of the fields of the Titles table.
Figure 3.4. A form with two independent Data controls and their associated bound control objects.
Each of the Data controls creates a Recordset object and manipulates its own record pointer. Text boxes and other bound controls with a binding to one of the Data controls reflect only changes to the record pointer of the Data control to which the
control is bound. As an example, you can copy and paste a duplicate of all the controls associated with the Data1 control and then change the DataSource property value of the copies of the bound controls to create a totally independent instance of a
Recordset object that is derived from the same Database and TableDef objects used by Data1.
If you don't want to create the preceding form, you can find the project and form files in your \Ddg_vb4\32_bit\Chaptr03 folder as Datacon3.vbp and Datacon3.frm (that is, if you installed the examples from the accompanying CD-ROM using the default directory structure).
The primary advantage of using Data controls is that they enable the binding of text boxes, labels, picture boxes, image frames, check boxes, and the other bound controls listed earlier in the chapter. This chapter (as well as the earlier ones) points
out that in order to create example-level Visual Basic database applications when using Data controls and bound control objects, you need to write little or no Visual Basic code. Data and bound controls are advantageous at this point in the book, because
these controls let you learn the basic methods of manipulating Recordset objects without becoming entangled in ancillary housekeeping code.
On the other hand, the number of data-enabled controls, although enhanced in Visual Basic 4.0, is still somewhat limited; Access's bound object group, which returns numeric values based on mutually-exclusive option button or check box selections,
doesn't have a Visual Basic equivalent. There is no Visual Basic version of the versatile linked subforms that are native to Access. You must write a considerable amount of code to create effective graphs with the Graph custom control, while Access
provides a Graph Wizard to help you create graphs and charts with MSGraph5. Each of these programming subjects (which Access developers might call "workarounds") is dealt with in Part III of this book, "An Introduction to Database Front-End
Design."
The following sections show you how to use Visual Basic event-handling code to manipulate the record pointer, validate updates to records, and perform other simple chores associated with entry-level database applications.
You can read the value of all the data-related properties of a Data control and set the value of those data-related properties (shown in Table 3.1) as read/write (RW) in run mode. The remaining properties of the Data control object are common to the
majority of other Visual Basic control objects. Properties shared by all or most Visual Basic controls are not discussed in this book. The only data-related properties of the Data control that you can't set are the Database and Recordset properties. These
properties are pointers to the associated Database and Recordset objects, respectively. To protect the unwary, Visual Basic doesn't enable performing operations on pointers.
You can change the source of Visual Basic's Database and Recordset objectsto which form controls are bound during run mode. Therefore, you need not specify a DatabaseName or supply a value for the Connect string (when the value is needed) in your
Visual Basic code. You can supply the property value(s) in run mode when you open the form that contains the Data control. Alternatively, you can let the user enter the source information in one of the following two ways: by selecting from a text box or by
choosing tables from list box entries created from the Name property of TableDef objects of the TableDefs collection.
You also can read and set the properties of bound Data controls in run mode. Therefore, it's possible to use members of the TableDefs and Fields collections to supply the value of the DataField property. You can do this for each group of bound text
boxes to which you haven't assigned default DataField values in design mode. Table 3.5 lists the data-related properties of bound controls. All the properties listed in Table 3.5 are read/write in run mode.
Property Name | Control(s) | Description |
DataSource | All bound controls | The name of the Data control to which the control is bound. |
DataField | All bound controls | The name of the field of the Data control's Recordset object to which the control is bound. |
DataChanged | All bound controls | True if the user or the application changes the data; False if data is unchanged. |
Text | Text box | Value of the data in the bound field. |
Value | Check box | Unchecked (0), checked (1), grayed (2). |
Caption | Label | Defaults to the Name property of the label in design mode, and the value of the bound field in run mode, if you specify a value for the DataField property. |
Most database front-end applications provide a set of command buttons to manipulate the record counter, as well as to update existing records, add new records, and delete obsolete records. The following are several reasons why command buttons are
preferred to graphical record selector objects (such as VCR buttons):
The following sections show you how to substitute command buttons and Move. . . methods for the VCR record selection buttons, how to avoid the No current record pitfalls (which occur when you don't use the Data control's built-in safety net for
manipulating the record counter), and how to find a specific record with the Find. . . methods.
The BOFAction and EOFAction properties of the Data control apply only to movement of the control's record pointer by mouse clicks to the Previous Record or Next Record buttons, respectively. These properties do not affect the behavior of the Data control when programmed with VBA code.
Figure 3.5 illustrates the design of a data entry form. The form has four command buttons, which substitute for the four VCR record-selection buttons of the Data control, plus the ubiquitous Quit button. To create the form, follow these steps:
Figure 3.6. The error that occurs when you attempt to move beyond the beginning or end of the Recordset.
There are several strategies you can apply to prevent the No current record problem. Visual Basic provides a very flexible set of options for almost every database operation you can imagine. Therefore, you might encounter a "choice crisis"
when attempting to choose the optimal method for writing code to solve a particular problem. The following are four of the choices you can use to prevent the appearance of the No current record message:
If you choose the last technique of the preceding list, you need to alter your code for the cmdNext_Click and cmdPrior_Click event-handling procedures to contain the code that appears in Listing 3.1.
Listing 3.1. The code for preventing the No current record message from appearing.
Private Sub cmdNext_Click () Data1.Recordset.MoveNext If Data1.Recordset.EOF Then Beep Data1.Recordset.MoveLast End If End Sub Private Sub cmdPrior_Click () Data1.Recordset.MovePrevious If Data1.Recordset.BOF Then Beep Data1.Recordset.MoveFirst End If End Sub
Choosing any of the other techniques in the preceding list requires additional code. As a rule of thumb, the quality of a procedure is inversely proportional to the square root of the number of the lines it contains (not counting comments).
There are only sixty-some records in the Publishers table of Biblio.mdb, so finding a record that matches a particular criteria with the record selector buttons, while not a major undertaking, is somewhat inconvenient. Using record selector buttons to
locate a particular record is obviously an efficient method, especially with large tables. The Find. . . method solves this problem for Recordset objects with the Dynaset type or Snapshot type. The Data control, by default, creates a Dynaset-type Recordset
object. (You can't use the Find. . . method on table-type Recordset objects; indexed table-type Recordset objects require that you apply the Seek method.) Here is the syntax of the Find. . . method:
rsRecordset.FindFirst strCriteria rsRecordset.FindNext strCriteria rsRecordset.FindPrevious strCriteria rsRecordset.FindLast strCriteria
rsRecordset represents any valid object reference to a Dynaset-type (rsdRecordset) or Snapshot-type (rssRecordset) Recordset object. The value of strCriteria is the WHERE clause of an SQL statement without the WHERE reserved
word. Chapter 5, "Learning Structured Query Language," describes the syntax of the WHERE clause of SQL statements in detail. If you want to search for the first occurrence of a publisher located in California, use the following code fragment in
the event-handler for a Find button:
Dim strCriterion As String strCriterion = "State = 'CA'" Data1.Recordset.FindFirst strCriterion
Notice that you need to enclose the value for which you are searching within quotation marks. Single-quote characters (') are the standard for identifying literal values in SQL statements, and single quotes eliminate the necessity of using
""" or Chr(34) expressions to add double-quote characters (") to your code.
Hard-coding search literals is not an effective method, so you need a means to let the user enter the Find. . . criteria. The simplest method is to use the InputBox function to create a simple dialog that includes a text box into which the user
can enter the value of strCriteria. You can create a more elegant Find dialog by opening a modal pop-up form that includes a text box, Find and Help buttons, and option buttons to choose which of the Find. . . methods to invoke. Chapter 8,
"Designing a Decision-Support Front-End," includes an example of a full-featured Find dialog based on the design of the Find dialog of Access.
To implement the FindFirst method for the DataCmd3 application, follow these steps:
Listing 3.2. Code to open an input box then invoke the FindFirst method.
Private Sub cmdFindFirst_Click() 'Declare local variables Dim strPrompt As String Dim strTitle As String Dim strEntry As String 'Set up the prompt and the title, then open the input box strPrompt = "Enter the two-character postal abbreviation " & _ "for the state you want to find." strTitle = "Find First Occurrence of State" strEntry = InputBox(prompt:=strPrompt, title:=strTitle) If Len(Trim(strEntry)) = 2 Then 'The user made an entry, set a bookmark strBookmark = Data1.Recordset.Bookmark 'Create the criterion string strCriterion = "State = '" & UCase(strEntry) & "'" 'Invoke the FindFirst method Data1.Recordset.FindFirst strCriterion If Data1.Recordset.NoMatch Then 'No matching record was found MsgBox prompt:="No records match '" & _ UCase(strEntry) & "'.", _ title:="Record Not Found" Data1.Recordset.Bookmark = strBookmark Else 'A match was found, place the caret in the State field Text7.SetFocus End If End If End Sub
Figure 3.7. The input box for entering a criterion value for the FindFirst method.
If you want to bypass the typing of the code examples in Listing 3.2 as well as the remaining code listings of this chapter, open Datacmd.vbp in your \Ddg_vb4\32_bit\Chaptr03 folder.
You can add a Find Next button that uses the same criterion value employed by the FindFirst method with the code shown in Listing 3.3.
Listing 3.3. Code for invoking the FindNext method with a previously-created criterion.
Private Sub cmdFindNext_Click () 'Check to see if the criterion has been entered If Len(strCriterion) > 0 Then 'Invoke the FindNext method Data1.Recordset.FindNext strCriterion If Data1.Recordset.NoMatch Then 'No matching record was found MsgBox prompt:="No more records match " & strCriterion, _ Title:="Record Not Found" Data1.Recordset.Bookmark = strBookmark Else 'A match was found, place the caret in the State field Text7.SetFocus End If Else 'There was no criterion MsgBox prompt:="You need to enter a criterion with the " & _ "Find First button first", _ Title:="No Criteria Entered" End If End Sub
A better method than using the message box to advise the user that no criterion has been entered in conjunction with a FindFirst operation is to disable the Find Next button until a FindFirst operation has found a matching record. Code that enables
command buttons only when their use is appropriate to the status of the application is called housekeeping code. Chapter 8 deals with housekeeping code for decision-support applications.
The Find. . . methods of Visual Basic applied to Recordset objects use the same syntax as and behave identically to the Find. . . methods of Access applied to Access 2+ Recordset objects of the Dynaset and Snapshot types, as well as the Dynaset and Snapshot objects of Visual Basic 3.0 and Access 1.x.
Figure 3.8 illustrates the set of buttons that you need to complete a basic data display and editing form. The added buttons include Update, Cancel, Add New, Delete, and Return. The Update button confirms both editing and appending new record
operations. The Return button returns the record pointer to a record that you have previously identified with a bookmark.
Figure 3.8. The button set for creating a complete data entry form.
The code examples in Listing 3.4 constitute the simplest form of event-handling code for each of the buttons. Only the bare minimum of code necessary to perform the buttons' functions is included. Some combinations of procedures generate an error, such
as clicking the Add New button and then clicking the Delete button. You also can generate an error if you attempt to add a new record without entering a unique numeric value for the PubID field. Code you add to the Validate event-handler, described in the
next section, can detect the missing PubID problem.
Listing 3.4. Code required to provide DataCmd3 with minimal record-editing capability.
Private Sub cmdUpdate_Click () 'Transfer data from changed text box values to current record Data1.Recordset.Update End Sub Private Sub cmdCancel_Click 'Cancel an AddNew or Update operation 'Restore the value of text boxes to the original field values 'NOTE: The Text1 text box is bound to the PubID field Data1.Refresh If IsNull(Text1.Text) Or Text1.Text = "" Then 'Don't attempt to update controls from an aborted append If Len(strBookmark) > 0 Then 'Return to the bookmarked record Data1.Recordset.Bookmark = strBookmark Else 'Go to the last record Data1.Recordset.MoveLast End If End If End Sub Private Sub cmdAddNew_Click () 'Position the record pointer on the tentative append record Data1.Recordset.AddNew End Sub Private Sub cmdDelete_Click () 'Delete the current record with confirmation If MsgBox(prompt:="Are you sure you want to delete this record?", _ buttons:=vbQuestion + vbYesNo, _ title:="Delete Record") = vbYes Then 'Test if the to-be-deleted record is the bookmarked record If (Len(strBookmark) > 0) And _ (strBookmark = CStr(Data1.Recordset.Bookmark)) Then strBookmark = "" End If 'Delete the record Data1.Recordset.Delete 'Refresh the Recordset Data1.Refresh If Len(strBookmark) > 0 Then Data1.Recordset.Bookmark = strBookmark Else Data1.Recordset.MoveLast End If End If End Sub Private Sub cmdMark_Click () 'Mark the current record strBookmark = Data1.Recordset.Bookmark End Sub Private Sub cmdReturn_Click () If Len(strBookmark) > 0 Then 'Move the record pointer to the Bookmarked position Data1.Recordset.Bookmark = strBookmark Else Beep End If End Sub
Like the examples for the Move. . . commands in the prior section, the preceding code examples in this section do not include housekeeping code to set the enabled properties of the command buttons or provide error-handling routines. Chapter 13,
"Designing Online Transaction-Processing Applications," shows you how to determine the logic for command buttons that appear on data entry forms.
Although the DataCmd3 application provides only rudimentary control over record pointer positioning and editing operations, DataCmd3 demonstrates how little Visual Basic code is required to create an application that is almost usable in a commercial
environment. Figure 3.9 shows the appearance of the final version of the Datacmd3.frm (Form1).
Figure 3.9. Running the DataCmd3 data entry form.
When you click the Add New button, you position the record pointer on the tentative append record. The tentative append record is a buffer (dedicated memory location) for a record that is automatically appended to the Recordset if the user enters data in any of the fields of the record and then moves the record pointer. The tentative append record is similar to the empty record you create with the xBase APPEND BLANK command, except that Visual Basic only appends the record if there is data in at least one field of the record.
A new record or an edited record without the data required to maintain referential integrity and information about the real-world object to which the record refers (in this case a publishing company) is invalid and compromises the data integrity of the
database. To constitute a valid record in the Publishers table, a record needs to have a unique PubID value to serve as the primary key field. The PubID value, a Long integer, of added records should be consecutive with the last (highest numbered) record
in the Publishers table. In addition, a record without at least a publisher's imprint (the Name field) is invalid. Therefore, you need to test at least the value of the PubID and Name fields of a proposed update to an existing record or a newly-added
record before the data in the Publishers table is altered. The Validate event and the associated Sub Validate event-handling code serve this purpose.
The Data1_Validate event-handler code of Listing 3.5 makes the tests described in the preceding paragraph and displays a message box if the PubID or Name value is missing. If either value is missing, both the intAction and fSave parameter values are
set to False, preventing addition of an invalid record. The code added to the cmdAddNew_Click event-handler automatically adds 1 to the last valid value of PubID and inserts the value in the PubID text box (Text1), so you don't have to remember the
last PubID number.
Listing 3.5. Using the Validate event-handler to verify the minimum data entry requirement.
Private Sub Data1_Validate (intAction As Integer, fSave As Integer) 'Validate entries when adding new or editing existing record Dim strMsg As String 'Message box message If (intAction = vbDataActionAddNew) Or _ (intAction = vbDataActionMoveNext) Then 'Add new or update occurred: Text1 is PubID, Text2 is Name If (Text1.Text = "") Or (Text2.Text) = "" Then If Text1.Text = "" Then strMsg = "A Publisher ID number (PubID) is required." Else strMsg = "An imprint (Name) is required." End If MsgBox prompt:=strMsg, Title:="Required Field Data Missing" intAction = vbDataActionCancel fSave = False End If End If End Sub Private Sub cmdAddNew_Click () Dim lngPubID As Long 'Add a new record after refreshing the Dynaset Data1.Refresh 'Find the last value of PubID and add 1 Data1.Recordset.MoveLast lngPubID = Data1.Recordset!PubId 'Text2 is the Name field If Text2.Text <> "" Then 'Increment PubID if the last record is valid lngPubID = lngPubID + 1 End If 'Position the record pointer on the tentative append record Data1.Recordset.AddNew 'Pass the PubID value to the PubID text box (Text1) Text1.Text = lngPubID End Sub
Apply the Refresh method liberally when you add, update, or delete records of Recordsets. Using the Refresh method (except when validating an entry) ensures that your application sees only valid records and records whose data values have been altered by others in a multiuser environment.
This chapter introduced you to the use of the Data control and the bound control objects that you can use when you add a Data control to one of the forms of your application. Elementary examples of using Visual Basic code to position the record
pointer, to add, update, and delete records, and to validate data prior to using the data to update values in tables outline the basic capability of the Data control. At this point, you've created your first data display and editing form with Visual Basic
4.0.
This chapter completes Part I of Database Developer's Guide with Visual Basic 4. You'll create more sophisticated examples of decision-support and transaction-processing applications when you reach Part III, "An Introduction to
Database Front-End Design," and Part IV, "Advanced Programming with Data Access and OLE Objects." The next chapter, "Optimizing the Design of Relational Databases," introduces you to database design methodology and shows you some
of the automated design tools available for Jet and client/server databases.