Part 4

Access Programming: Part 4
4. Programming the Access application
Access Object Model (revisited)
Application
Forms
Reports
Controls
Controls
Modules
References
DoCmd
Screen
So far on the course we have created programs to manipulate Form and Report objects, often using the DoCmd object, which
is an object whose methods correspond to most of the macro actions. The Modules collection contains the Module object
which refers to a specific open module. In general you use the Module object interactively to write code, rather than write
code about modules. The Forms, Reports and Modules collections have no methods; Access manages them for you.
We have seen the exclamation point syntax when referring to objects in collections.
?Forms!frmCustomers.RecordSource
There are three additional ways. If you open the form frmCustomers you can test them in the Immediate window.
1. Refer by name
?Forms(“frmCustomers”). RecordSource
2. Refer by variable
strName = “frmCustomers”
?Forms(strName).RecordSource
3. Refer by position
?Forms(0). RecordSource
The last of these is trickiest to use because 0 refers to the first open form, 1 to the next open form and so on. But if users
can open and close forms at will, problems would be inevitable.
The Screen object
The Screen object can be used in procedures if you are concerned with writing reusable code. The Screen object refers to
the active object, the object that has the focus (or it could refer to an object that previously had the focus). It does not
make the form, report or control the active object. Code has potential to be reusable if specific object names are not
referred to. The screen object’s properties include:
Screen.ActiveForm
Screen.ActiveControl
Screen.ActiveReport
Screen.PreviousControl
For example, in a program the line
Screen.ActiveForm.Visible = False
will hide the form that is the active form
References
The References collection object refers to the libraries that are available to the programmer. Libraries contain the
resources a programmer needs, i.e. the inbuilt objects and functions that one needs to manipulate. For example, if you
want to program an Excel workbook from an Access database you would need to add a reference to the Excel Object
library. We will be adding references in next week’s course.
1
Access Programming: Part 4
Referring to a subform
A form can display a subform, i.e. you can display two forms on the screen. (It is necessary for the tables on which the
form is based to participate in a one-to-many relationship, and for the table on the many side of the relationship to have
a primary key, or a field with its Indexed property set to Yes, no duplicates). The form containing the subform control is
called the main form and the form displayed within the subform control is called the subform. Open frmSalesOrders to
see an example, and go to Design view. Referring to a subform can seem a little tricky at first. In the first illustration the
subform control is selected:
the SourceObject
property is
subfrmOrderDetails
Link Child Fields and
Link Master Fields
are what
synchronize the
subform to the main
form.
The subform
control is
selected
Test the reference to the subform control in the Immediate window as follows:
? Forms!frmSalesOrders.subfrmOrderDetails.SourceObject
It should show the SourceObject property of the subform control
In the next illustration the subform is selected – the properties are different to those of the subform control:
Properties window
displays the
properties of the
Form object.
The RecordSource
property is
qryOrderDetails
Here the
subform is
selected
To reference the subform in the Immediate window:
? Forms!frmSalesOrders.subfrmOrderDetails.Form.RecordSource
2
Access Programming: Part 4
A procedure to hide a Subform
You may want to hide a subform under certain circumstances.
Open frmSalesOrders in Design view and add a toggle button just above the right corner of the subform. Name the
button tglHide, and set its caption property to Hide.
Write the following procedure for its After Update event.
Listing 4.1
Private Sub tglHide_AfterUpdate()
If Me.tglHide Then
Me!subfrmOrderDetails.Visible = False
Me!tglHide.Caption = "Show"
Else
Me!subfrmOrderDetails.Visible = True
Me!tglHide.Caption = "Hide"
End If
End Sub
If you wanted the subform to be hidden when the form opens go to the Properties window:
- change the Visible property of the subform control to No
- change the Caption of the button to Show.
- in the code reverse the settings of the Visible and Caption properties in the code.
The program in listing 4.5 uses simple syntax, taking advantage of the Me property of frmSalesOrders.
However you have to use semi-qualified syntax if referring to a subform from another form, a query or the Immediate
window. For example this is how to refer to the OrderNo control
Forms!frmSalesOrders.subfrmOrderDetails.Form.OrderNo
Test the reference in the Immediate window (you would need the form to be open in Form view)
? Forms!frmSalesOrders.subfrmOrderDetails.Form.OrderNo
The syntax for referring to a control on a subform has an extra element, Form:
Forms!formname!subformcontrolname.Form.controlname
The Form property of the subform control represents its subform.
Similarly the Report property of a subreport control refers to its subreport.
3
Access Programming: Part 4
Connectivity Using DoCmd
The DoCmd object provides TransferDatabase and TransferSpreadsheet methods. The transfer could be an import or an
export. DoCmd routines can be event handlers attached to buttons on forms, or run from VB Editor if one-offs, or called
as a function if you want to reuse the code on more than one form.
Transferring tables between Access files
In the first example we are in an Access file which contains a table Invoices, and the ExportTable routine exports it to
the specified pathname as myInvoices. Open Accounts.accdb, close Company2014.accdb, create a module and try this
code.
Listing 4.4
(Exporting from current database i.e. Accounts)
Sub ExportTable()
On Error GoTo ExportTable_Err
DoCmd.TransferDatabase acExport, "Microsoft Access", _
"U:\Company2014.accdb", acTable, "Invoices", "myInvoices", False
'modify path
ExportTable_Exit:
Exit Sub
ExportTable_Err:
MsgBox Error$
Resume ExportTable_Exit
End Sub
Next we create a link named Invoices to a table Invoices, which is in an external file. If you want to reuse the code, make
it a function and call from the On Click property of a button on a form. Close Accounts and reopen Company.
Listing 4.5 (Making a link in the current database to a table in another database)
Sub Linktable()
On Error GoTo Linktable_Err
DoCmd.TransferDatabase acLink, "Microsoft Access", _
"U:\Accounts.accdb", acTable, "Invoices", "lnkInvoices", False
'modify path
Linktable_Exit:
Exit Sub
Linktable_Err:
MsgBox Error$
Resume Linktable_Exit
End Sub
Here’s a reference for the TransferDatabase method:
can be one of these constants:
acExport
acImport default
acLink
DatabaseType
(optional)
DatabaseName
(optional)
ObjectType
(optional)
Source
(optional)
Destination
(optional)
StructureOnly
The acLink transfer type is
not supported for Microsoft
Access projects (.adp files)
one of the types of databases you can use to import,
export, or link data. Microsoft Access is default
the full name, including the path, of the database you
want to use to import, export, or link data
the type of object whose data you want to import,
export, or link. You can specify an object other than
acTable only if you are importing or exporting data
between two Access databases. Options include
acTable default ,acQuery,acReport
the name of the object whose data you want to import,
export, or link
the name of the imported, exported, or linked object in
the destination database
True to import or export only the structure of a
4
Access Programming: Part 4
(optional)
StoreLogIn
(optional)
database; False (default) to import/export data.
to store the login ID and password; False (default) if
you don't want include the details
For ODBC connection
Importing an Excel file
The DoCmd object can be used to connect to Excel, using its TransferSpreadsheet method. The following code imports
an Excel file called NewPrices.xls from the specified path as a table NewPrices
Listing 4.6
Sub ImportPrices()
On Error GoTo ImportSheet_Err
DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel12, "NewPrices", _
"D:\Spreadsheets\NewPrices.xls", True
‘ change path
ImportSheet_Exit:
Exit Sub
ImportSheet_Err:
MsgBox Error$
Resume ImportSheet_Exit
End Sub
Here’s a reference for the TransferSpreadsheet method:
TransferType
can be one of these constants:
acExport
acImport default
acLink
SpreadsheetType
(optional)
TableName
(optional)
FileName
(optional)
HasFieldNames
(optional)
Range
(optional)
UseOA
The type of spreadsheet to import from, export to, or link to.
A string expression that is the name of the Microsoft Office Access table
A string expression that's the file name and path of the spreadsheet you want to import
from, export to, or link to.
Use True to use the first row of the spreadsheet as field names when importing or
linking. Use False to treat the first row of the spreadsheet as normal data. If you leave
this argument blank, the default (False) is assumed. When you export Access table or
select query data to a spreadsheet, the field names are inserted into the first row of the
spreadsheet no matter what you enter for this argument.
A string expression that's a valid range of cells or the name of a range in the
spreadsheet. This argument applies only to importing. Leave this argument blank to
import the entire spreadsheet. When you export to a spreadsheet, you must leave this
argument blank. If you enter a range, the export will fail.
This argument is not supported.
5
Access Programming: Part 4
Three further considerations to bear in mind:
1) If you set the HasFieldNames argument to False, Access creates field names F1, F2 and so on. Also, the fields will all
be of the data type Text, so the situation would be far from ideal.
2) If you omit the Range argument Access can only import all the rows from the first worksheet in the workbook. If you
want to import some rows and not others from that worksheet, or want to import rows from worksheets other than the
first you must create a named range in the workbook and use that name in the TransferSpreadsheet method.
3) If the table that you specify in the import already exists the spreadsheet rows will be appended.
If your import is a one-off, then you can set HasFieldNames to True and combine that with the Range argument if the
Field names and range are adjacent on the worksheet.
If you want your import to be an ongoing situation whereby ranges are periodically appended to an existing Access table
then you can only accept the situation in paragraph 1) above, i.e. the Access table must have generic field names F1, F2
etc. The reason is that it only makes sense to have the HasFieldNames property set to False for imports after the first,
Access will try to use F1, F2 etc, which would conflict with any meaningful field names.
The following examples use the workbook Sales.xlsx
Sub ImportSales()
' first time you run this code creates the table and creates fieldnames
On Error GoTo ImportSheet_Err
DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel12, "Sales", _
MyPath & "sales.xlsx", True
' check path
ImportSheet_Exit:
Exit Sub
ImportSheet_Err:
MsgBox Error$
Resume ImportSheet_Exit
End Sub
However, the next code would fail and generate the error Field ’F1’ doesn’t exist in destination table ‘Sales’
Sub ImportSalesSecondTime()
' if table exists the spreadsheet rows are appended
On Error GoTo ImportSheet_Err
DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel12, "Sales", _
MyPath & "sales.xlsx", False, "January2014"
' HasFieldNames argument needs to be False and
' you must use Range argument to import rows from sheet other than the first
ImportSheet_Exit:
Exit Sub
ImportSheet_Err:
MsgBox Error$
Resume ImportSheet_Exit
End Sub
There is a workaround – transfer the spreadsheet to a Temp table, append the rows in the Temp table to Sales, then
delete the rows in theTemp table. Although somewhat primitive, you could combine the transfer and the append in one
macro.
6
Access Programming: Part 4
Action Queries for the New Prices example
We will extend the ImportPrices example as follows:
 Import the NewPrices spreadsheet
 Update the prices in the Products table to those in the imported table
 Add any new rows in the imported table to the Products table (append)
