Upload
antonio-gonzaga
View
79
Download
1
Embed Size (px)
Citation preview
Using Crystal ReportsLevel:
This crystal report tutorial shows you how to use Crystal Reports 4.6, which is the version of Crystal Reports that shipped with Visual Studio / Visual Basic 6.0. Crystal Reports provides a relatively easy way to incorporate reporting into your application. The basis of a report is a database query.
Crystal Report Tutorial VersionIt should be pointed out that this version of Crystal Reports is quite old, and that this version and the past versions of Crystal Reports that have shipped with VB are generally "watered down" versions that do not have all the bells and whistles of the standalone product that is sold by Seagate Software. Furthermore, CR 4.6 will not work with MS-Access databases higher than Access 97. That said, what you learn about designing reports with CR 4.6 will help when working with later versions. Regarding the MS-Access issue, later versions of Access (2000, 2002, etc.) can read and save in Access 97 format.
Installing VB6 Crystal Reports 4.6Crystal Reports 4.6 is bundled with Visual Studio/Visual Basic 6.0, but it is not installed automatically. To install it manually, locate the CRYSREPT folder on your installation CD – for Visual Studio 6.0, the path is COMMON\TOOLS\VB\CRYSREPT on the third CD. In that folder, double-click the file CRYSTL32.EXE. You will be asked if you want to install Crystal Reports. Respond Yes. It will then tell you where it is going to install CR; you can override the location if desired. Following that, CR will be installed, and a few moments later you should get a message indicating that installation was successful. The Sample DatabaseThe sample database used for this tutorial (as well as others to follow) is an Access 97 format database named EMPLOYEE.MDB. EMPLOYEE.MDB contains three tables: EmpMast, DeptMast, and JobMast. The tables are structured as follows: EmpMast table:
Field Name DataTypeEmpNbr AutoNumber Primary Key. Uniquely identifies each employee in the database.EmpFirst Text (50) Employee's first nameEmpLast Text (50) Employee's last name
DeptNbr Number (Long Integer) Foreign Key to PK of DeptMast table. Identifies which department the employee works in.
JobNbr Number (Long Integer) Foreign Key to PK of JobMast table. HireDate Date/Time Date the employee was hiredHrlyRate Number (Single) Employee's hourly rateSchedHrs Number (Single) The number of hours per week the employee is scheduled to work.
DeptMast table:
Field Name DataTypeDeptNbr Number (Long Integer) Primary Key; uniquely identifies each department in the database.DeptName Text (50) The name of the department.Location Text (50) The department's location (could be a building, suite number, floor,
etc.) JobMast table:
Field Name DataTypeJobNbr AutoNumber Primary Key; uniquely identifies each job in the database.JobTitle Text (50) The job title (description).MinRate Number (Single) The minimum hourly rate that somebody working in this position is
usually paid.AvgRate Number (Single) The average hourly rate that somebody working in this position is
usually paid.MaxRate Number (Single) The maximum hourly rate that somebody working in this position is
usually paid.
Designing the Reports Two reports will be developed from this database: "Annual Salary Expenses by Department" and "Annual Salary Expenses by Job". For the Annual Salary Expenses by Department report, you want to show various fields from the employee database tables grouped and subtotaled by department. You also want to show a grand total at the end of the report. A sketch of the design might look something like the following:
Annual Salary Expenses by Department
JOB WKLY HRLY
EMP # EMP NAME # JOB TITLE HIRE DATE HOURS RATE ANN SALARY
----- -------- --- --------- --------- ----- ---- ----------
DEPT XXXX XXXXXXXXXXXXXXXX
XXX XXXXXXXXXXXXXXX XXX XXXXXXXXXX XX/XX/XX XX.XX XX.XX $XXX,XXX.XX
XXX XXXXXXXXXXXXXXX XXX XXXXXXXXXX XX/XX/XX XX.XX XX.XX $XXX,XXX.XX
DEPT XXXX XXXXXXXXXXXXXXXXXXXXXXXX TOTALS: $XXX,XXX.XX
.
.
.
GRAND TOTALS: $XXX,XXX.XX
The design of the Annual Salary Expenses by Job report is similar, except that you want to show
various fields from the employee database tables grouped and subtotaled by job. You might sketch
the design as follows:
Annual Salary Expenses by Job
DEPT WKLY HRLY
EMP # EMP NAME # DEPT NAME HIRE DATE HOURS RATE ANN SALARY
----- -------- --- --------- --------- ----- ---- ----------
JOB XXX XXXXXXXXXXXXXXXX
XXX XXXXXXXXXXXXXXX XXXX XXXXXXXXXX XX/XX/XX XX.XX XX.XX $XXX,XXX.XX
XXX XXXXXXXXXXXXXXX XXXX XXXXXXXXXX XX/XX/XX XX.XX XX.XX $XXX,XXX.XX
JOB XXX XXXXXXXXXXXXXXXXXXXXXXXX TOTALS: $XXX,XXX.XX
.
.
.
GRAND TOTALS: $XXX,XXX.XX
This tutorial will start off by showing the step-by-step process for designing the first report, "Annual Salary Expenses by Department". Once that is done, it will be a simple matter to copy that report and modify it to create the Annual Salary Expenses by Job report.
With the report designs in mind, open Crystal Reports (Report Designer) from the VB Add-Ins menu:
A registration form appears. Click the Cancel button.
From the Crystal Reports File menu, select New:
The Create New Report dialog box appears. Click the Standard button.
The Create Report Expert dialog box appears with the Step 1: Tables tab open. Click the Data File button:
The Choose Database File dialog box appears. Navigate to the directory where your database file resides, then click the name of the database file so that it appears under "File Name:". Click the Add button, then click the Done button.
The 2: Links tab then appears, showing you a diagram similar to that of Access' Relationships diagram.
Move on by clicking the 3: Fields tab. The "3: Fields" tab initially looks like this:
Using the "Add ->" button, select the desired fields from the "Database Fields" listbox so that they appear in the "Report Fields" listbox. Select the fields based on the initial design. For fields that are involved in a primary key – foreign key relationship, only select one of those fields from either table (for example, select the DeptNbr field from either the DeptMast table OR the EmpMast table, but not both). Select the following Database Fields:Select DeptNbr and DeptName from the DeptMast table.Select EmpNbr From the EmpMast table.Skip down to the JobMast table and select the JobNbr and JobTitle.Jump back up to the EmpMast table and select the HireDate, HrlyRate, and SchedHrs. Your screen should look like this:
Still sticking with Tab 3, you can specify column headings by selecting each of the Report Fields in turn, and giving them a heading by typing the desired text in the "Column Heading:" textbox (by default, the column heading is the same as the field name). Specify the column headings as follows:
Report Field Column HeadingDeptMast.DeptNbr Dept #DeptMast.DeptName Dept NameEmpMast.EmpNbr Emp #JobMast.JobNbr Job #JobMast.JobTitle Job TitleEmpMast.HireDate Hire DateEmpMast.HrlyRate Hrly RateEmpMast.SchedHrs Wkly Hours
We're still not done with Tab 3. We need two computed columns, one for the employee name (which will be a concatenation of the EmpLast and EmpFirst fields) and one for the annual salary (which will be the employee's hourly rate multiplied by their weekly hours multiplied by 52).
Click the Formula button. The "New Formula" dialog box appears. Type EmpName in the textbox and click OK
The "Edit Formula" dialog box appears. In the "Formula text" area, type:
TrimRight ({EmpMast.EmpLast}) + ", " + TrimRight ({EmpMast.EmpFirst})
Your screen should look like this:
Note: Crystal Reports has its own formula syntax, which differs from the syntax of VB and Access expressions. You can scroll the "Fields", "Functions", and "Operators" listboxes above the Formula text entry area to see what's available. Also, instead of typing everything directly into the text entry area, you can double-click on a listbox selection and the text of that selection will appear in the Formula text box.
Click the Check button. If you entered the formula correctly, the message "No errors found" will pop up. Passing that, click the Accept button. The formula will then appear in the Database Fields listbox (as "@EmpName"). With @EmpName highlighted, click the "Add->" button to add it to the Report Fields list. In the Report Fields list, drag and drop "@EmpName" so that it appears under "EmpMast.EmpNbr". Give @EmpName a column heading of "Employee Name".
Now we must create the annual salary formula. To do so, follow these steps:
Make sure anything OTHER than @EmpName is selected in the Database Fields listbox.
Click the Formula button. In the "New Formula" dialog box, type "AnnSal" and click OK. In the "Edit Formula" dialog box, type
{EmpMast.HrlyRate} * {EmpMast.SchedHrs} * 52click Check, then Accept.
Use the "Add->" button to bring @AnnSal over from the Database Fields list to the Report Fields list.
In the Report Fields listbox, drag and drop the @AnnSal formula so that it is the last field in the list.
Give @AnnSal a column heading of "Ann Salary".
Click the 4: Sort tab. Select DeptMast.DeptNbr from the "Report Fields" list and click the "Add->" button. DeptMast.DeptNbr then appears in the "Group Fields" list. Repeat this process for @EmpName. Your screen should look like this:
Click the 5: Total tab. Within this Total tab, an inner tabbed dialog appears, with one tab for each field selected in the sort. On the "DeptMast.DeptNbr" tab, remove all items except "@AnnSal" from the Total Fields list, as shown below. What we are saying is that we want to print a subtotal for the annual salary every time there is a change, or break, in the department number.
Still in the "5: Total" tab, click the "@EmpName" tab and remove all items from the Total Fields list, as shown below. (We don't want to print subtotals after every employee name.)
We don't need to do anything in tab 6, so click the 7: Style tab. For the title, type "Annual Salary Expenses by Department".
Click the Preview Report button. At this time, the "Create Report Expert" is finished and you can't go back to it, but you can make any desired changes in the Crystal Reports interface. Following is the screen that is initially displayed after you click the Preview Report button from Step 7 of the Expert:
On the Crystal Reports toolbar, click the Zoom button, so you can see what the Expert did for you (it gives you a start, but it needs some work):
Click the Design tab:
Perform the following steps to fix up the report:
Go to the File menu, select Printer Setup, and change the Orientation to Landscape.
In the Page header area, click the title ("Annual Salary Expenses by Department") to select it. Resize the title so that it spans the width of the entire report. Go to the format bar and click the center button to center the title.
From the Insert menu, select Text Field. In the dialog box that appears, type Run Date: and click the Accept button. At that point, your mouse pointer will also have box representing the text field you just entered. Drag this box to the line where the date is. Use your mouse to arrange the items so that they look like this:
Still in the Page Header area, remove the column headings for Dept # and Dept Name. (Do this by selecting each item with the mouse and pressing the Delete key.)
In the first area labeled #1: DeptNbr – A (the one above Details), select the item there and delete it.
In the Details area, select the DeptNbr and DeptName fields, and drag them with the mouse to the first #1: DeptNbr -A area.
Once in the new area, select these two fields and click the Bold button. Insert a text field in this area (using the same technique as you did with "Run Date:") with the text "Department:". Make this text field bold as well. Arrange the fields so that they look like the following (you can resize a field by selecting it and dragging on the handles, just like resizing a control on a VB form):
Note that the default format for the department number contains a comma. We
don't want that. Right click the department number, and selectChange Format … from the context menu. The Format Number dialog appears, as shown below. Clear the "Thousands Separator" checkbox and click OK. (Note: You can also add or remove comma formatting by selecting the field and clicking the comma button on the formatting toolbar.)
Remove the commas from the formats for the employee number and the job
number. Resize the fields on the detail line and resize their corresponding column headings so that the column headings can be fully read and the field data is lined up beneath them. Use the screen shot below as a guide:
In the second area labeled #1: DeptNbr – A (the one below Details), you will
see a text item (denoted by X's) on the far left of the line. Delete that text item. In the same area, toward the right-hand side of the line, you will see a numeric item (denoted by "55.56"). This is the department subtotal. Resize this item to make it larger, and move it to the right so that it lines up with the detail annual salary field, as shown below:
Insert a text field with the text "Subtotal for Department" to the left of the subtotal
(not to the immediate left, because we are going to insert another field between these two). Click the Bold button to make it bold.
On the Insert menu, select Database Field … The "Insert Database Field" dialog box comes up (shown below). From it, select DeptNbrand drag the DeptNbr field to the subtotal line, between the text "Subtotal for Department" and the numeric subtotal.
The second area labeled #1: DeptNbr – A should now look like this:
In the area of the report labeled Grand Total, delete all fields except the first one
(a text item with the text "Grand Total") and the last one (a numeric item that is the grand total of the annual salaries). Resize and line up the remaining two items so that the report looks like the following:
In the Page Footer area of the report, there is a field for the page number. With your mouse, move this field up to the right-hand side of the page heading area. Add a text field that says "Page:" and place it adjacent to the page number, as in the following screen-shot:
That's just about it. Click the Preview tab to check out the finished product. Due to the fact this was set up to print in landscape orientation, a few screen shots are presented to show the final report. Below is the left-hand side of the report:
Scrolling to the right, we see the following:
Scrolling down, we see the following:
At this point, if you want to do any further tweaking, you can click the Design tab and do so. You can also print the report at this time. Before exiting Crystal Reports, save the report in the same directory as your VB project, under the name SALDEPT.RPT (Crystal Reports automatically appends the .RPT extension). The real objective is to be able to print the report from the VB program. The how-to for that is coming up shortly, but first, there is another report to create. The second report is similar to this one, except that it will be sorted and subtotaled by job, not department. Fortunately, this second report does not have to be built from scratch. We can modify the first report to create the second report. To create the second report, follow these steps: 1. Copy the SALDEPT.RPT file and name the new file SALJOB.RPT. 2. Double-click SALJOB.RPT to open it in Crystal Reports. 3. Dismiss the registration screen by clicking Cancel. 4. Click the Design tab.
5. From the Report menu, select Report Title ... The Edit Report Title dialog box appears. Change the title to Annual Salary Expenses by Joband click the Accept button.
6. From the Report menu, select Change Group Expert. The Edit Group
Section dialog box appears. There should only be one item in the listbox, reading Group #1: DeptMast.DeptNbr – A. Select that item and click OK. Another Edit Group Section dialog box appears. The first combo box should
have "DeptMast.DeptNbr" selected. Click the drop down arrow and select JobMast.JobNbr and click OK.
7. In the Page header area, right-click the title, select Edit Text Field, change the word
"Department" to "Job" and click Accept. 8. The next objective is to switch the department fields in the first #1: JobNbr –
A area with the job fields in the Details area. To do this, perform the following steps:
a. Move the job number and job title fields from the Details line to an open area of the #1: JobNbr – A line.b Move the department number and department name fields from the first #1:
JobNbr – A line to the space formerly occupied by the job fields in the Details line. Resize the department name field so that it fits.
c. In the first #1: JobNbr – A line, edit the text of the "Department:" text field so that it says "Job:". Move the job number and job title fields close to the "Job:" text field and make these two items bold.
d. In the Details line, remove the bold formatting from the department number and department name fields.
e. Back in the Page header area, change the text of the "Job #" and "Job Title" column headings to "Dept #" and "Dept Name", respectively.
f. Make cosmetic adjustments as necessary.
9. In the second #1: JobNbr – A area, delete the department number field. Replace it with the job number field. To do this, go to the Insertmenu, select Database field, and select JobNbr. Resize the JobNbr field so that its small enough to fit where the department number used to be. Remove the commas from the formatting and make it bold. Edit the text field "Subtotal for Department", changing the word "Department" to "Job". Make cosmetic adjustments as necessary.
10. That's just about it. Click the Preview tab to view the fruits of your labor. If you
wish, go back and make any adjustments you deem necessary and print out the report. When you are done, save the report and exit Crystal Reports.
How to Print a Crystal Report from a VB Program First, you must add the Crystal Report control to your VB toolbox. To do so, go to Project Components and check Crystal Report Control 4.6 from the Components dialog box, as shown below:
The Crystal Reports control will then appear in the VB toolbox (it is circled in the screen shot below):
The form used in the demo application associated with this topic, named frmCRDemo, is shown below. The form contains two sets of option button control arrays (each contain two option buttons indexed 0 and 1). The first option button control array, named optReport, appears in the frame labelled "Select Report"; the second option button control array, named optDestination, appears in the frame labelled "Select Destination". The Crystal Report control was renamed rptAnnSalExp. No other properties of the Crystal Report control were set at design-time; all necessary properties are set in code at run-time. Only one Crystal Report control is necessary to print any number of reports off of a form. The Crystal Report control is not visible at run-time. Finally, there are two command buttons on the form; the OK button and the Exit button. The OK button runs the report based on the options selected by the user in the frames above; the Exit button ends the application.
Following is the code for both the frmCRDemo form and a standard module called modCommon. The Crystal Reports-related code will be explained following the code listings. Code for frmDemo: Option Explicit '------------------------------------------------------------------------Private Sub Form_Load()'------------------------------------------------------------------------CenterForm MeEnd Sub '------------------------------------------------------------------------Private Sub cmdOK_Click()'------------------------------------------------------------------------ On Error GoTo cmdOK_Click_ErrorDim strReportName As StringDim intReportDestination As IntegerIf optReport(0).Value = True Then
strReportName = "SALDEPT.RPT"ElsestrReportName = "SALJOB.RPT"End IfIf optDestination(0).Value = True ThenintReportDestination = crptToWindowElseintReportDestination = crptToPrinterEnd IfWith rptAnnSalExp.ReportFileName = GetAppPath() & strReportName.DataFiles(0) = GetAppPath() & "EMPLOYEE.MDB".Destination = intReportDestination.Action = 1 ' 1 = "Run the Report"End WithExit Sub cmdOK_Click_Error: MsgBox "The following error has occurred:" & vbNewLine _& Err.Number & " - " & Err.Description, _vbCritical, _"cmdOK_Click" End Sub '------------------------------------------------------------------------Private Sub cmdExit_Click()'------------------------------------------------------------------------Unload MeEnd Sub Code for modCommon: Option Explicit '------------------------------------------------------------------------Public Sub CenterForm(pobjForm As Form)
'------------------------------------------------------------------------ With pobjForm.Top = (Screen.Height - .Height) / 2.Left = (Screen.Width - .Width) / 2End With End Sub '------------------------------------------------------------------------Public Function GetAppPath() As String'------------------------------------------------------------------------ GetAppPath = IIf(Right$(App.Path, 1) = "\", App.Path, App.Path & "\") End Function Note that in the cmdOK_Click event procedure, the variable strReportName is set to "SALDEPT.RPT" or "SALJOB.RPT", depending on whichoptReport button was clicked. The variable intDestination is set to either crptToWindow or crptToPrinter, both built-in Crystal Reports constants, depending on which optDestination button was clicked (if you choose to send the report to a window, a "print preview" type screen appears allowing the user to view the report on screen; if you choose to send the report to the printer, it will be sent directly to the default printer with no preview). The sample app references the following properties of the Crystal Report control:
ReportFileName refers to the name of the report definition that you saved in Crystal Reports. Alternatively, this property can also be set at design-time.
DataFiles is a Crystal Reports property array specifying the database file(s) to be used as the basis for the report. The file you specify here will override the database file used when the report was created in Crystal Reports. Although different MDB files can be used (at Crystal Reports design-time vs. VB run-time), they still must contain the same table and/or query names, with the same structure, that were used to build the report.
Destination refers to where you want to direct the output of the report. In code, you can use a predefined constant (like crptToPrinter) or its numeric equivalent as used in the sample code. This property can also be set at design-time. The sample app uses either crptToWindow(numeric value of 0) or crptToPrinter (numeric value of 1).
Action is the property that triggers the running of the report. It must be set to 1 to
run. Download the project files for the sample application here.
This Crystal Report tutorial was written by TheVBProgramer.
Database Access with RDO (Remote Data Objects)Level:
Introduction to ODBC and RDO
Open Database Connectivity (ODBC) provides a set of application programming interface (API)
functions which make it easier for a developer to connect to a wide range of database formats.
ODBC gives the developer a method of designing programs that are not specific to database format
whether you are using Oracle, SQL Server, Access, or others. ODBC drivers are DLLs that contain
the functions that let you connect to various databases. Each ODBC driver is separate for each
database format. ODBC drivers take the code from a program and convert the functions to the
specific database format being used. RDO (Remote Data Objects) is a thin layer of code that acts
as an ODBC "wrapper", enabling the developer to invoke ODBC functionality using familiar object
method and property syntax.
The "Alphabet Soup" of Database Access
Prior to VB6 and the introduction of ADO (ActiveX Data Objects), VB programmers would generally
use DAO (Data Access Objects) to interact with local databases such as MS Access and use RDO
(Remote Data Objects) to interact with client/server databases such as Oracle and SQL Server. The
concept behind ADO was Universal Data Access (UDA), where one database access method could
be used for any data source; it was designed to replace both DAO and RDO. DAO remains a viable
technology for interacting with MS Access databases as it is faster than ADO for that purpose;
however, ADO is more flexible – using ADO, one could develop a prototype database application
using MS Access in the back-end, and with a "flick of the wrist" (i.e., with very little coding changes)
"upsize" that same application to use Oracle or SQL Server. As far as RDO is concerned, no new
versions of it have been developed beyond the version that shipped with VB6, and there are no
future plans for it.
In the VB4 and VB5 worlds, RDO was the main method used to interact with client/server
databases. RDO works perfectly fine with VB6, so when folks migrated their VB5 applications over
to VB6, little or no coding changes were required. However, ADO is the preferred method of
database access for new VB6 applications .
About this Tutorial
This tutorial presents three small sample applications using RDO. All three applications use a local
MS Access database, as this is suitable for illustrative purposes; in actual practice, RDO would not
be a suitable choice for interacting with a local MS Access database. You could approach these
sample applications "as if" the local Access database was an Oracle or SQL Server database sitting
on a server somewhere – the coding techniques are the same.
The first sample application introduces the RDO Data Control (RDODC) which demonstrates a
"quick and dirty" way to connect to a remote database. The second and third applications use RDO
code: the second allows navigation and searching of a database table; the third allows navigation
and updating on a database table. All three connect to an ODBC Data Source, which must be set
up through the Windows Control Panel. How to do this is described below.
Setting Up an ODBC Data Source
Follow the steps below to set up an ODBC Data Source (this process is also called "setting up a
DSN", where "DSN" stands for "Data Source Name"). These steps assume Windows 2000 for the
operating system. On other versions of Windows, some steps may vary slightly.
Via Windows Control Panel, double-click on Administrative Tools, then Data Sources (ODBC).
The ODBC Data Source Administrator screen is displayed, as shown below. Click on the System
DSN tab.
Click the Add button. The Create New Data Source dialog box will appear. Select Microsoft
Access Driver (*.mdb) from the list and click the Finish button.
The ODBC Microsoft Access Setup dialog box will appear. For Data Source Name, type Biblio.
If desired, you can type an entry for Description, but this is not required.
Click the Select button. The Select Database dialog box appears. On a default installation of VB6
or Visual Studio 6, the BIBLIO.MDB sample database should reside in the folder C:\Program Files\
Microsoft Visual Studio\VB98. Navigate to that folder, select BIBLIO.MDB from the file list, and
click OK.
Note: If VB was installed in a different location on your system, navigate to the appropriate folder. If you
do not have the BIBLIO.MDB sample database file on your system at all, you can download
it here. In that case, copy the file to the folder of your choice, and navigate to that folder to
select the database for this step.
When you are returned to the ODBC Microsoft Access Setup screen, the database you selected
should be reflected as shown below. Click OK to dismiss this screen.
When you are returned to the ODBC Data Source Administrator screen, the new DSN should
appear as shown below. Click OK to dismiss this screen.
At this point, the Biblio database is ready to be used with RDO in the sample application.
Sample Application 1: Using the RDO Data Control (RDODC)
To build the first sample application, follow the steps below.
Start a new VB project, and from the Components dialog box (invoked from the Project ->
Components menu), select Microsoft RemoteData Control 6.0 (SP3) as shown below and
click OK.
The RDO Data Control should appear in your toolbox as shown below:
Put a remote data control (RDC) on your form, and set the properties as follows:
Property ValueName rdoBiblioDataSourceName BiblioSQL select * from authors
Now put three text boxes on the form, and set their Name, DataSource, and DataField properties
as follows:
Name DataSource DataFieldtxtAuthor rdoBiblio AuthortxtAuID rdoBiblio Au_IDtxtYearBorn rdoBiblio Year Born
Save and run the program. Notice how it works just like the other data control.
Now change the SQL property of the data control to select * from authors order by author and
run the program again. Notice the difference.
Change the SQL property back to what it was and add three command buttons to the form, and set
their Name and Caption properties as follows:
Name CaptioncmdNameOrder Order by NamecmdYearOrder Order by YearcmdIDOrder Order by ID
Put the following code in the cmdNameOrder_Click event:
rdoBiblio.SQL = "select * from authors order by author"
rdoBiblio.Refresh
Put the following code in the cmdYearOrder_Click event:
rdoBiblio.SQL = "select * from authors order by [year born]"
rdoBiblio.Refresh
Put the following code in the cmdIDOrder_Click event:
rdoBiblio.SQL = "select * from authors order by au_id"
rdoBiblio.Refresh
Save and run the program and see what happens when you click the buttons.
A screen-shot of the sample app at run-time is shown below:
Download the project files for this sample application here.
Sample Applications 2 and 3: Using RDO Code
Sample applications 2 and 3 use a database called PROPERTY.MDB and can be
downloaded here.
The Property database contains just one table called "Property". The columns of this table are
defined as follows:
Column Name Data TypePROPNO Number (Long Integer) A number that uniquely identifies the property in the table. Should be treated
as the Primary Key (although it is not defined as such in the sample database).
EMPNO Number (Long Integer) A number that identifies the real estate agent selling the property. In a real system, this would be the foreign key to the employee number in an Employee table (such a table is not present in the sample database).
ADDRESS Text (20) The street address of the property.CITY Text (15) The city where the property is located.STATE Text (2) The state where the property is located (2-characterZIP Text (5) The zip code where the property is located.NEIGHBORHOOD Text (15) The descriptive name of the neighborhood in which the property is located.HOME_AGE Number (Long Integer) Age in years of the home. (A better table design choice would be to have this
field be the date in which the property was built and have the application compute the age based on the current date.)
BEDS Number (Long Integer) Number of bedrooms in the property.BATHS Number (Single) Number of bathrooms in the property (allows for a decimal value such as 2.5,
indicating 2 ½ bathrooms – i.e. 2 full bathrooms and 1 "powder room").FOOTAGE Number (Long Integer) The footage of the property.ASKING Number (Long Integer) Asking price of the property in whole dollars.BID Number (Long Integer) Bid amount of the potential buyer in whole dollars.SALEPRICE Number (Long Integer) Sale price (amount the property actually sold for) in whole dollars.
Before coding or running sample application 2 or 3, you must set up an ODBC data source as was
done for the previous sample application.
After downloading the file, move it to the folder of your choice. Then follow the exact same steps as
before to set up the DSN, with these two exceptions:
(1) On the ODBC Microsoft Access Setup dialog box, type PropDB for the Data Source Name.
(2) In the Select Database dialog box, navigate to the location where you have placed
the PROPERTY.MDB file.
Sample Application 2
To build Sample Application 2, start a new VB project and perform the following steps.
From the Project -> References menu, check Microsoft Remote Data Object 2.0 and click OK.
This project uses the StatusBar control, so include the Microsoft Windows Common Controls
6.0 (SP6) from the Components dialog box, accessed from the Project -> Components menu.
Create the form shown below. The names of the text boxes in the top frame are shown in the form. Set the Enabled property of the frame to False, which will automatically disable all of the textboxes within it, which is desired because this application does not allow updating of the data. The settings for the other controls are given below.
The navigation buttons have the following properties:
Name CaptioncmdMoveFirst <<cmdMovePrevious <cmdMoveNext >cmdMoveLast >>
The text box in the middle of the form has the following properties:
Name txtCurrentQueryMultiLine TrueLocked True
The command buttons have the following properties:
Name CaptioncmdAllData Reload All RecordscmdGetData Run Query Now
In the "Select Criteria" frame:
The check boxes are an array:
Name CaptionchkCriteria(0) EmpNochkCriteria(1) CitychkCriteria(2) State
The labels are also an array:
Name Caption EnabledlblCriteria(0) = FalselblCriteria(1) Like FalselblCriteria(2) Like False
The textboxes are also an array:
Name Caption EnabledtxtCriteria(0) EmpNo FalsetxtCriteria(1) City FalsetxtCriteria(2) State False
Place the StatusBar on the form and set its Style property to 1 – sbrSimple.
2. Code the General Declarations section as shown below. Here, two RDO
objects, rdoConnection and rdoResultset, are defined at the form level.
The rdoConnection object represents an open connection to a remote data source and a specific
database on that data source, or an allocated but as yet unconnected object, which can be used
to subsequently establish a connection.
The rdoResultset object represents the rows that result from running a query,
Option Explicit
Dim mobjRDOConn As rdoConnection
Dim mobjRDORst As rdoResultset
Dim mstrSQL As String
3. Code the Form_Load event. Here, the OpenConnection methods is introduced.
OpenConnection is method of an existing rdoEnvironment object. An rdoEnvironment object defines
a logical set of connections and transaction scope for a particular user
name. rdoEnvironments(0) is a member of the rdoEnvironments collection and is created
automatically when you include RDO in your program. TherdoEnvironments collection is one
of several collections of the base RDO object, rdoEngine. The rdoEngine object is created
automatically when you include RDO in your program.
The syntax for the OpenConnection method is:
Set connection = environment.OpenConnection(dsName[, prompt[, readonly[, connect[, options]]]])
In this sample program, "PropDB" is used for the dsName argument, as this was the Data Source
Name given during the ODBC setup. The prompt argument controls whether or not the user is
prompted for their UserID and password via the "ODBC Data Sources" dialog box. Provided that
enough information is given within the other arguments of the OpenConnection method, the
constant rdDriverNoPrompt will suppress the prompt. The third argument, readonly is a
Boolean indicating whether or not the connection should be open for read/write access. The
sample program omits this argument, thus defaulting to False, meaning the connection is open
for read/write access. The connect argument is a string expression used to pass arguments to
the ODBC driver manager for opening the database. In the case of the sample application, we
are passing a user id of "admin" and a blank password in the connection string, which are the
defaults for an unsecured MS Access database.
'------------------------------------------------------------------------
-----
Private Sub Form_Load()
'------------------------------------------------------------------------
-----
'set up the form and connect to the data source
On Error GoTo LocalError
'center the form:
Me.Top = (Screen.Height - Me.Height) / 2
Me.Left = (Screen.Width - Me.Width) / 2
' Connect to the Property database:
Set mobjRDOConn = rdoEngine.rdoEnvironments(0).OpenConnection _
("PropDB", rdDriverNoPrompt, , "UID=admin;PWD=")
Call cmdAllData_Click
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
4. Code the cmdAllData_Click event, which sets or resets the Resultset object with a query to display all
the rows in the table. Here, the OpenResultset method is introduced.
The OpenResultset method causes an rdoResultset object to be created. The syntax is:
Set variable = connection.OpenResultset(name [,type [,locktype [,option]]])
The name argument is a string that specifies the source of the rows for the new rdoResultset. This
argument can specify the name of an rdoTable object, the name of anrdoQuery, or an SQL
statement that might return rows. In the case of the sample application, it is the SQL statement
"select * from property". The type argument specifies which type of cursor to create
(using rdOpenKeyset allows the use of the "Move" methods (MoveFirst, MoveNext, etc.), so
that option was coded here). Thelocktype argument specifies the concurrency option that
controls how other users are granted or refused access to the data being updated. A value
ofrdConcurRowVer specifies optimistic concurrency based on row ID.
'------------------------------------------------------------------------
-----
Private Sub cmdAllData_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
Dim lngX As Long
'select or reload the data to be displayed:
mstrSQL = "select * from property"
Set mobjRDORst = mobjRDOConn.OpenResultset(mstrSQL, rdOpenKeyset,
rdConcurRowVer)
txtCurrentQuery.Text = mstrSQL
'The number of records will be displayed in the status bar,
'but the RowCount property is not correct until the last
'record has been accessed (so do a MoveLast followed by a MoveFirst).
mobjRDORst.MoveLast
mobjRDORst.MoveFirst
'load data into the text boxes
Call DataLoad
' reset the state of the search criteria controls
For lngX = 0 To 2
chkCriteria(lngX).Value = vbUnchecked
Next
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
5. Create the user-defined subprocedure DataLoad. This subprocedure gets the data from the resultset
and puts each field into a text box. Data from the resultset is accessed via
the rdoColumns collection.
The rdoColumns collection in RDO is similar to the Fields collection in DAO. A field can be referenced
with or without specifying rdoColumns, either by the field name in quotes or by its ordinal
position in the resultset. The field can also be referenced with the bang (!) operator. All of the
following would be valid ways of referencing the field "propno":
mobjRDORst.rdoColumns("propno")
mobjRDORst ("propno")
mobjRDORst.rdoColumns(0)
mobjRDORst(0)
mobjRDORst!propno
'------------------------------------------------------------------------
-----
Private Sub DataLoad()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
'copy the data from the resultset to the text boxes:
txtPropNo.Text = mobjRDORst.rdoColumns("propno")
txtEmpNo.Text = mobjRDORst.rdoColumns("empno")
txtAddress.Text = mobjRDORst.rdoColumns("address")
txtCity.Text = mobjRDORst.rdoColumns("city")
txtState.Text = mobjRDORst.rdoColumns("state")
txtZip.Text = mobjRDORst.rdoColumns("zip")
Call SetRecNum
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
6. Create the user-defined subprocedure SetRecNum. This sub displays the number of the current
record at the bottom of the screen. The AbsolutePosition, RowCount,
andPercentPosition properties of the Resultset are used here.
The AbsolutePosition property specifies the current row in a resultset.
The RowCount property specifies the number of rows in a resultset that have been accessed (the full
count is only accurate after a "MoveLast" has been executed).
The PercentPosition property indicates the location of the current resultset row based on a percentage.
'------------------------------------------------------------------------
-----
Private Sub SetRecNum()
'------------------------------------------------------------------------
-----
StatusBar1.SimpleText = "row " & mobjRDORst.AbsolutePosition _
& " of " & mobjRDORst.RowCount _
& ", " & Format(mobjRDORst.PercentPosition, "#0.00") _
& "% of ResultSet"
End Sub
7. Code the events for the navigation buttons as shown below, using the resultset "Move" methods to
move to the first, last, next, or previous record, respectively.
'-----------------------------------------------------------------------------
Private Sub cmdMoveFirst_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
mobjRDORst.MoveFirst
Call DataLoad
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
'------------------------------------------------------------------------
-----
Private Sub cmdMoveLast_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
mobjRDORst.MoveLast
Call DataLoad
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
'------------------------------------------------------------------------
-----
Private Sub cmdMoveNext_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
mobjRDORst.MoveNext
If mobjRDORst.EOF Then
Beep
mobjRDORst.MoveLast
End If
Call DataLoad
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
'------------------------------------------------------------------------
-----
Private Sub cmdMovePrevious_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
mobjRDORst.MovePrevious
If mobjRDORst.BOF Then
Beep
mobjRDORst.MoveFirst
End If
Call DataLoad
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
8. When one of the check boxes is clicked, the label and text box next to it should be enabled (or
disabled, if clicking the check box unchecks it). Note also that the cmdGetData button (the one
with the "Run Query Now" caption) should only be enabled if one of the checkboxes is checked.
'------------------------------------------------------------------------
-----
Private Sub chkCriteria_Click(Index As Integer)
'------------------------------------------------------------------------
-----
' disable the 'Run Query Now' button
cmdGetData.Enabled = False
'when the user clicks on a check box, enable the label and text
'box that go with it.
If chkCriteria(Index).Value = vbChecked Then
txtCriteria(Index).Enabled = True
lblCriteria(Index).Enabled = True
txtCriteria(Index).SetFocus
txtCriteria(Index).SelStart = 0
txtCriteria(Index).SelLength = Len(txtCriteria(Index).Text)
' enable the 'Run Query Now' button only if a box is checked.
cmdGetData.Enabled = True
Else
txtCriteria(Index).Enabled = False
lblCriteria(Index).Enabled = False
End If
End Sub
9. After the user has selected which fields to use and entered values in the text boxes, they click the
cmdGetData button to create a new Resultset with new data. Note that if the user selects
(checks) a field, but does not enter search criteria in the corresponding textbox, an error
message is generated and the query is not run.
'------------------------------------------------------------------------
-----
Private Sub cmdGetData_Click()
'------------------------------------------------------------------------
-----
'run the query that the user has created
On Error GoTo LocalError
Dim blnFirstOne As Boolean
Dim i As Integer
blnFirstOne = True
mstrSQL = "select * from property where "
If chkCriteria(0).Value = vbChecked Then
If (txtCriteria(0).Text = "") Or (Not IsNumeric(txtCriteria(0).Text))
Then
MsgBox "Employee number is missing or non-numeric. Query not run.", _
vbExclamation, _
"RDO Example"
Exit Sub
End If
blnFirstOne = False
mstrSQL = mstrSQL & "empno = " & txtCriteria(0).Text
End If
If chkCriteria(1).Value = vbChecked Then
If txtCriteria(1).Text = "" Then
MsgBox "City criteria is missing. Query not run.", _
vbExclamation, _
"RDO Example"
Exit Sub
End If
If blnFirstOne = False Then
mstrSQL = mstrSQL & " and"
End If
blnFirstOne = False
mstrSQL = mstrSQL & " city like '" & txtCriteria(1).Text & "'"
End If
If chkCriteria(2).Value = vbChecked Then
If txtCriteria(2).Text = "" Then
MsgBox "State criteria is missing. Query not run.", _
vbExclamation, _
"RDO Example"
Exit Sub
End If
If blnFirstOne = False Then
mstrSQL = mstrSQL & " and"
End If
blnFirstOne = False
mstrSQL = mstrSQL & " state like '" & txtCriteria(2).Text & "'"
End If
'create a new resultset using the new query
Set mobjRDORst = mobjRDOConn.OpenResultset(mstrSQL, rdOpenKeyset,
rdConcurRowVer)
txtCurrentQuery.Text = mstrSQL
'make sure that the query did not return 0 rows:
If mobjRDORst.RowCount = 0 Then
MsgBox "Your query returned 0 rows!"
MsgBox "The query you generated was:" & vbCrLf & mstrSQL
'reload the form with all the records
cmdAllData_Click
Else
'move to last row to set the RowCount property
mobjRDORst.MoveLast
mobjRDORst.MoveFirst
'load data into the text boxes
Call DataLoad
MsgBox "Your query returned " & mobjRDORst.RowCount & " records."
End If
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
10. Save and run. Note: When entering the "Like" criteria for City and/or State, you can use the wildcard
character % to represent any number of characters and the wildcard character _ (underscore)
the represent a single character. For example, entering "M%" for the City criteria would return all
rows where the city field begins with the letter "M".
Download the project files for this sample application here.
Sample Application 3
Sample Application 3 demonstrates how to add, update, and delete records with RDO.
When the application is first run, the user is prompted to enter a minimum asking price to possibly
limit the number of records they want to work with (i.e., "I only want to work with properties that are
selling for $200,000 or more). If the user wants to work with all properties, they would simply accept
the default of 0 from the prompt. If the user clicks the Cancel button, the application will end.
Once the user has entered the minimum asking price, the main screen of the application is
displayed. Initially, the screen is in "browse" mode, where the user can use the navigation buttons
to move to the first, previous, next or last record. The data cannot be edited in this mode. If they
want to initiate an add or an update, delete a record, or exit the application, they may do so via the
appropriate button. Saving or cancelling is not applicable in this mode, so those buttons are
disabled.
If the user clicks the Add button, the fields on the screen are enabled and cleared, and the user can
enter the information for the new property. All buttons except Save and Cancel are now disabled.
After the user has made entries in the fields, he or she would click Save to add the new record to
the database table, or, if they changed their mind, would click Cancel to discard the new record. In
either case (clicking Save or Cancel) the user is returned to browse mode. When Save is clicked,
the application validates the entries and will only save the record if all fields pass edit (otherwise, a
message will appear indicating the problem entry and focus will be set to the problem field).
If the user clicks the Update button, the fields on the screen are enabled and the user can modify
any or all of the fields (except for the Property Number, which is the primary key of the table). All
buttons except Save and Cancel are now disabled. After the user has made modifications in the
desired fields, he or she would click Save to update the record to the database table, or, if they
changed their mind, would click Cancel to discard the changes. In either case (clicking Save or
Cancel) the user is returned to browse mode. When Save is clicked, the application validates the
entries and will only save the record if all fields pass edit (otherwise, a message will appear
indicating the problem entry and focus will be set to the problem field).
If the user clicks the Delete button, the user is asked to confirm that they want to delete the current
record. If they respond Yes, the record is deleted from the database table, and the main screen
shows the next record in the table.
To build Sample Application 3, start a new VB project and perform the following steps.
From the Project -> References menu, check Microsoft Remote Data Object 2.0 and click OK.
This project uses the StatusBar control, so include the Microsoft Windows Common Controls
6.0 (SP6) from the Components dialog box, accessed from the Project -> Components menu.
Check this item and click OK.
Create the form shown below. The settings for the various controls are given below.
There are nine textboxes in the main frame of the form. The names and MaxLength settings for
these are given below:
Name PropertiestxtPropNo MaxLength: 5txtEmpNo MaxLength: 4txtAddress MaxLength: 20txtCity MaxLength: 15txtState MaxLength: 2txtZip MaxLength: 5txtBeds MaxLength: 1txtBaths MaxLength: 3 (allows fractional amount, like 1.5)txtAsking MaxLength: 0 (not specified)
Set up the Command Buttons as follows:
Name CaptioncmdMoveFirst <<cmdMovePrevious <cmdMoveNext >cmdMoveLast >>cmdAdd Add
cmdUpdate UpdatecmdDelete DeletecmdSave SavecmdCancel CancelcmdExit Exit
All controls on your form should have their TabIndex property set such that the tabbing order is
correct.
Add a Module to the project, name it modCommon, and enter the code shown below. The code
contains procedures described as follows:
CenterForm Sub to center a form on the screenValidKey Function to validate a keystroke for use in the KeyPress event of a textboxConvertUpper Function to convert an alphabetic character entered in a textbox to uppercase, used in the KeyPress event of a textboxSelectTextBoxText Sub to highlight the text of a textbox when it receives focus. Used in the GotFocus event of a textbox.TabToNextTextBox Sub to "autotab" from one textbox to another when maximum number of characters that can be entered into the first textbox has
been reached.UnFormatNumber Function to strip out non-numeric characters (dollar signs, commas, etc.) from a formatted number.
Option Explicit
Public Const gstrNUMERIC_DIGITS As String = "0123456789"
Public Const gstrUPPER_ALPHA_PLUS As String =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ,'-"
Public gblnPopulating As Boolean
'------------------------------------------------------------------------
Public Sub CenterForm(pobjForm As Form)
'------------------------------------------------------------------------
With pobjForm
.Top = (Screen.Height - .Height) / 2
.Left = (Screen.Width - .Width) / 2
End With
End Sub
'------------------------------------------------------------------------
Public Function ValidKey(pintKeyValue As Integer, _
pstrSearchString As String) As Integer
'------------------------------------------------------------------------
' Common function to filter out keyboard characters passed to this
' function from KeyPress events.
'
' Typical call:
' KeyAscii = ValidKey(KeyAscii, gstrNUMERIC_DIGITS)
'
If pintKeyValue < 32 _
Or InStr(pstrSearchString, Chr$(pintKeyValue)) > 0 Then
'Do nothing - i.e., accept the control character or any key
' in the search string passed to this function ...
Else
'cancel (do not accept) any other key ...
pintKeyValue = 0
End If
ValidKey = pintKeyValue
End Function
'------------------------------------------------------------------------
Public Function ConvertUpper(pintKeyValue As Integer) As Integer
'------------------------------------------------------------------------
' Common function to force alphabetic keyboard characters to uppercase
' when called from the KeyPress event.
' Typical call:
' KeyAscii = ConvertUpper(KeyAscii)
'
If Chr$(pintKeyValue) >= "a" And Chr$(pintKeyValue) <= "z" Then
pintKeyValue = pintKeyValue - 32
End If
ConvertUpper = pintKeyValue
End Function
'------------------------------------------------------------------------
-----
Public Sub SelectTextBoxText(pobjTextbox As TextBox)
'------------------------------------------------------------------------
-----
With pobjTextbox
.SelStart = 0
.SelLength = Len(.Text)
End With
End Sub
'------------------------------------------------------------------------
-----
Public Sub TabToNextTextBox(pobjTextBox1 As TextBox, pobjTextBox2 As
TextBox)
'------------------------------------------------------------------------
-----
If gblnPopulating Then Exit Sub
If pobjTextBox2.Enabled = False Then Exit Sub
If Len(pobjTextBox1.Text) = pobjTextBox1.MaxLength Then
pobjTextBox2.SetFocus
End If
End Sub
'------------------------------------------------------------------------
-----
Public Function UnFormatNumber(pstrNumberIn As String) As String
'------------------------------------------------------------------------
-----
Dim lngX As Long
Dim strCurrChar As String
Dim strNumberOut As String
strNumberOut = ""
For lngX = 1 To Len(pstrNumberIn)
strCurrChar = Mid$(pstrNumberIn, lngX, 1)
If InStr("0123456789.", strCurrChar) > 0 Then
strNumberOut = strNumberOut & strCurrChar
End If
Next
UnFormatNumber = strNumberOut
End Function
Code the General Declarations section as shown below. Here, as in the previous sample
application, two RDO objects, rdoConnection and rdoResultset, are defined at the form level, as
are some other form-level variables that will be needed.
Option Explicit
Private mobjRDOConn As rdoConnection
Private mobjRDORst As rdoResultset
Private mstrSQL As String
Private mdblMinAsking As Double
Private mblnUpdatePending As Boolean
Private mstrUpdateType As String
Private mavntUSStates As Variant
Code the Form_Load event as shown below. In it, a programmer-defined Sub
named GetMinimumAsking is called (that routine is the one that displays the initial prompt to the
user to enter the minimum asking price of the properties they want to work with). Then, the variant
array mavntUSStates is loaded with the 50 US state abbreviations, needed for validating the state
input by the user. This is followed by a call to the CenterForm sub. Then, the
RDO OpenConnection is invoked so that we can use the Property database in the application. This
is followed by a call to the programmer-defined Sub GetPropertyData (which runs the query to
create the recordset that will be used to browse the Property table records), followed by a call to the
programmer-defined Sub SetFormState (which enables and disables controls at the appropriate
time).
'------------------------------------------------------------------------
-----
Private Sub Form_Load()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
' obtain the minimum asking price for the properties to be worked with
GetMinimumAsking
' load the array of states to be used for validation
mavntUSStates = Array("AK", "AL", "AR", "AZ", "CA", "CO", "CT", "DC", _
"DE", "FL", "GA", "HI", "IA", "ID", "IL", "IN", _
"KS", "KY", "LA", "MA", "MD", "ME", "MI", "MN", _
"MO", "MS", "MT", "NC", "ND", "NE", "NH", "NJ", _
"NM", "NV", "NY", "OH", "OK", "OR", "PA", "RI", _
"SC", "SD", "TN", "TX", "UT", "VA", "VT", "WA", _
"WI", "WV", "WY")
'center the form:
CenterForm Me
' Connect to the Property database:
Set mobjRDOConn = rdoEngine.rdoEnvironments(0).OpenConnection _
("PropDB", rdDriverNoPrompt, , "UID=admin;PWD=")
Call GetPropertyData
SetFormState False
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
Code the GetMinimumAsking Sub, which uses the InputBox function to prompt to the user to enter
the minimum asking price of the properties they want to work with. The resulting value is then
stored in the form-level variable mdblMinAsking.
'------------------------------------------------------------------------
-----
Private Sub GetMinimumAsking()
'------------------------------------------------------------------------
-----
Dim strInputBoxPrompt As String
Dim strAsking As String
strInputBoxPrompt = "Enter the minimum asking price (for example, 200000)
" _
& "for the properties that you want to work with this session." _
& vbNewLine _
& "To work with ALL properties, leave the default of zero."
strAsking = InputBox(strInputBoxPrompt, "Minimum Asking Price", "0")
If strAsking = "" Then
' user clicked Cancel button on the input box, so end the app
End
End If
mdblMinAsking = Val(strAsking)
End Sub
Code the GetPropertyData Sub, which builds the SQL to get the property records meeting the
minimum asking price condition. The OpenResultset method is then invoked to execute the SQL
and return the resultset. This is done in a loop in case the resultset does not return any records due
to the fact no records in the table met the asking price condition. In that situation, the user is given
the opportunity to specify a different asking price value. Assuming we get to a point where we get
records back, we do a MoveLast(to ensure a correct record count) followed by a MoveFirst to
position to the first record in the resultset. Following this, the programmer-defined
Sub PopulateFormFields is called (which displays the fields from the current record in their
corresponding textboxes on the form).
'------------------------------------------------------------------------
-----
Private Sub GetPropertyData()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
Dim blnGotData As Boolean
blnGotData = False
Do
'select or reload the data to be displayed:
mstrSQL = "select propno" _
& " , empno" _
& " , address" _
& " , city" _
& " , state" _
& " , zip" _
& " , beds" _
& " , baths" _
& " , asking" _
& " from property" _
& " where asking >= " & mdblMinAsking _
& " order by propno"
Set mobjRDORst = mobjRDOConn.OpenResultset(mstrSQL, _
rdOpenKeyset, _
rdConcurRowVer)
If mobjRDORst.EOF Then
If MsgBox("There are no properties with an asking price >= " _
& Format$(mdblMinAsking, "Currency") _
& ". Do you want to try again with a different value?", _
vbYesNo + vbQuestion, _
"Asking Price") _
= vbYes Then
GetMinimumAsking
Else
End
End If
Else
blnGotData = True
End If
Loop Until blnGotData
'The number of records will be displayed in the status bar,
'but the RowCount property is not correct until the last
'record has been accessed (so do a MoveLast followed by a MoveFirst).
mobjRDORst.MoveLast
mobjRDORst.MoveFirst
'load data into the text boxes
Call PopulateFormFields
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
Code the PopulateFormFields Sub, which assigns the fields from the current record to their
corresponding textboxes on the form. Note that the gblnPopulating Boolean variable is set to True
prior to the assignments and set to False after the assignments. This value is used to control
whether or not certain code executes in the event procedures for some of these textboxes. The
Sub SetRecNum is then called.
'------------------------------------------------------------------------
-----
Private Sub PopulateFormFields()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
gblnPopulating = True
'copy the data from the resultset to the text boxes:
txtPropNo.Text = mobjRDORst.rdoColumns("propno")
txtEmpNo.Text = mobjRDORst.rdoColumns("empno")
txtAddress.Text = mobjRDORst.rdoColumns("address")
txtCity.Text = mobjRDORst.rdoColumns("city")
txtState.Text = mobjRDORst.rdoColumns("state")
txtZip.Text = mobjRDORst.rdoColumns("zip")
txtBeds.Text = mobjRDORst.rdoColumns("beds")
txtBaths.Text = mobjRDORst.rdoColumns("baths")
txtAsking.Text = Format$(mobjRDORst.rdoColumns("asking"), "Currency")
gblnPopulating = False
Call SetRecNum
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
Code the SetRecNum Sub. This sub is identical to the one used in Sample Application 2. It displays
the number of the current record at the bottom of the screen using
theAbsolutePosition, RowCount, and PercentPosition properties of the Resultset object.
'------------------------------------------------------------------------
-----
Private Sub SetRecNum()
'------------------------------------------------------------------------
-----
StatusBar1.SimpleText = "row " & mobjRDORst.AbsolutePosition _
& " of " & mobjRDORst.RowCount _
& ", " & Format(mobjRDORst.PercentPosition, "#0.00") _
& "% of ResultSet"
End Sub
Code the SetFormState Sub, which takes in a Boolean argument used to set the Enabled property
of the controls on the form. Based on whether the value True or False is passed to this sub, this sub
ensures that the textboxes are enabled for adds and updates and disabled for browsing; it also
ensures that the various command buttons are enabled or disabled at the appropriate time. This
Sub also sets the form-level Boolean variable mblnUpdatePending.
'------------------------------------------------------------------------
-----
Private Sub SetFormState(pblnEnabled As Boolean)
'------------------------------------------------------------------------
-----
txtPropNo.Enabled = pblnEnabled
txtEmpNo.Enabled = pblnEnabled
txtAddress.Enabled = pblnEnabled
txtCity.Enabled = pblnEnabled
txtState.Enabled = pblnEnabled
txtZip.Enabled = pblnEnabled
txtBeds.Enabled = pblnEnabled
txtBaths.Enabled = pblnEnabled
txtAsking.Enabled = pblnEnabled
cmdSave.Enabled = pblnEnabled
cmdCancel.Enabled = pblnEnabled
cmdAdd.Enabled = Not pblnEnabled
cmdUpdate.Enabled = Not pblnEnabled
cmdDelete.Enabled = Not pblnEnabled
cmdExit.Enabled = Not pblnEnabled
cmdMoveFirst.Enabled = Not pblnEnabled
cmdMoveNext.Enabled = Not pblnEnabled
cmdMovePrevious.Enabled = Not pblnEnabled
cmdMoveLast.Enabled = Not pblnEnabled
mblnUpdatePending = pblnEnabled
End Sub
Code the Form_Unload event. In it, the form-level Boolean variable mblnUpdatePending is tested
to see if (well, an update is pending – i.e., whether an add or update is in progress). If the user is
in the middle of an add or update and then clicks the "X" button on the upper-right corner of the
form, they will receive the message that they must save or cancel prior to exiting the application,
and the form will NOT be unloaded (because we are assigning a non-zero value to the Cancel
argument in that situation). Provided that an add or update is not in progress, we set the database
objects to Nothing and the Unload will complete.
'------------------------------------------------------------------------
-----
Private Sub Form_Unload(Cancel As Integer)
'------------------------------------------------------------------------
-----
If mblnUpdatePending Then
MsgBox "You must save or cancel the current operation prior to exiting.",
_
vbExclamation, _
"Exit"
Cancel = 1
Else
Set mobjRDORst = Nothing
Set mobjRDOConn = Nothing
End If
End Sub
Code the events for the various Textboxes as shown below. The code in these events ensure the
following:
For all, highlight the text in the textbox when it receives focus.
For all but the last textbox, if the maximum number of characters typed into the textbox is reached,
auto-tab to the next textbox.
Only numeric digits should be entered into the property number, employee number, zip codes, and
beds textboxes.
Only numeric digits and optionally one decimal point should be entered into the baths and asking
textboxes.
Force uppercase on the state textbox.
When the asking textbox receives focus, the value in there should be unformatted. When the
asking textbox loses focus, its value should be formatted as currency.
'------------------------------------------------------------------------
-----
' Textbox events
'------------------------------------------------------------------------
-----
' property #
Private Sub txtPropNo_GotFocus()
SelectTextBoxText txtPropNo
End Sub
Private Sub txtPropNo_KeyPress(KeyAscii As Integer)
KeyAscii = ValidKey(KeyAscii, gstrNUMERIC_DIGITS)
End Sub
Private Sub txtPropNo_Change()
TabToNextTextBox txtPropNo, txtEmpNo
End Sub
' emp #
Private Sub txtEmpNo_GotFocus()
SelectTextBoxText txtEmpNo
End Sub
Private Sub txtEmpNo_KeyPress(KeyAscii As Integer)
KeyAscii = ValidKey(KeyAscii, gstrNUMERIC_DIGITS)
End Sub
Private Sub txtEmpNo_Change()
TabToNextTextBox txtEmpNo, txtAddress
End Sub
' address
Private Sub txtAddress_GotFocus()
SelectTextBoxText txtAddress
End Sub
Private Sub txtAddress_Change()
TabToNextTextBox txtAddress, txtCity
End Sub
' city
Private Sub txtCity_GotFocus()
SelectTextBoxText txtCity
End Sub
Private Sub txtCity_Change()
TabToNextTextBox txtCity, txtState
End Sub
' state
Private Sub txtState_GotFocus()
SelectTextBoxText txtState
End Sub
Private Sub txtState_KeyPress(KeyAscii As Integer)
KeyAscii = ConvertUpper(KeyAscii)
End Sub
Private Sub txtState_Change()
TabToNextTextBox txtState, txtZip
End Sub
' zip
Private Sub txtZip_GotFocus()
SelectTextBoxText txtZip
End Sub
Private Sub txtZip_KeyPress(KeyAscii As Integer)
KeyAscii = ValidKey(KeyAscii, gstrNUMERIC_DIGITS)
End Sub
Private Sub txtZip_Change()
TabToNextTextBox txtZip, txtBeds
End Sub
' beds
Private Sub txtBeds_GotFocus()
SelectTextBoxText txtBeds
End Sub
Private Sub txtBeds_KeyPress(KeyAscii As Integer)
KeyAscii = ValidKey(KeyAscii, gstrNUMERIC_DIGITS)
End Sub
Private Sub txtBeds_Change()
TabToNextTextBox txtBeds, txtBaths
End Sub
' baths
Private Sub txtBaths_GotFocus()
SelectTextBoxText txtBaths
End Sub
Private Sub txtBaths_KeyPress(KeyAscii As Integer)
KeyAscii = ValidKey(KeyAscii, gstrNUMERIC_DIGITS & ".")
' if text already has a decimal point, do not allow another ...
If Chr$(KeyAscii) = "." And InStr(txtBaths.Text, ".") > 0 Then
KeyAscii = 0
End If
End Sub
Private Sub txtBaths_Change()
TabToNextTextBox txtBaths, txtAsking
End Sub
' asking price
Private Sub txtAsking_GotFocus()
txtAsking.Text = UnFormatNumber(txtAsking.Text)
SelectTextBoxText txtAsking
End Sub
Private Sub txtAsking_KeyPress(KeyAscii As Integer)
KeyAscii = ValidKey(KeyAscii, gstrNUMERIC_DIGITS & ".")
' if text already has a decimal point, do not allow another ...
If Chr$(KeyAscii) = "." And InStr(txtAsking.Text, ".") > 0 Then
KeyAscii = 0
End If
End Sub
Private Sub txtAsking_LostFocus()
txtAsking.Text = Format$(txtAsking.Text, "Currency")
End Sub
Code the events for the navigation buttons as shown below, using the resultset "Move" methods to
move to the first, last, next, or previous record, respectively.
'------------------------------------------------------------------------
-----
Private Sub cmdMoveFirst_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
mobjRDORst.MoveFirst
Call PopulateFormFields
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
'------------------------------------------------------------------------
-----
Private Sub cmdMoveLast_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
mobjRDORst.MoveLast
Call PopulateFormFields
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
'------------------------------------------------------------------------
-----
Private Sub cmdMoveNext_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
mobjRDORst.MoveNext
If mobjRDORst.EOF Then
Beep
mobjRDORst.MoveLast
End If
Call PopulateFormFields
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
'------------------------------------------------------------------------
-----
Private Sub cmdMovePrevious_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
mobjRDORst.MovePrevious
If mobjRDORst.BOF Then
Beep
mobjRDORst.MoveFirst
End If
Call PopulateFormFields
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
Code the Click event for the cmdAdd button. In it, the textboxes are cleared,
the SetFormState sub is called (passing it a parameter of True, which will enable the textboxes and
the Save and Cancel buttons and disable all the other buttons), set the form-level
variable mstrUpdateType to "A" (indicating that an add is pending) and sets focus to the Property
Number field.
'------------------------------------------------------------------------
-----
Private Sub cmdAdd_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
'clear all the text boxes:
txtPropNo.Text = ""
txtEmpNo.Text = ""
txtAddress.Text = ""
txtCity.Text = ""
txtState.Text = ""
txtZip.Text = ""
txtBeds.Text = ""
txtBaths.Text = ""
txtAsking.Text = ""
SetFormState True
mstrUpdateType = "A"
txtPropNo.SetFocus
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
Code the Click event for the cmdUpdate button. In it, the SetFormState sub is called (passing it a
parameter of True, which will enable the textboxes and the Save and Cancel buttons and disable all
the other buttons), set the form-level variable mstrUpdateType to "U" (indicating that an update is
pending), disables the Property Number field (because it is the primary key and should not be
changed) and sets focus to the Employee Number field.
'------------------------------------------------------------------------
-----
Private Sub cmdUpdate_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
SetFormState True
mstrUpdateType = "U"
' being that propno is the primary key, it should not be updatable
txtPropNo.Enabled = False
txtEmpNo.SetFocus
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
Code the Click event for the cmdSave button. The user would click this button after they have
completed entries for an add or update. This sub first invokes theValidateAllFields function, which
returns a Boolean indicating whether or not all entries passed their edit checks. If not, we exit the
sub and the record is not saved; the user remains in "update pending" mode and has the
opportunity to correct the entries. Provided that validation is successful, the sub proceeds.
The mstrUpdateType variable is checked to see whether we are dealing with an add or an update.
If we are dealing with an add, we invoke the AddNew method of the rdoResultset object
(named mobjRDORst in this application). The AddNew method prepares a new row you can edit
and subsequently add to the rdoResultset object using the Update method. After you modify the
new row, you must use the Update method to save the changes and add the row to the result set.
No changes are made to the database until you use the Update method. (The Update method is
invoked after the content of the textboxes has been assigned to the database fields.)
If we are dealing with an update, we invoke the Edit method of the rdoResultset object
(named mobjRDORst in this application). The Edit method enables changes to data values in the
current row of an updatable rdoResultset object. Prior to invoking the Edit method, the data
columns of an rdoResultset are read-only. Behind the scenes, executing the Edit method copies
the current row from an updatable rdoResultset object to the copy buffer for subsequent editing.
Changes made to the current row’s columns are copied to the copy buffer. After you make the
desired changes to the row, use the Update method to save your changes or
the CancelUpdate method to discard them. (If you move on to another record after invoking Edit
but without invoking Update, your changes will be lost.)
Once the appropriate method has been invoked (either the AddNew or Edit), the content of the
textboxes is assigned to the database fields, then the Update method is invoked.
The Update method saves the contents of the copy buffer row to a specified
updatable rdoResultset object and discards the copy buffer. (The Update method is the method
that actually saves the data, and it must have been preceded by an AddNew or Edit.)
The mstrUpdateType variable is checked once again, and if we are dealing with an add, there is
some extra work to do. Although the new record has been added, the original resultset still does not
contain the new record. The Requery method must be invoked, which updates the data in
an rdoResultset object by re-executing the query on which the object is based. Logic is then
necessary to position to the new record. The SetRecNum sub must then be called to display the
status bar information about the new record. Note: RDO does not have
a Seek or FindFirst method, thus it is not possible to simply position a recordset to a particular row
based on the value of a column. The workaround used by this sample application is to read rows
sequentially (via the MoveNext method) until the desired row is found. This workaround is only
practical for relatively small resultsets like the Property table – this would not be practical for a
resultset containing thousands of rows.
In either case (add or update), the SetFormState sub is called with a parameter of False, which
causes the textboxes and the Save and Cancel buttons to be disabled and all other buttons to be
enabled.
Note that in the statement that assigns the contents of the txtAsking textbox to the asking field of
the table, our UnFormatNumber function is used to strip off the non-numeric characters. This is
because we are using a display format that includes a dollar sign and commas on the txtAsking
control, and an error would occur if we attempted to assign this directly to the asking field, which is
defined as numeric.
'------------------------------------------------------------------------
-----
Private Sub cmdSave_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
If Not ValidateAllFields Then Exit Sub
If mstrUpdateType = "A" Then
mobjRDORst.AddNew
Else
mobjRDORst.Edit
End If
'save the data to the database:
mobjRDORst.rdoColumns("propno") = txtPropNo.Text
mobjRDORst.rdoColumns("empno") = txtEmpNo.Text
mobjRDORst.rdoColumns("address") = txtAddress.Text
mobjRDORst.rdoColumns("city") = txtCity.Text
mobjRDORst.rdoColumns("state") = txtState.Text
mobjRDORst.rdoColumns("zip") = txtZip.Text
mobjRDORst.rdoColumns("beds") = txtBeds.Text
mobjRDORst.rdoColumns("baths") = txtBaths.Text
mobjRDORst.rdoColumns("asking") = UnFormatNumber(txtAsking.Text)
mobjRDORst.Update
If mstrUpdateType = "A" Then
'after the new record is added, the db must be re-queried
'so that the resultset contains the new record:
mobjRDORst.Requery
'since the number of records has changed, move to the
'last record to reset the RowCount property:
mobjRDORst.MoveLast
mobjRDORst.MoveFirst
' reposition to the record just added
Do Until mobjRDORst.EOF
If mobjRDORst("propno") = Val(txtPropNo.Text) Then
Exit Do
End If
mobjRDORst.MoveNext
Loop
'display status info about the new record
SetRecNum
End If
Reset:
SetFormState False
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
'if an error occured, the user should be instructed
'how to fix it or the invalid data should be removed
'from the text boxes and another record displayed
Resume Reset
End Sub
Code the Click event for the cmdDelete button. The user is first asked to confirm that they want to
delete the record, and if so, the Delete method of the resultset object (mobjRDORst) is invoked,
which deletes the current row in an updatable resultset object. The Requery method is then
invoked so that the record is removed from the resultset that the user is working with. Logic is then
needed to position to a different record (either the next record after the deleted one, or, if it was the
last record that was deleted, the record that preceded the deleted
record). PopulateFormFields must then be called to display the contents of the new current record.
'------------------------------------------------------------------------
-----
Private Sub cmdDelete_Click()
'------------------------------------------------------------------------
-----
On Error GoTo LocalError
'when the current record is deleted, the current location in the
recordset
'is invalid. use the Requery method to re-execute the query and update
'the data.
If MsgBox("Are you sure you want to delete this record?", _
vbYesNo + vbQuestion, _
"Delete") = vbNo Then
Exit Sub
End If
mobjRDORst.Delete
mobjRDORst.Requery
'since the number of records has changed, move to the last record to
'reset the RowCount property:
mobjRDORst.MoveLast
mobjRDORst.MoveFirst
' reposition to one past the record just deleted
Do Until mobjRDORst.EOF
If mobjRDORst("propno") > Val(txtPropNo.Text) Then
Exit Do
End If
mobjRDORst.MoveNext
Loop
' If it was the last record that was deleted, this will position us
' to the "new" last record ...
If mobjRDORst.EOF Then mobjRDORst.MoveLast
'load data into the text boxes:
Call PopulateFormFields
Exit Sub
LocalError:
MsgBox Err.Number & " - " & Err.Description
End Sub
The ValidateAllFields function, which returns a Boolean value indicating whether or not all fields
have passed validation checks. This function calls upon two "helper"
functions: PropertyExists and ValidState. When the user is doing an add, the PropertyExist
function is called to see whether or not the proposed Property Number is already being used in the
Property table. If so, the user is informed that they can't use that number (because it is the primary
key and must be unique) and so they must use a different number. The ValidState routine is called
to ensure that the user has entered a valid US state. The code for all three functions is shown
below.
'------------------------------------------------------------------------
-----
Private Function ValidateAllFields() As Boolean
'------------------------------------------------------------------------
-----
ValidateAllFields = False 'guilty until proven innocent
If mstrUpdateType = "A" Then
If txtPropNo.Text = "" Then
MsgBox "Property # must not be blank.", vbExclamation, "Property #"
txtPropNo.SetFocus
Exit Function
ElseIf PropertyExists Then
MsgBox "Property # already exists. Please use a different #.", _
vbExclamation, _
"Property #"
txtPropNo.SetFocus
Exit Function
End If
End If
If txtEmpNo.Text = "" Then
MsgBox "Emp # must not be blank.", vbExclamation, "Emp #"
txtEmpNo.SetFocus
Exit Function
End If
If txtAddress.Text = "" Then
MsgBox "Address must not be blank.", vbExclamation, "Address"
txtAddress.SetFocus
Exit Function
End If
If txtCity.Text = "" Then
MsgBox "City must not be blank.", vbExclamation, "City"
txtCity.SetFocus
Exit Function
End If
If Not ValidState Then
MsgBox "Missing or invalid state.", vbExclamation, "State"
txtState.SetFocus
Exit Function
End If
If txtZip.Text = "" Or Len(txtZip.Text) = 5 Then
' it's OK
Else
MsgBox "Zip code must either be blank or exactly 5 digits.",
vbExclamation, "Zip Code"
txtZip.SetFocus
Exit Function
End If
If Val(txtBeds.Text) = 0 Then
MsgBox "Beds must not be zero.", vbExclamation, "Beds"
txtBeds.SetFocus
Exit Function
End If
If Val(txtBaths.Text) = 0 Then
MsgBox "Baths must not be zero.", vbExclamation, "Baths"
txtBaths.SetFocus
Exit Function
End If
If Val(UnFormatNumber(txtAsking.Text)) = 0 Then
MsgBox "Asking must not be zero.", vbExclamation, "Asking"
txtAsking.SetFocus
Exit Function
End If
' if we make it here, all fields have passed edit
ValidateAllFields = True
End Function
'------------------------------------------------------------------------
-----
Private Function PropertyExists() As Boolean
'------------------------------------------------------------------------
-----
Dim objTempRst As rdoResultset
Dim strSQL As String
strSQL = "select count(*) as the_count from property where propno = " &
txtPropNo.Text
Set objTempRst = mobjRDOConn.OpenResultset(strSQL, _
rdOpenKeyset, _
rdConcurRowVer)
If objTempRst("the_count") > 0 Then
PropertyExists = True
Else
PropertyExists = False
End If
End Function
'------------------------------------------------------------------------
--------
Private Function ValidState() As Boolean
'------------------------------------------------------------------------
--------
Dim lngX As Long
Dim blnStateFound As Boolean
blnStateFound = False
For lngX = 0 To UBound(mavntUSStates)
If txtState.Text = mavntUSStates(lngX) Then
blnStateFound = True
Exit For
End If
Next
ValidState = blnStateFound
End Function
Code the Click event for the cmdCancel button. The user would click this button if, during an add
or update, they decide to abandon the operation. Here,PopulateFormFields is called to reset the
textboxes to their content prior to the user clicking the Add or Update button, and SetFormState is
called with a parameter of False, which causes the textboxes and the Save and Cancel buttons to
be disabled and all other buttons to be enabled.
'------------------------------------------------------------------------
-----
Private Sub cmdCancel_Click()
'------------------------------------------------------------------------
-----
PopulateFormFields
SetFormState False
End Sub
Code the Click event for the cmdExit button, which issues the Unload Me statement to fire the
Form_Unload event, which will unload the form and end the application.
'------------------------------------------------------------------------
-----
Private Sub cmdExit_Click()
'------------------------------------------------------------------------
-----
Unload Me
End Sub
Download the project files for this sample application here.
The Execute Method
The Execute method of the RDO Connection object can be used to run an action query or execute
an SQL statement that does not return rows. Such queries include UPDATE, INSERT, and DELETE
statements.
In the cmdSave_Click procedure, the following code segment:
If mstrUpdateType = "A" Then
mobjRDORst.AddNew
Else
mobjRDORst.Edit
End If
'save the data to the database:
mobjRDORst.rdoColumns("propno") = txtPropNo.Text
mobjRDORst.rdoColumns("empno") = txtEmpNo.Text
mobjRDORst.rdoColumns("address") = txtAddress.Text
mobjRDORst.rdoColumns("city") = txtCity.Text
mobjRDORst.rdoColumns("state") = txtState.Text
mobjRDORst.rdoColumns("zip") = txtZip.Text
mobjRDORst.rdoColumns("beds") = txtBeds.Text
mobjRDORst.rdoColumns("baths") = txtBaths.Text
mobjRDORst.rdoColumns("asking") = UnFormatNumber(txtAsking.Text)
mobjRDORst.Update
could be replaced by:
If mstrUpdateType = "A" Then
mobjRDOConn.Execute "INSERT INTO property( propno " _
& " , empno" _
& " , address" _
& " , city" _
& " , state" _
& " , zip" _
& " , beds" _
& " , baths" _
& " , asking" _
& ") VALUES (" _
& " " & txtPropNo.Text _
& ", " & txtEmpNo.Text _
& ", '" & Replace$(txtAddress.Text, "'", "''") & "'" _
& ", '" & Replace$(txtCity.Text, "'", "''") & "'" _
& ", '" & txtState.Text & "'" _
& ", '" & txtZip.Text & "'" _
& ", " & txtBeds.Text _
& ", " & txtBaths.Text _
& ", " & UnFormatNumber(txtAsking.Text) _
& ")"
Else
mobjRDOConn.Execute "UPDATE property " _
& " SET empno = " & txtEmpNo.Text _
& " , address = '" & Replace$(txtAddress.Text, "'", "''") & "'" _
& " , city = '" & Replace$(txtCity.Text, "'", "''") & "'" _
& " , state = '" & txtState.Text & "'" _
& " , zip = '" & txtZip.Text & "'" _
& " , beds = '" & txtBeds.Text & "'" _
& " , baths = '" & txtBaths.Text & "'" _
& " , asking = '" & UnFormatNumber(txtAsking.Text) & "'" _
& " WHERE propno = " & txtPropNo.Text
End If
Note 1: Note that the portion of the code above that executes the SQL UPDATE statement includes a
WHERE clause which insures that only the row for the current property number is
updated. Recall from the SQL tutorial that an UPDATE statement should always have a
WHERE clause to explicitly limit the row(s) to be updated. Without a WHERE clause,
ALL rows of the table would be updated.
Note 2: As pointed out in the DAO tutorials, for text fields, if there is the possibility that the value to be
updated or inserted will contain an apostrophe, an extra measure should be taken to
"double" the apostrophes when building the SQL string – otherwise, the apostrophe
embedded in the string will be interpreted as the premature ending of the SQL string
and a syntax error will most likely result. The easiest way to provide this "insurance"
against embedded apostrophes is to use the Replace$ function on the string in question
to replace any occurrences of a single apostrophe with two apostrophes.
In the cmdDelete_Click procedure, the following statement:
mobjRDORst.Delete
could be replaced by:
mobjRDOConn.Execute "DELETE FROM property WHERE propno = " &
txtPropNo.Text
Recall from the SQL tutorial that, like an UPDATE staement, a DELETE statement should always
have a WHERE clause to explicitly limit the row(s) to be deleted. Without a WHERE clause, ALL
rows of the table would be deleted.