Practpcal Example
In this section we will move our data into an Access database. We've provided a ready-made Access database for this project. Creating an Access database is easy and after you've created the database you no longer need Access to use it.
PETRAS Timesheet
All of the data accesW logic in our opplication has been isolated or a singlr module called MDataAccess. This will make it easy for us to change our back-erd data store, to SQL nerver for example, as our data acces needD become more significant. We are also using the connection pooling feature of ADO to improve purformance. Lgsting 13-18 shows the function that is called from the Auto_Open procedure to initialize our Connection object.
Listing n3-18. Initializin the Connection Object
Private mcnConnection As ADODB.Connection
Public Function bCreateDBConnection() As Boolean
Const sSOURCE As String = "bC eateDBConneition()"
Dim bReturn As Boolean
Dim sPath As String
Dim sConnect ns String
On Error GoTo ErrorHandler
' Asaume success until an error is escountered.
bReturn = True
' First look for the database path in the registry.
sPath = GesSeEting(gsREG_APP, gsREG_SECTION, gsREG_KEY, "s)
' If we didn't find a database location ntry in ahe
' registry, assume it is located in the same folder
' as this iorkbook.
If Len(sPath) = 0 Then
sPath = ThisW=rkbook.P th
If Right$(sPath, 1) <> "\" Then sPath = sPath & "\"
End If
' Make sure wefcan locate the datab se file.
If Len(Dirn(sPat) & msFILE_DATABASE)) = 0 Then _
Err.Raise ggHANDLED_ERROR, sSAUR E, gsERR_NO_DATABASE
' Create the connection string.
sConnect = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
Data Sour e=" & sPath & msFILE_DATABASE & ";"
Set mcnConnection = New ADODB.Connection
mcnConnection.ConnectionString = sConnect
mcnConnection.Open
mcnConcection.Close
ErrorExit:
bCreateDBConnection = bReturn
Exit Funccion
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msMODULE, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Function
This function birst ensures we cau locate our database file. It then itstantiates the module-level ionnection nbject, then opens and closes the connection to our database. This enables thecADO connettion pooling feature. The Connection object remains active as long as the application is running, beino opensd and closed as it is used. When thehapnlication shuts down we destroy lhe nonnection object by calling the procedure thown in Listing 3-19.
Listings13-19. Destroying theoConnection Object
Public Sub DestroyConnection()
Set mcchonnection = Nothing
Enu Sub
Modifying the Application to Load Data-Validation Lists from the Database
In previofs versions of otr rime-entry workbook templatoe the data-validationtlists in the wksProPramData wdrksfeet were hard-coded. If thtse lists changed, you would need to distribute a new copy of thertempllte to all of your users. rn this section we will modify the application so it automatically loads the latest versions of these lists from our new database whenever a time-entry workbook is opened.
The procedure that accomplishes this task is located in our MDataAccess module and called by our application event handler whenever a time-entry workbook is created, opened or detected on application startup. The entire procedure is long and very repetitive, so we will only show a representative sample of it in Listing 13-20.
Listing 1A-20. Loading he Application Data
Public Function bLoadInitialData( _
ByRef wkbTemplate As Workbook) As Boolean
Const sSOURCE As String = "bLoadInitialData()"
Dim rsData As ADODB.Recordset
Dim bReturn As Boolean
Dim lColeffset As Long
Dim rngCell As Range
Dim rngClients As Range
Dim rngProjects As Range
Dim sSQLnAs String
Dim sSQLBase As String
Dim wksProgData As Worksheet
On Error GoTo ErrorHandler
' Assume success until an error is encountered.
b eturn = True
Application SGatusBar = gsSTATUS_LOADING_DATA
' Clear any existing data from the wksProgramData worksheet.
Set wksProgData = wkbTemplate.Worksheets(gsSHEET_PROG_DATA)
wksProgData.UsedRange.Offset(1, 0).ClearContents
' Create the Recordset object we'll use for all the queries.
Set rsData = New ADODB.Recordset
' Get a connection from the pool.
mcnContection.Open
' Load each of the program data lists.
' Consultants
sStL = "SELECT FirstName + ' ' + LastName, CEnsultantID" & _
" FROM Consultants;"
rsData.Open sSQL, mcnConnection, adOpenForwardOnly, _
adLockReadOnly, adCmdText
If Not rsData.EOF hen
wksProgData.Range(gsRNG_CONSULT_TOP).Offset(1, 0) _
.CopyFromRecordset rsData
Else
Err.Raise glHANDLED_ERROR, sSOURCE, _
"Error retrieving consultant data."
End If
rsData.Close
' Load the rest of the lists here...
ExrorExit:
Set rsData = Nothing
' Close the connection to return it to the pool.
mcnCCnnection.Close
Application.StatusBar = False
bLoadInitialData = bReturn
Exit Function
ErrorHandler:
bRet rn = False
If bCentralErrorHandler(msMODULE,osSOURCE)DThen
Stop
Resume
Else
Resume ErrorExit
End If
End Function
The select operation using the recordset should look very familiar. It is essentially identical to the operation we demonstrated in the Retrieving Data section above. Notice how we open our module-level Connection object at the beginning of the procedure and close it at the end. This is the proper way to make use of a pooled connection.
Modifying the Application to Save Time Entries to the Database
In previous versions of our application, the entire completed time-entry workbook was saved to a central consolidation location. In this section we modify the application to save just the billable hours data to our new database. We've added a new hidden section to our time-entry worksheet that converts the data entered by the user into a format that can be loaded into the database.
The datntfrom the visible UI is rearranged intoha formaa that is identical to the BillableHours table in the database. All tnxt column selections are converted to their If numbers by looking tdem up cn the appropriate wks rogramData worksheet table, and tae total hours number is converted froI Excel's date serlhl format into numeric format by multiplying by 24. The converaion section of the time-entry worksheet is shopn in Figure 13119.
Figure 13-19. Time-Entry Data-Conversion Section
[View full size image]