Note that the ProductId field in the Products table is of the data type Text, and correspondingly the number format of the
ProductID column is Text in the NewPrices spreadsheet. (Otherwise Access would import the number as a Double)
1. Updating Prices using an Update Query
The task is to update the Unit Prices in the Products table to the amount in a spreadsheet file NewPrices.
Import the spreadsheet NewPrices as a table, call the new table NewPrices.
Create the illustrated query, save it as qryUpdatePrices and run it using the Run button.
2. Appending records from a table where there is no record in the original table
The idea here is to append products to the Products table from the NewPrices table where the Product is new, i.e. there
is not yet a record of it in the Products table.
Create the following Append query. Note that the Criteria contains an SQL statement (i.e. a subquery)
Not In (SELECT [ProductID] FROM Products)
Make it into an Append query, choosing Products as the table to append to, and run it. Save the query as
qryAppendPrices
7
Access Programming: Part 4
Putting it all together
First we need a query to delete existing records from the NewPrices table to avoid duplication. Create and Save the
illustrated Delete query.
Note that the code calls the ImportPrices program. Also note that it has been made a function – this is not necessary but
it makes it possible to attach the code to the click event of as many buttons as you like. (For example, put a button on
the Menu form and type =ImportAndModify() into its Click Event in the Properties window.
Listing 4.7
Function ImportAndModify()
DoCmd.SetWarnings False
DoCmd.OpenQuery "qryDeleteRecords"
ImportPrices
DoCmd.OpenQuery "qryUpdatePrices"
DoCmd.OpenQuery "qryAppendPrices"
DoCmd.SetWarnings True
End Function
The setting to suppress warnings caused by Action Queries can be found in File Menu, Options, Client Settings, Editing,
Confirm. Clear the checkbox for Action Queries to make them run silently. However this setting will persist and you my
not want that. DoCmd has a SetWarnings method which can be set to False while the program runs, and set back to True
for interactive use after End Sub.
Opening/Running Queries
The above code uses DoCmd.OpenQuery method, which is fine for Select queries and Action queries. There is also a
DoCmd.RunSQL method which takes an SQL statement as its argument, e.g.,
Public Sub DoSQL()
Dim strSQL As String
strSQL = "UPDATE Employees " & _
"SET PayRate = PayRate + 5 " & _
"WHERE Dept = 'MK'"
DoCmd.RunSQL strSQL
End Sub
which would increase the PayRate field in the Employees table by 5 for records with MK in the Dept field. You can only
use RunSQL with Action queries and SQL DDL statements, not Select queries.
For the sake of completion you could use the Execute method of the Database instead of DoCmd.RunSQL, e.g,
CurrentDb.Execute strSQL
8
Access Programming: Part 4
Importing Text
The TransferText method is available to import text files.
Listing 4.8
Sub ImportText()
On Error GoTo ImportText_Err
DoCmd.TransferText acImportDelim, "", "Pay", "U:\pay.csv", True
‘ change path
ImportText_Exit:
Exit Sub
ImportText_Err:
MsgBox Error$
Resume importText_Exit
End Sub
9
Access Programming: Part 4
Appendix: Built-in functions
Both Access and VBA provide functions that you can use in procedures. Functions are classified into categories: some
work with Date and Time fields; amongst others there are also Text Manipulation functions; Data Type Conversion
functions; Domain Aggregate functions; Mathematical functions; Financial functions; General purpose functions.
One way to see the categories of
functions is to open the
Expression Builder, expand
Functions, then expand Built-In
Functions. The middle column
shows the categories.
If you choose a function from
the right hand column you can
click the Help button to access
further information.
Functions are diverse in the tasks they perform and there is no one way to use them all. Each function must be looked at
on the basis of its own characteristics. The majority take one or more arguments, some like Date take no arguments –
some like the financial functions might need some research on your part before you could use them correctly.
You can also look for help on specific functions is the Contents page of VB Help; expand Visual Basic Language
Reference, expand Functions, then expand the appropriate alphabetical category
The following tables list selected functions. The examples can be tested in the Immediate Window
Date and Time functions
Function
Date() or Now()
Day()
Month()
Year()
Weekday()
Description
returns the current system date
returns an Integer representing the day
from a Date value
returns an Integer that represents the
month of a Date value
returns an Integer that represents the year
of a Date value
returns day of the week (Sunday = 1)
Example (You can use # instead of “ in examples)
Date()
Day(“31-Jan-2004”)
returns 31
Month(“31-Jan-2004”)
returns 1
Year(“31-Jan-2004”)
returns 2004
Weekday(“31/01/2004”)
returns 7
DateAdd()
DateDiff()
DateSerial()
returns a data to which a specified time
interval has been added; all three
arguments are required
DateAdd("yyyy", 2, date())
returns an Integer representing the
difference between two dates
DateDiff(“d”, Date(), ”31-Jan-2004”)
returns a date given three integers
that specify year, month, day in that order
DateSerial(2004, 1,31)
returns the date 2 years from today
DateAdd("ww", -3, ”4 Mar 2004”)
returns the date 3 weeks earlier than 4th March
2004
returns the number of days between 31-Jan-2004
and the current date
returns 31/01/2004
10
Access Programming: Part 4
Nested functions
You can have a function within another, e.g.,
Month(Date())
will return the current month as an Integer
To evaluate a nested function start with the innermost brackets, i.e. Date() – this evaluates to the current date, then the
Month function extracts the month part of the date.
Text manipulation functions
LCase()
UCase()
Len()
Trim()
LTrim()
RTrim()
Left()
Right()
Mid()
returns lower case version of a string
returns upper case version of a string
returns number of characters in a string
removes leading and trailing spaces
from a string
removes leading spaces from a string
removes trailing spaces from a string
returns leftmost characters of a string
returns rightmost characters of a string
returns specified number of characters
from specified starting point in a string
LCase(“Abcd”)
UCase(“Abcd”)
Len(“ABCDE”)
Trim(“ ABC “)
LTrim(“ ABC”)
RTrim(“ABC “)
Left(“ABCDEF”,3)
Right(“ABCDEF”,3)
Mid("Lindsay Davenport",9,4)
returns Dave.
The function returns the string starting at the ninth
character which is four characters long
StrComp()
Val()
Chr() or Chr$()
compares two strings for equivalence
and returns the integer result of
comparison: 0 if strings are equal, -1 if
strings are different
returns numeric value of a string in
data type appropriate to the format of
the argument.
returns a character associated with a
character code
StrComp(“ABC”,”abc”)
returns 0. The strings are equal.
Val(“123.45”)
returns 123.45 (i.e. a number)
chr(34) returns " (double quote)
chr(42) returns * (asterisk)
Mathematical functions
Abs()
returns absolute value of numeric field
Int()
returns numeric value with the decimal
fraction truncated
returns a number rounded to a specified
number of decimal places.
syntax is:
Round()
Round(expression, numdecimalplaces)
where the numdecimalplaces argument
Rnd()
is
optional; the function returns an integer
if it is omitted.
creates random number (single)
between 0 and 1
Abs(-1234.5)
returns 1234.5
Int(13.9)
returns 13
round(12.459, 2)
returns 12.46
round(12.9)
returns 13
to generate a random whole number between 0 and
9 you can use:
Int(Rnd()*10)
Explanation – the Rnd() function generates a single
between 0 and 1; this value is multiplied by 10, so
we have a number between 0 and 9; finally the Int()
function removes the fractional part leaving a
whole number.
Other maths functions are: Atn, Cos, Exp, Fix, Log, Sgn, Sin, Sqr, Tan
General-Purpose functions
Iif()
returns one value if the result of an expression
is True, another if the result is False
Iif([Exam mark] >=50, “Pass”,”Fail”)
11
Access Programming: Part 4
IsNumeric()
IsNull()
IsDate()
IsEmpty
VarType
Format
returns True if argument is one of the number
field data types, otherwise returns False
returns True if argument is Null, otherwise
returns False
indicates whether an expression can be
converted to a date
indicates whether a variable (of type variant)
has been assigned a value
Returns an Integer indicating the subtype of a
variable. Refer to Help for the list of constants
with which the function is used.
This function can be used to format numbers,
strings and dates.
IsNumeric([Field Name])
IsNull([Field Name])
IsDate("21 feb 98")
returns True
IsEmpty(variable name)
e.g. you could test if a variant is an integer
as follows:
If VarType(varname) <> vbInteger Then
Format(5459.4, "£##,##0.00")
returns £5,459.40
Format("HELLO", "<")
returns "hello"
Format(now, "dd/mm/yy hh:mm:ss AMPM")
returns 28/10/04 01:00:16 PM
Format(date,"Long Date")
returns 28 October 2004
IIf() (Immediate If)
An Immediate If is like a one line If ... Then ... Else statement; it can be used in places that an If ... Then ... Else statement can’t,
e.g., in a query which is Control Source for an object on a form or report.
The way it works is that it takes three arguments:
the first argument is a condition to be evaluated
the second argument is the action if the condition evaluates to True
the third argument is the action if the condition evaluates to False
As an example, if the value of a field on a report is 0 you might not want to print a zero. You could use the Iif statement
in such a situation:
Expr1: IIf([DiscountValue]=0," ","Discount: " & [DiscountValue])
IIf statements can often be hard to read – it helps to split the argument into three components
[DiscountValue]=0
the condition is True if DiscountValue evaluates to zero
""
action to take if True is to print a space, i.e. nothing shows up on report
"Discount: " & [DiscountValue])
action to take if condition is false is to print the string “Discount “ and
concatenate DiscountValue
Data Type Conversion functions
CInt()
converts a string to a number
CInt(“99”)
returns 99
rounds a fraction to an Integer
CInt(123.5)
returns 124
CLng()
rounds a fraction to a Long Integer
CCur()
CDate()
converts numeric value to Currency data type
converts string expression to Date
CStr
converts its argument to a string
CLng(100000.1)
returns 100000
CCur(123.5)
CDate("21Feb 98")
returns 21/02/1998
CStr(99)
returns “99”
Other conversion functions are: CBool, CByte, CDbl, CDec, CSng, and CVar. You may come across Str and Val functions:
these are older conversion functions that can be replaced with the more modern versions.
12
Access Programming: Part 4
Domain Aggregate functions
Domain Aggregate functions provide statistical information about sets of records (known as domains). In this respect
they are similar to the functions in Totals queries, which are known as SQL Aggregate functions. The syntax of Domain
Aggregate functions takes a little getting used to. The following example can be taken as representative:
DCount(expr, domain, [criteria])
where
expr
identifies the field for which you want to count records; it can be a field in a table, a control on a form
domain is a string expression identifying the set of records. It can be a table name or a query name
criteria is an optional string expression to restrict the range of data on which the function is performed
DCount()
DSum()
DAvg()
determines the number of records that
are in a specified set of records (a
domain).
calculates the sum of a set of values
in a domain
calculates the average of a set of
values in a domain
DCount("OrderNo", "SalesOrders")
DSum("ItemCost", "qryOrderDetails")
The third optional argument will take a string which is an
SQL Where clause without the word WHERE e.g.
DSum("ItemCost", "qryOrderDetails", "ProductID = '103' ")
DAvg("PayRate", "Employees"
You can use domain aggregate functions to supply summary information in a text box on an unbound form as follows:
Create a new form in design view without specifying any data source
Click the text box tool
Then click on the form; you will get
an unbound text box
In the text box type the expression:
=DAvg("PayRate", "Employees")
Go to form view; the textbox should show the average pay rate of all employees in the Employees table;
all you need to do is format it as currency and type a meaningful caption.
The function gives a result equivalent to the query illustrated
right – domain aggregate functions are available however where
SQL aggregate functions are not, like on a form.
Add some more text boxes and try the other examples from the table.
Exercise:
modify the DAvg example to show average Payrate for employees in the MK department
13
Access Programming: Part 4
Timer function
If you are concerned with testing performance issues in your database, you can use the Timer function to time how long
something takes. For example, here is an example that times how long a query takes to run.
Public Function QueryRunTime(strQueryName As String) As Double
Dim Start As Double, Finish As Double
Start = Timer
DoCmd.OpenQuery strQueryName
Finish = Timer
QueryRunTime = Finish - Start
MsgBox strQueryName & " run time is " & QueryRunTime
End Function
Call the function like this, say in the Immediate window
?queryruntime("qryRegionalCustomers")
Getting the Discount value into the Invoice report
Working with reports can be incredibly tricky, especially where subreports are concerned – you don’t have to read this
section if you don’t want to. On the other hand it might shed some light on problems you have experienced in you own
work.
This is the way I got the discount value into the invoice. In query builder view of qryOrderDetails I have amended the way
DiscountValue is organised. The basic calculation is now in a column captioned DValue. The DiscountValue field, which is
to appear on forms/reports takes DValue and applies the Format function to it as illustrated.
(I suppose it could have been done all in one giant expression, but it would be almost impossible to read).
Close and save the query in the Query Builder.
Next open the report rptInvoice in design view, select the subreport control, then the subreport and click the Field List
button. DiscountValue should be present. Drag it to the subreport, and delete its label. Preview the report
the DiscountValue
field should be
visible
The field is nicely formatted, but leaves a problem if you don’t want zeros to appear on the invoice. Go back to
qryOrderDetails in query builder view and create an expression as illustrated (you can leave its caption as Expr1)
Close the Query Builder and save. Now in design view of the subreport use Expr1 instead of DiscountValue.
Here’s the evidence:
14
Access Programming: Part 4
Expr1
field
Obviously there’s more work to be done on the report e.g. subtract discounts from subtotal, charge VAT and so on.
15