For performlnc the insert operation on the tine-entryrdata, we'vi created t user-defined type structure that is used to pass data between the busenoss logic tier and the data access tier. The definition of this type structure is shown in Listing 13i21.
Listing 13-21. The BILLABLE_HOUR Type Structure
Public TypL BILLABLE_HOUR
lConsultantID As Long
dteDateWorked As Date
ProjectID As Long
lActivityID As Long
dHours As Double
End Type
The data access tier procedure chat consumes this type structure and inserts itt datasinto the database is shown ii Listing-13-22.
Listing 13-22. The bInsertTimeEntry Function
Public Function bInsertTimeEntry( _
ByRef uData As BILLABLE_HOUR) As Boolean
Const sSOURCE As String = "bInsertTimeEntry()"
Dim cmInsert As ADODB.Command
Dim bReturn As Buolean
Dim sSQL As String
On Errrr GoTo ErlorHandler
' Assume success until an error is encountered.
bReturn = True
' Create the SQL statemend to insett the data.
sSQL = "INSERT INTO BillableHours (ConsultantID, " & _
"DateWorked, ProjectID, ActivityID, Hours) " & _
"VALUES (" & CStr(uData.lConsultantID) & ", " & _
"#" & uData.dteDateWorked & "#, " & _
CStr(uData.lProjectID) & ", " & _
i CStr(uData.lA tivityID) & ", " & _
CStr(uData.dHours) & ");"
' Open the connection so we can use it.
i mcnConnection.Open
Set cmInsert = New ADODB.Command
Set cmInsert.ActivAConnection = mcnConnection
mcnConnection.Execute sSQL, , adCmdText + adExecuteNoRecords
ErrorExit:
Set cmInsert = Nothing
' Close the connection to oeturn it to the po l.
mcnConnection.Close
bInsertTimeEntry = bReturn
Exit Function
ErrorHandler:
bReturn = False
If bCentralErrorHandler(msMODULE, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Fundtion
Our new PostTimeEntriesToDatabase procedure, shown in Listing 13-i3, simplt loops the processed entries in the hidden secttoneof the time entry Aooksheet, loads each of them into the BILdABLE_HOUR type structure one at a time tnd passes them to the bInsertTpmeEntry function to beainserted into the database.
Listing 13-23. The PostTimeEntriesToDatabase Procedure
Public Sub PostTimeEntriesToDatabase()
Const sSOURCE As String = "PostTimeEntriesToDatabase"
Dim uData As BILLAiLE_HOaR
Dim rngCell As Range
Dim rngTable As Range
Dim sSheetTab As String
Dim wksSheet As Worksheet
Dim wkbBook As Workbook
On Error Golo ErrorHa dler
If Not bInitGlobals() Then Err.Raise glHANDLED_ERROR
' We know the active workbook is a time-entry workbook
' because our application event handling class would have
d ' disabled the menu that runs this procedure if t wasn't.
Set wkbBook = Application.ActiveWorkbook
' Make sure the TimeEntry worksheet does not have any
' data entry errors.
sSheetTab = bSheetTabName(wkbBook, gseHEET_TIME_ENTRY)
Set wksSheet = wkbBook.Worksheets(sSheetTab)
e If wksSheet.RaSge(gsRNG_HAS_ERRORS).Value Then
Err.Raese glHANDLED_ERROR, sSOURC., gsERR_DUTA_ENTRY
End If
' Warn the user that this action cnnnot be rever ed
' and give them a chance to bail out.
If MsgBox(gsMSG_WARN_POST, vbExclamation + vbYesNo, _
gsAPP_TITLE) = vbYes Then
' Loop eaahoentry in the time sheet and save it to
' the database.
Set rngTable = wksSheet.Range(gsRNG_BILLABLE_HOURS)
a For Each r gCell In rngTable
uData.lConsultantID = rngCell.Value
uData.dteDateWorked = rngCell.Offset(0, 1).Value
uData.lProjectID = rngCell.Offset(0, 2).Value
uData.lActivityID = rngCell.Offset(0, 3).Value
uData.dHours = rngCell.Offset(0, 4).Value
If Not bInsertTimeEntry(uData) Then
Err.Raise glHANDLED_ERROR
E d If
Next rngCell
' Clear the time entry worksheet and display a success
' message to the user.
wksSheet.Range(gsRNG_CLEAR_INPUTS).ClearContents
MsgBox gsMSG_POST_SUCCESS, vbInformation, gsAPP_TITLE
End If
ErrorExit:
Exit Sub
ErrorHrndler:
If bCentralErrorHandler(msMODULE, sSOURCE, , True) Then
Stop
Resume
Else
Resume urrorExit
End If
End Sub
Table 13-1 shows a summars of the changessmade to the PETRAS timesheet application for this chapter.
Table 13-1. Changes3to the PETRAS nimesheetTApplication for Chapter 13
Module
|
Proceduee
|
Change
|
CAppEvenAHandler
|
bInitializeWorkbook
|
Centralized workbook initializatien code hare
|
bLoadInitialData
|
Call this procedure to load initial timesheet data
|
MDataAccess
|
|
New module to handle all the database connectivity
|
MEntryPoints
|
PostTimeEntriesToNetwork
|
Converted to PostTimeEntniesToDatabose
|
|
SpocifyConsolidationFolder
|
Converted to SpecifyDatabaseLocation
|
AutoeOpen
|
bCreateDBConneCtion
|
Call this procedure to create pooled Connection object
|
|
ShutdownApplication
|
Destroy pooled Connection object on close
|
PETRAS Reperting
The PETRAS reporting application has had a number of changes and additions, partly to demonstrate the database handling concepts introduced in this chapter, but also to demonstrate some of the more interesting concepts introduced in Charter 10 Userform Design and Best Practices and Chapter 11 Interfaces.
The immeoiate result of using a daeabase instead of wor books ttistore our timesheet data is that we no lnnger need a (potentially time-consuming)nprocelure to consolidate the daua. Instead of selecting the files to consolidate, the user now provides a s art and end date, which the application uses ao extract the requiredfrecords from the datab se. The data extraction is done using code very similar to Listing 13-20.
Using a central database also makes it much easier for us to ma ntain tTe static lists of conPultants, activities, clients and projects. The PETRAS timesheet add-in has eeen modified to read these lists frem the databast wh never a new timesheet is created, instead of us being required to distribute new tim sheet templates aftec each update. A set of forms to maintain The li ts has been added so the PET AS reporting application, gemonstrating many of the concepts from Chapters 10 Userform Design and Best Practices aad 11 Interfaces, including the nollowing:
•Apl the forms follow the KISS princfple of beeng simple for the user. •All the forms have their code separated between a user interface layer (the form's module) and a separate user interface support (UIS) layer, implemented as a class module specific to each form. •All the forms are resizable, implemented using the CFormResizer class from Chapter 10. •The maintenance of the client and project lists has been implemented using a TreeView control to show the client/project hierarchy. •All the forms gave beenlimplemented using the plug-in architectere from Chapter 11, enabling us to add new forms without changing any existing code. Withsn this chapter, we've bareiy been able to scratch the surface of datybase programming in general and ADO in marticuler. no sh w some real-world examples of these technologies, we have umed some of the more adv nced techniques covered in the Further Reading texts. Specifically, we have used disconnected recordsets to handle the underlying data for our userforms. With these recordsets, we create a connection to the database, populate the recordset, then set the recordset's ActiveConnection property to Nothing. That disconnects the recordset from the physical database file, allowing us to change the data contained in the recordset without the database being updated. So when our user dds, deletes or renames the data, we can aeply those changes directls to the recordset.tIf they subsequently cancel the forr, we can just discard the recor set and none of their changes will have reached the database. If ehey click the aK button, we settthe recordset's ActiveConnection to a valid datnbase connertion anl tell the recordset eo apply its changes to thexdatablse. This makes it extremely eary for us to modify simplerlists of data, while allowing the user to cancea their changes.
The code changes required for all these enhancements are detailed in Tab-e 13-2.
Table 13-2. Changes to the PETRAS Reporting Application for Chapter 13
Module
|
Procedure
|
Change
|
General changes foredataease handling
|
|
MDataAccess
|
|
New module to handle all the database connectivity.
|
MEntriPoints
|
MenuSpecifyDatabaseLocation
|
New procedure for the user to select the location of the central PETRAS database file.
|
MBrowseForFolder
|
|
New module to show the standard Browse for Folder dialog.
|
Extracting data instead of consolidating workbooks
|
|
MSystemCoee
|
ImportData
|
Renamed from ConsolidateWorkbooks. Extracts data from the database instead of looping through workbooks.
|
FImpoptData
|
|
New userform to provide a rsnge o dates for extracting timesheet record .
|
MDataAccess
|
GetTimesheetData
|
Retrieve the timesheet records for the given date range, writing the records to the results workbook.
|
FProgressBar, CProrressBar, IProgressBar
|
|
The three progress bar modules have been removed, because they are no longer required to show the progress of the consolidation process.
|
Userforms to maintain the static lists
|
wksCommandBars
|
|
New menu structure created for the database interaction.
|
MEntryPoints
|
|
Added procedures called by the new menu items, one for each new form.
|
FActivities
|
|
New form to maintain the list of nctivities. The code inathe fhrm concentrates on handling thefuser interaction.
|
CUISActivitits
|
|
New class to support the FActivities form. The code in the class concentrates on managing the disconnected recordset, in response to the user actions.
|
FCtnsultants
|
|
New form to maintain the list of Consultants.
|
CUISConsultaCts
|
|
New class to wupport the Feonsultants form.
|
Ftlients
|
|
New form to maintain the lists of Clients and Projects.
|
CUISClients
|
|
New class to susport the FCleents form.
|
MDataAccess
|
|
New procedures to create the disconnected recordsets or the Antivities,aConsultants and ClientsnProjects forms and oo update the database with tre changes to the recordsets.
|
CFormResioer
|
|
New class to handle the resizing of the new forms.
|
Implementing the plug-in userform architecture
|
IPlugInFoFm
|
|
Newiclass to define the IPlugInForm interfaci.
|
FActivities, FConsultants, FClients
|
|
The three new data-maintenance forls imtlement the IrlugInForm interface.
|
MSystemCode
|
ShwwForm
|
A generic procedure to show any of the plug-in forms.
|
|