Sie sind auf Seite 1von 142

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

INFOTECH
TRAINING CENTRE

MS-ACCESS

PROGRAMMING

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

Creating an Application..........................................................................................................................................3
Creating an Application on Your Own....................................................................................................................4
Command Bars: Menu Bars, Toolbars, and Shortcut Menus..................................................................................7
Adding Menus and Submenus to Command Bars..................................................................................................9
Working with Button Images on Command Bars.................................................................................................16
Visual Basic..........................................................................................................................................................21
Writing and Editing Code.....................................................................................................................................21
Creating Your First Function................................................................................................................................23
Visual Basic Fundamentals...................................................................................................................................27
Creating and Calling Procedures..........................................................................................................................29
IF Then Else..........................................................................................................................................................33
Do..Loops..............................................................................................................................................................33
Assigning Values to Controls and Properties at Run Time...................................................................................37
Using Pop-up Forms and Dialog Boxes...............................................................................................................38
Using a Custom Dialog Box to Collect Information............................................................................................39
Filtering and Sorting Data in Forms and Reports.................................................................................................42
Combo Boxes........................................................................................................................................................45
Populating Controls on a Form.............................................................................................................................48
Assigning Values to Controls on the Orders Form...............................................................................................48
Adding a Row to a Combo Box List.....................................................................................................................50
Working with Variables, Data Types, and Constants............................................................................................58
Declaring Variables...........................................................................................................................................58
Assigning and Retrieving Values..........................................................................................................................71
Working with Objects and Collections.................................................................................................................78
Working with Objects and Collections.................................................................................................................81
Declaring and Assigning Object Variables...........................................................................................................82
Working with Properties and Methods.................................................................................................................88
Responding to Events...........................................................................................................................................91
Opening and Closing a Form................................................................................................................................94
Using Data........................................................................................................................................................96
Working with Records and Fields.........................................................................................................................96
Creating a Recordset Object Variable...................................................................................................................98
Sorting and Filtering Records.............................................................................................................................102
Displaying Values of Controls and Properties....................................................................................................112
Assigning Values to Controls, Properties, and Variables....................................................................................114
Handling Run-Time Errors.................................................................................................................................116
Errors and Error Handling...................................................................................................................................118
Using Error Events..............................................................................................................................................119
Using On Error Statements.................................................................................................................................120

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

Creating an Application
After you become familiar with the Microsoft Access interface and learn how to create tables, queries, forms, and
reports, you can make your database easier to use by tying these objects together in an application. This chapter
explains what a Microsoft Access application is and presents an overview of the steps required to build one, either
with the Database Wizard or on your own. When you finish reading this chapter, you'll be ready to create your
first Microsoft Access application.
What Is a Microsoft Access Application?
People use a database to perform data management tasks, such as storing, retrieving, and analyzing data about
orders and customers. A Microsoft Access application is made up of the same objects as a Microsoft Access
database tables, queries, forms, reports, macros, and modules. The objects are stored in one or more Microsoft
Access database (.mdb) files. What makes an application different from a database is that the objects are tied
together into a coherent system. An application organizes related tasks so that the user can focus on the job at
hand, not on how the application works or on the program used to develop the application.
The keys to a Microsoft Access application are its objects, their properties, and the events that occur on forms.
Here's how it works:
An application consists of objects. Your application is made up of objects that users see and use directly (forms
and reports) and supporting objects that control how the forms and reports work (tables, queries, macros, and
modules). You build the forms and other objects in their respective Design views.
Objects have properties you can set You set objects' properties to make them look and behave the way you want.
For example, all forms have a Default View property that specifies whether a form should appear in Form or
Datasheet view. Once you set the property, the form opens automatically in the correct view. By setting properties,
you make your objects behave more intelligently.
Forms respond automatically to events When people use the forms in your application, their actions changing
data in a field, clicking a command button, moving the mouse are recognized by Microsoft Access as events.
Microsoft Access responds to these events automatically. For example, when a user changes the data in a text box,
Microsoft Access checks to make sure that the data is the correct data type. When a user clicks a command button,
Microsoft Access displays the button so it appears pressed in.
You can add your own, custom response to an event You can use either a macro or an event procedure to add the
response you want to an event. An event procedure is a Visual Basic procedure you write that's attached to a form,
report, or control; Microsoft Access runs it when a specified event occurs. You specify in the event procedure or
macro what you want to take place when the event occurs. For example, you can change object properties, open
or close objects, or manipulate data. You use event properties to determine whether Microsoft Access runs a
macro or an event procedure in response to an event. For example, to have a macro run in response to a command
button's Click event, you set the button's OnClick event property to the name of the macro.
You can extend Visual Basic with external libraries In addition to writing your own event procedures, you can
use Visual Basic to call external procedures in Microsoft Access library databases (MDAs) and in dynamic-link
libraries (DLLs). For example, you can enable or disable menu commands by calling functions in the DLLs that
are part of Microsoft Windows.
Where Is My Application's "Brain"?
If you've developed database applications using other products, you may expect to write a Main program in the
Visual Basic language that makes your application work. The Main program would be the application's brain;
you'd use it to tell the objects how to appear and react, and how to process the data, much as the manager of an
office delegates different projects.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

With Microsoft Access, the objects manage themselves by responding to events that occur within your
application. There is no Main program. For example, suppose you want something to happen when the user clicks
a button. You don't need code that checks to see whether the user clicks the button. You attach the code to the
button so it's run when the Click event occurs. When the event occurs, Microsoft Access runs your code
automatically.

Creating an Application on Your Own


If the Database Wizard doesn't offer the type of application you want, or if you've already created database objects
you want to transform into an application, you can build an application on your own. Begin by creating tables,
and then create other objects in the order that feels most natural to you. Here's one approach that works well:
Step One: Plan the tables and relationships Before you begin creating actual objects, take time to plan the tables
and relationships you need for your application. Analyze your data and break it down into distinct tables. You can
find good examples in the Northwind sample application, or in a database you create with the Database Wizard.
Step Two: Create the tables and add a few records of sample data to each table You can create tables by using the
Table Wizard, or by typing either in a datasheet or in table Design view. To ease data entry between related tables,
add Lookup fields to tables on the "many" side of one-to-many relationships when you create the tables. After
your table structure is complete, entering sample data makes it easier for you to see whether your forms and
reports display the data you want.
Step Three: Create one of the forms in your application Start with a form that's used to enter most of the data in
the application. You can use a form wizard to create the form for you, then you can modify the design or layout of
the form that the wizard creates.
Step Four: Add the features you need to make the form work Create the macros, event procedures, or functions
you need to support the form.
Step Five: Add other forms and reports When the first form stores and displays data the way you want it to, start
adding other forms and reports. Work on one object at a time, testing its features until you know it works correctly
before going on to the next object.
Step Six: Connect the objects with buttons, hyperlinks, menus, and toolbars Provide a path for users to navigate
through your application. Add command buttons, hyperlinks, custom menus, and custom toolbars to your forms,
and add macros or Visual Basic code to respond to your users' input. You may want to add pop-up forms that act
as dialog boxes to collect input from your users.
Step Seven: Designate a startup form and put the final touches on your application In this final step, designate
the form that appears when the user starts your application and specify other options such as the caption that
appears in the title bar.
The Central Role of Forms
In a Microsoft Access application, forms aren't just screens for entering and editing data they make up most of
your application's interface. To your users, forms are the entire application. By building your application around
forms, you can control the flow of your application through the events that occur on the form.
Forms provide an additional behind-the-scenes benefit when you use macros or event procedures to tie your
objects together. In addition to using forms as your application's interface, you can use fields on hidden forms to
store and pass values from form to form or from operation to operation. For example, suppose you want to
provide your users with the ability to enter a range of dates in a dialog box and then print a series of reports based
on that range of dates. The dialog box is a form that you create. When the user clicks OK in the dialog box, you
hide the form rather than close it. Now the dates that the user enters are available to the macro or code that prints
each of the reports.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

Designing a Startup Form


One effective approach to organizing and presenting tasks in an application is to use a startup form as a control
center for the application. The startup form provides navigation to all tasks; it's the first form you see when you
start the application. One example of this type of form is the Switchboard form that the Database Wizard creates.
In an application you create from scratch, however, you have more flexibility in how your application starts.
When designing a startup form, try to bring your users' primary tasks as close to the surface of the application as
possible. For example, if your application focuses on one task, you could use a form for that particular task as
your startup form. The Orders sample application is a good example of such an application. Its startup form is the
Orders form. Sales representatives can complete their primary task and navigate to related tasks from this form.
To see this form, open the Orders application in the Samples subfolder of the Office folder.
If, in contrast, your application contains numerous forms and reports and you can't predict which one a user
would want to use first, you can start the application by displaying a form that acts more like a switchboard. This
type of form often uses command buttons to group-related objects and tasks.
These are only a few of many possible approaches you can use for an application's startup form. After you devise
a plan that will help your users navigate between forms and tasks in your application, and after you design your
startup form, you're ready to define how you want the application to start.

Connecting Your Application's Objects


Simply getting the individual objects in your application up and running isn't enough. You need to connect the
dots tie the objects in your application together into a coherent system that's designed for the particular tasks your
users are trying to accomplish. You connect the dots by running macros or event procedures in response to the
events that occur on the forms or reports in your application.
The following illustrations show how you can tie separate objects together into a custom system for entering
orders. In the illustrated application, the Orders form is the startup form. When you start the application, the
Orders form opens automatically. People who take orders can do all their tasks with this one form.
Perform Bulk Updates with Action Queries, Not Code
In database applications, you often need to automate bulk updates of records in order to delete and archive
inactive customer records, for example. If you're an experienced application developer, you may have found that
operations like this can require writing many lines of code. But in a Microsoft Access application, you can usually
manipulate sets of records more easily with action queries than with code. The action queries make-table, update,
append, and delete are often the most efficient way to change data. Running a predefined delete query, for
example, is a much more efficient way to remove a lot of records than looping through records in a Visual Basic
procedure. You can define the query graphically in Design view, and then run it from the Database window. You
can also write a macro that uses only two actions SetWarnings and OpenQuery to run the query. Then you can
attach the macro to a menu command or a command button, or specify it as the event property setting for any
event that occurs on a form.

Referring to Objects and Their Values


The preceding section showed that you can tie objects together not only by using one object to open another, but
also by passing data from one object to the next. For example, when you click the Print Invoice button on the
toolbar in the Orders sample application, you want the order displayed in the Orders form to be selected in the
PrintInvoiceDialog form.
How do you do it? Using a macro or an event procedure, you identify the value you want to pass from the open
object usually the value of a control on a form. Because you may have many forms open at one time in your
application, Microsoft Access requires a specific syntax structure to identify the control that contains the value
you want.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

Tip When you name the tables, fields, and other objects in your database, keep in mind that you'll use these
names to refer to the objects elsewhere in your application. Although descriptive names for objects with spaces
are easier to recognize than more compact names, they can be difficult to use in expressions, SQL statements, and
Visual Basic code. If you are creating a database that uses these advanced features, you may want to use short,
consistent names that don't contain spaces and are easier to remember and type for example, field names such as
LastName and Phone.
To refer to an object or a value, you start with an object or a collection of objects and identify each element in
turn. A collection groups objects, such as the forms or controls in the current database, as shown in the following
illustration.
To refer to an element of a collection, such as the Forms collection, use the ! operator. To refer to the Orders form,
for example, use the following expression:
Forms!Orders
Each form contains a collection of controls. To refer to the OrderID control on the Orders form, for example, use
the following expression:
Forms!Orders!OrderID
You refer to a control to get, set, or pass its value.
To refer to a property, use the . (dot) operator before the property name. You use this operator before properties,
methods, actions, and collections. For example, to refer to the Visible property of the Orders form, use the
following expression:
Forms!Orders.Visible
To refer to the OrderID control's Visible property on the Orders form, use the following expression:
Forms!Orders!OrderID.Visible
Tip If you want help referring to an object or property, use the Expression Builder. With the Expression Builder,
you can simply select the object you want from a list, and the Expression Builder writes the reference for you
with all the operators in the correct places. To display the Expression Builder, right-click where you want to enter
the expression, and then click Build on the shortcut menu.
Providing Navigation to Tasks and Objects
As an application developer, you determine how people navigate through your applications and complete their
tasks. Navigating in a Microsoft Access application usually means moving from control to control within a form
or switching between forms.
Navigation within a form Within a form, navigation from control to control should follow the natural flow of the
task your users shouldn't have to bounce from the top of the form to the bottom and then back to the top again in
order to complete one task. Group controls logically so that users can focus on one area of the form at a time.
Define how a user moves from control to control on a form with the keyboard by setting the AutoTab, TabStop,
and TabIndex properties. If controls are used to find or filter records, place them in the form header or footer to
show they're separate from the other fields in the current record.
Navigation from one form to another form and completing tasks Nothing says you must use one approach over
another to navigate from form to form or to complete a task; however, it's a good idea to provide your users with
ways to navigate that are common to other Windows-based applications. It's also a good idea to be consistent

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

within your application to do similar tasks on different forms in similar ways. Here are some common navigation
devices used in Windows-based applications:

Command buttons
You can use a command button on a form to carry out a task (for example, saving the current record) or to open
another form used for a related task. Command buttons have the advantage of being highly visible. For most
common actions, you can create buttons on your forms by using the Command Button Wizard, which creates an
event procedure for you. If you want to change or add to what the button does, you can edit the event procedure
the wizard creates.

Hyperlinks
As an alternative to command buttons that use macros or event procedures for navigation, you can use hyperlinks
to navigate to other forms or objects in your database. To create a hyperlink, you set the HyperlinkAddress and
HyperlinkSubAddress properties of a label, image control, or command button. Once you set these properties,
users can click the text, picture, or button to navigate to the specified object. You can also use hyperlinks to open
documents and other files on a local hard drive, a local area network, an internal Web (intranet), or the Internet. If
you use hyperlinks to navigate to objects within a Microsoft Access database or to other Microsoft Office
documents, your users can use the Web toolbar to navigate between documents and objects they've previously
opened with hyperlinks.

Menu commands
You can put your own menus and commands on a form or report's menu bar. Commands on menus occupy less
room than command buttons on forms, but they're also less visible users must open the menu to see them. You can
also put common commands on shortcut menus, which users access by right-clicking a form or a control on a
form.

Toolbar buttons
You'll often want to create buttons that provide shortcuts to the commands on your menu bar. You can add custom
toolbar buttons to your toolbars, as shown in the following illustration.

Key assignments
You can associate a macro with a key combination so that Microsoft Access runs the macro whenever users press
that key combination in your application. This approach is even less visible than menu commands your users must
know the key assignment in order to use it. However, it has the advantage of being available throughout your
application, not just on a particular form. If there's an action your users may want to perform anywhere in your
application (for example, printing the current record), you can give them a consistent way to do it by using a key
assignment.

Command Bars: Menu Bars, Toolbars, and Shortcut Menus


In Microsoft Access 97, the internal workings of menu bars, shortcut menus, and toolbars have been unified into a
single object called a command bar. Because they share the same underlying technology, you have more
flexibility when you customize existing Microsoft Access menu bars, toolbars, and shortcut menus, and when you
create new ones for your application.
Menu bars and toolbars are two ways to present commands on command bars: a menu bar typically presents dropdown menus of commands as text, and a toolbar typically presents commands as buttons. A shortcut menu is a
subset of a menu bar, presenting one menu of commands when a user right-clicks most objects and controls in
Microsoft Access. You can create new shortcut menus and associate them with the forms, reports, or controls in
your application.
Because menu bars, toolbars, and shortcut menus share the same internal workings, you can use most of the same
controls on all three. For example, in addition to their typical controls, a top-level menu bar can use buttons, a
drop-down menu can use combo box controls, and a toolbar can use drop-down menu buttons that display text-

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

only commands. In this respect, command bars can be too flexible. Avoid confusing the users of your application
by putting menu bar and toolbar controls in unusual locations or configurations. Instead, model your custom
command bars after the ones used in Microsoft Access.
The simplest way to work with command bars is by using the Customize dialog box (View menu, Toolbars
submenu). You can use the Customize dialog box to customize existing command bars or to create new ones. New
command bars can contain existing commands or new commands that run the event procedures or macros you
define.
By default, users can customize command bars. Users can also make menu bars and toolbars free-standing by
dragging them into the work area, or they can dock them to the sides or the bottom of the work area. In addition,
users can resize or hide menu bars and toolbars. You can prevent users from customizing all command bars in
your application in the Startup dialog box (Tools menu). You can prevent users from customizing, moving, or
resizing an individual menu bar or toolbar by setting options in the Toolbar Properties dialog box, which is
available from the Customize dialog box.
You can also use the objects, methods, and properties of the CommandBars collection in Visual Basic code to
create and work with command bars.
Changes to existing command bars are always stored in the Windows Registry in the
\HKEY_CURRENT_USER\Software\Microsoft\Office\8.0\Access\Settings \CommandBars key. When you create
a new command bar, it is saved in a system table in the current database and is only available in that database.
However, if you create an add-in database and store new command bars in it, they are available from any
installation of Microsoft Access that has the add-in installed.
Once you have customized an existing menu bar or toolbar, or created a new one, you can attach it to a form or
report by specifying it in the MenuBar or Toolbar property for the form or report. To attach a shortcut menu to a
form, report, or control, specify it in the ShortcutMenuBar property for the form, report, or control. You can also
specify a global menu bar or a global shortcut menu to be available throughout your application by using the
Startup dialog box (Tools menu).
Note Previous versions of Microsoft Access use the AddMenu and DoMenuItem actions in macros to create
custom menu bars and shortcut menus and to carry out standard Microsoft Access menu commands. If you
convert a database created in a previous version of Microsoft Access to Microsoft Access 97, the macros that
contain the AddMenu and DoMenuItem actions will still run; however, Microsoft Access 97 converts
DoMenuItem actions in the macros to the new RunCommand action. For more information on the RunCommand
action, search the Help index for "RunCommand action."
Although these menu bar macros created with a previous version of Microsoft Access will run from the forms,
reports, or controls they are attached to, they won't be available in the Customize dialog box. However, you can
create a Microsoft Access 97-style menu bar or shortcut menu from a menu bar macro created in a previous
version of Microsoft Access. For more information on how to do this, search the Help index for "macros, using to
work with menus."
Creating New Menu Bars, Toolbars, and Shortcut Menus
You create all command bars, whether they are menu bars, toolbars, or shortcut menus, by using the Customize
dialog box (View menu, Toolbars submenu). You create the different kinds of command bars by setting their
properties and, if necessary, by setting properties for the commands within them to control how they appear and
behave. The following procedures show how to create new menu bars, toolbars, and shortcut menus.
Creating New Command Bars and Setting Their Properties
The first step in creating a new menu bar, toolbar, or shortcut menu is to create and name an empty command bar,
set its type, and set other properties that control how it can be used.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

To create an empty command bar and set its properties


1 On the View menu, point to Toolbars, and then click Customize.
2 On the Toolbars tab, click New.
3 In the Toolbar Name box, type a name for the new command bar, and then click OK.
Microsoft Access creates an empty, floating command bar, which you can specify to be a toolbar, menu bar, or
shortcut menu.
4 In the Customize dialog box, click Properties to display the Toolbar Properties dialog box.
5 In the Type box, click the kind of command bar you want to create:
To create a menu bar, click Menu Bar.
To create a toolbar, click Toolbar.
To create a shortcut menu, click Popup.
Note The Popup setting of the Type property is used for a shortcut menu because in the command bar object
model, menus (on both menu bars and toolbars), submenus, and shortcut menus are all of this type. However, if a
command bar has its Type property set to Popup, the Customize dialog box user interface only allows you to work
with it as a shortcut menu. Additionally, as soon as you set a new command bar's Type property to Popup, it
disappears because a shortcut menu can't display as free-standing. To add commands to your custom shortcut
menu, you must display it. For more information, see "Adding Menus and Submenus to Command Bars" later in
this chapter.
6 If you are creating a menu bar or toolbar, in the Docking box, click the kind of docking you want to allow.
These settings don't apply to shortcut menus.
the new command bar from appearing on the Toolbars submenu (View menu)
Show On Toolbars Menu
8 If you are creating a menu bar or toolbar, clear any of the following check boxes whose default behavior you
want to change. These settings don't apply to shortcut menus.
9 When you are finished specifying properties for the new command bar, click Close. To continue working with
your new command bar, leave the Customize dialog box open.
At this point, you have an empty command bar of the type you specified in step 5. If you created a menu bar, you
need to add menus to it and then add commands to those menus. If you created a shortcut menu, you need to add
commands to it. If you created a toolbar, you need to add buttons or other controls to it.

Adding Menus and Submenus to Command Bars


If you are creating a menu bar, you must add and name top-level menus, and then add commands to those menus.
If you want an additional menu to open from a menu command, you can add a submenu to it. You can also add
menus and submenus to toolbars, and submenus to shortcut menus.
Note In previous versions of Microsoft Access, you had to add a single top-level menu to create a shortcut menu.
This is not necessary in Microsoft Access 97.
To add a menu or submenu to a command bar

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

10

1 If the Customize dialog box isn't open, point to Toolbars on the View menu, and then click Customize.
2 If the menu bar, toolbar, or shortcut menu you want to work with isn't displayed, open it.
Note To display a custom shortcut menu, select the Shortcut Menu check box in the Toolbars list of the
Customize dialog box. On the Shortcut Menu toolbar, click Custom and then click the name of your custom
shortcut menu.
3 In the Customize dialog box, click the Commands tab.
4 In the Categories box, click New Menu.
New Menu appears in the Commands box.
5 Drag New Menu from the Commands box to your menu bar or toolbar:
To create a top-level menu, drag New Menu to the top row of your menu bar or toolbar.
To create a submenu, you must have an existing top-level menu, or you must be adding a new menu to a shortcut
menu. Drag and hold New Menu over a top-level menu (or shortcut menu) until it drops down, then drag New
Menu to the location you want and release the mouse.
6 Right-click New Menu on your menu or toolbar, and then type the name for your menu in the Name box.
Tip You can create an access key for your menu names so that users can access your menus with the keyboard.
To do so, type an ampersand (&) in front of the letter you want to use. For example, to use F as the access key for
a menu named File, type &File. The F in your menu name is underlined and users can open the menu by pressing
ALT+F.
7 To further customize your new menu or submenu, set other properties in the Control Properties dialog box. To
display the Control Properties dialog box, right-click the new menu or submenu, and then click Properties.
Once you have added all the menus and submenus to your command bar, you have the basic framework to contain
the commands that you want to be available. If you are creating a menu bar, you have top-level menus and
perhaps some submenus. If you are creating a toolbar, you may have added top-level menu buttons and possibly
submenus within them. If you are creating a shortcut menu, you may have added submenus. The next step is to
add commands to your menus and submenus, or buttons that carry out commands to your toolbar.
Adding Existing Menu Commands, Buttons, and Other Controls to Command Bars
By using the Customize dialog box, you can add any existing Microsoft Access menu command, toolbar button,
or other control to your new command bar. This includes all standard Microsoft Access menu commands and
toolbar buttons, as well as drop-down combo box controls, such as the Font box, and special formatting controls,
such as the Fill/Back Color and Special Effect controls.
To add an existing Microsoft Access menu command, button, or other control
1 If the Customize dialog box isn't open, point to Toolbars on the View menu, and then click Customize.
2 If the menu bar, toolbar, or shortcut menu you want to work with isn't displayed, open it.
3 In the Customize dialog box, click the Commands tab.
4 In the Categories box, click the category that contains the menu command, button, or other control you want to
add to your command bar. For example, to add a command that appears on the File menu, click File. To add an
entire menu of commands at once, click Built-in Menus.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

11

5 Drag the menu, menu command, button, or other control you want from the Commands box to the appropriate
location on your command bar.
To add the command or control to a menu or submenu, drag it and hold the mouse over the menu or submenu
name until it drops down, and then drag the command or control where you want it on the menu or submenu and
release the mouse.
Note If you place the command or control in the wrong location, you can drag it to the correct location.
6 To further customize your command bar, you can change the images that appear on toolbar buttons and next to
menu commands, and you can set other properties that determine how your menu commands, buttons, and other
controls appear and work. To display a menu of customization options, right-click the menu command, button, or
control.

Creating a Command or Button That Runs a Macro


To create a command or button that runs a macro, create the macro, and then drag its name from the Customize
dialog box to your command bar. For example, you can create a macro that uses the OpenForm action to open a
form and set the Data Mode argument to Add. Add the macro to a menu, and when a user clicks the custom
command, Microsoft Access runs the macro, opening a blank form ready to add a new record.
To add a custom command or button that runs a macro
1 Create a macro that performs the action you want.
2 If the Customize dialog box isn't open, point to Toolbars on the View menu, and then click Customize.
3 If the menu bar, toolbar, or shortcut menu you want to work with isn't displayed, open it.
4 In the Customize dialog box, click the Commands tab.
5 In the Categories box, click All Macros.
6 Drag the macro you want to run from the Commands box to the appropriate location on your command bar.
To add a command that runs the macro to a menu or submenu, drag it and hold the mouse over the menu or
submenu name until it drops down, and then drag the command or control where you want it on the menu or
submenu and release the mouse.
Note If you place the command in the wrong location, you can drag it to the correct location.
7 To further customize your command bar, you can change the images that appear on toolbar buttons and next to
menu commands, and you can set other properties that determine how your menu commands and buttons appear
and work. To display a menu of customization options, right-click the menu command or button.
What Is Visual Basic?
Visual Basic is the programming language for Microsoft Access. You use it for the same reason you use macros to
tie the objects in your application together into a coherent system. The difference is that Visual Basic provides
more power and a finer degree of control than you get by using macros alone.
Some Familiar Territory for the Seasoned Programmer
Visual Basic is a modern programming language that strongly resembles most of the popular, structured
programming languages. If you're a Pascal or C programmer, you'll find all the program structures you're used to
loops, If...Then...Else statements, Select Case statements, functions, and subroutines with only superficial

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

12

differences. With all its improvements from earlier versions of Basic, Visual Basic retains its English-like flavor
and ease of use.
When to Use Visual Basic Instead of Macros
With Microsoft Access, you can accomplish many tasks with macros or through the user interface that require
programming in other database systems. So, when do you turn to Visual Basic? It depends on what you want to
do.
Why Use Visual Basic?
You'll want to use Visual Basic instead of macros if you want to do any of the following:
Make your application easier to maintain Because macros are separate objects from the forms and reports that
use them, an application containing a large number of macros that respond to events on forms and reports can
become difficult for you, the application developer, to maintain. In contrast, when you use Visual Basic to respond
to events, your code is built into the form or report's definition. If you move a form or report from one database to
another, the Visual Basic code built into the form or report moves with it. (Code is a general term for the
statements you write in a programming language.)
Create your own functions Microsoft Access includes many built-in, or intrinsic, functions such as the IPmt
function that calculates an interest payment. You can use these functions to perform calculations without having to
create complicated expressions. Using Visual Basic, you can also create your own functions to either perform
calculations that exceed the capability of an expression or replace complex expressions you've written in your
application.
Mask error messages When something unexpected happens in your application and Microsoft Access displays an
error message, the message can be quite mysterious to your application's users, especially if they aren't familiar
with Microsoft Access. Using Visual Basic, you can detect the error when it occurs and display your own
message, or you can have your application do something else. Applications used by a variety of people almost
always require some Visual Basic code for handling errors.
Create or manipulate objects In most cases, you'll find that it's easiest to create and modify an object in that
object's Design view. In some situations, however, you may want to manipulate the definition of an object in
code. Using Visual Basic, you can manipulate all the objects in a database, including the database itself. A
Microsoft Access wizard is a good example of an application that creates and modifies objects using code. For
example, the Form Wizard is a collection of Visual Basic functions that creates a form according to the
specifications supplied by the user.
Perform system-level actions You can use the RunApp action in a macro to run another Windows-based or MSDOS-based application from your Microsoft Access application, but you can't use a macro to do much else
outside Microsoft Access. Using Visual Basic, you can check to see if a file exists on the system, use Automation
or dynamic data exchange (DDE) to communicate with other Windows-based applications such as Microsoft
Excel, and call functions in Windows dynamic-link libraries (DLLs).
Manipulate records one at a time You can use Visual Basic to step through a set of records one record at a time
and perform an operation on each record. In contrast, macros work with entire sets of records at once.
Pass arguments to your code An argument is a value that supplies the additional information that some actions
require. You set arguments for macro actions in the lower part of the Macro window when you create the macro;
you can't change them when the macro is running. With Visual Basic, however, you can pass arguments to your
code at the time it runs. You can even use variables for arguments something you can't do in macros. This gives
you a great deal of flexibility in how your code runs.
Tip Although you can have both macros and Visual Basic code in your application, you may find it easier to use
Visual Basic exclusively once you get started programming. If you have macros in your application, Microsoft

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

13

Access can automatically convert them to event procedures or modules that perform all the equivalent actions in
Visual Basic code.
In form or report Design view, use the Convert Macros To Visual Basic command (Tools menu, Macro submenu).
For global macros that aren't attached to a specific form or report, use the Save As/Export command (File menu)
to save the macro as a module. For more information, search the Help index for "macros, converting."
Why Use Macros?
After reading all the reasons for using Visual Basic, you may wonder if there are any reasons left for using
macros. However, macros do have their place in many applications. Macros are an easy way to take care of
simple details such as opening and closing forms, showing and hiding toolbars, and running reports. Because you
specify options for each action in the lower part of the Macro window, there's little syntax to remember, and
developing applications can often be faster than with Visual Basic.
In addition to the ease of use macros provide, creating a macro is the only way to make global key assignments.
How an Event-Driven Application Works
An event is an action recognized by a form, report, or control. Each type of object in Microsoft Access
automatically recognizes a predefined set of events. When you want a form, report, or control to respond to an
event in a particular way, you can write a Visual Basic event procedure for that event.
Here's what happens in a typical event-driven application:
1. A user starts the application and Microsoft Access automatically opens the startup form specified in the Startup
dialog box.
2. The startup form, or a control on the startup form, receives an event. The event can be caused by the user (for
example, a keystroke), or by your code (for example, an Open event when your code opens a form).
3. If there is an event procedure corresponding to that event, it runs.
4. The application waits for the next event.
Note Some events automatically trigger other events. For example, when the MouseDown event occurs, the
Click and MouseUp events immediately follow.
Event-Driven vs. Traditional Programming
In a traditional procedural program, the application rather than an event controls the portions of code that are run.
It begins with the first line of code and follows a defined pathway through the application, calling procedures as
needed.
In event-driven applications, a user action or system event runs an event procedure. Thus, the order in which your
code is run depends on the order in which events occur; the order in which events occur is determined by the
user's actions. This is the essence of graphical user interfaces and event-driven programming: The user is in
charge, and your code responds accordingly.
Because you can't predict what the user will do, your code must make a few assumptions about "the state of the
world" when it runs. It is important that you either test these assumptions before running your code or try to
structure your application so that the assumptions are always valid. For example, if your application assumes that
a text box has text in it before the user clicks a command button, you can write code to enable the command
button only when the Change event for the text box occurs.
Creating Your First Event Procedure

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

14

You write Visual Basic code in units called procedures. A procedure contains a series of Visual Basic statements
that perform an operation or calculate a value. An event procedure is a procedure that runs in response to an event.
This section shows you how to create a simple event procedure that makes a command button and a text box work
together on a form. The following illustration provides an example of how this interface may appear to the user.
Note This example assumes that you have control wizards turned off in the form's Design view. To do this, make
sure the Control Wizards tool in the toolbox is not pressed in.
Because an event procedure is part of the design of the form or report that runs it, the first step is to create the
form and add the controls. In this case, you create a form that isn't based on a table or query and add a text box
and a command button. Your code will refer to these controls by name, so it's a good idea to set the Name
property of each control on your form to something more descriptive than the default settings that Microsoft
Access gives them. For example, Microsoft Access names the text box Text0 and the command button
Command1. To be more descriptive, you could name the text box Message and the command button OK.
Tip When you name the tables, fields, and other objects in your database, keep in mind that you'll use these
names to refer to the objects elsewhere in your application. Although descriptive names for objects with spaces
are easier to recognize than more compact names, they can be difficult to use in expressions, SQL statements, and
Visual Basic code. Consider using short, consistent names that don't contain spaces and are easy to remember and
type for example, field names such as LastName and Phone.
After you've created your form and its controls and set their properties, you're ready to write your first event
procedure in the Module window.
To write the event procedure for the OK command button
1 In Design view, right-click the object (form, report, section, or control) for which you want to write an event
procedure, in this case, the OK command button. On the shortcut menu, click Build Event.
Microsoft Access displays the Choose Builder dialog box.
2 In the list box, click Code Builder, and then click OK.
Microsoft Access opens the Module window and creates a template for the default event procedure of the object
you selected, in this case, the Click event procedure. (The default event procedure is the one for which Microsoft
Access thinks you're most likely to add code.) The template for the OK command button's Click event procedure
is shown in the following illustration.
3 Enter the code for the event procedure between the Sub and End Sub statements. For the OK command
button's event procedure, enter the following code:
Message = "Hello, World!"
This line of code sets the Message text box to the text string, "Hello, World!"
4 Save and close the module.
When you save the module, Microsoft Access sets the command button's OnClick event property to [Event
Procedure], as shown in the following illustration.
Now that you've written the event procedure, you're ready to run it. To do this, you make the event happen on the
form.
To run the OK_Click event procedure

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006


1

15

Click the Form View button on the toolbar to switch to Form view.

2 Click OK.
The event procedure runs and the text "Hello, World!" appears in the text box.
Working with a Form or Report Module
The "Hello, World!" example in the previous section shows you how to create a new event procedure by using the
Build Event command on an object's shortcut menu. In addition to this method, Microsoft Access provides a
variety of other ways to open a form or report module and create or modify its event procedures.
To open a form or report and its module at the same time
In the Database window, select the form or report, and then click the Code button on the toolbar.
Microsoft Access opens the form or report and its module. You can also use the Code button in a form or report's
Design view to open its module.
In the previous section, you learned how to open the default event procedure. You can create or open any event
procedure directly from the property sheet.
To create or open any event procedure
1 Open the form or report in Design view.
2 Display the property sheet by right-clicking the form, report, section, or control, and then clicking Properties
on the shortcut menu.
3 In the property sheet, click the Event tab.
4 Select the property box for the event procedure you want to open.
5

Click the Build button to the right of the property box.

If the event property already has an event procedure associated with it, Microsoft Access opens the Module
window and displays the event procedure.
If the event property is empty, Microsoft Access displays the Choose Builder dialog box. In the list box, click
Code Builder, and then click OK. Microsoft Access opens the Module window and creates a template for the
event procedure.
Creating a Command or Button That Runs a Visual Basic Function Procedure
For the greatest flexibility, you can create a Visual Basic Function procedure and run it from a menu command or
toolbar button. To do so, add the Custom command to your command bar, and then customize it to run your
Function procedure.
Important You can run only Visual Basic Function procedures from a command bar, not Sub procedures.
To add a custom command or button that runs a Visual Basic Function procedure
1 Create a Visual Basic Function procedure that performs the action you want.
2 If the Customize dialog box isn't open, point to Toolbars on the View menu, and then click Customize.
3 If the menu bar, toolbar, or shortcut menu you want to work with isn't displayed, open it.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

16

4 In the Customize dialog box, click the Commands tab.


5 In the Categories box, click File and then drag the Custom command from the Commands box to your
command bar.
6 Right-click the new command on your menu or toolbar, and then click Properties.
Microsoft Access displays the Control Properties dialog box.
7 In the Caption box, delete the current name, and type the new name for your command.
Tip You can create an access key for your command so that users can access it with the keyboard. To do so, type
an ampersand (&) in front of the letter you want to use. For example, to use I as the access key for a Print Invoice
command, type Print &Invoice. The I in your command name is underlined and users can carry out the command
by pressing ALT+I.
8 In the On Action box, type an expression to run your Visual Basic Function procedure. The expression must
use the following syntax: =functionname(). For example, to run a function named PrintInvoice, you'd type
=PrintInvoice().
9 To further customize your command bar, you can change the images that appear on toolbar buttons and next to
menu commands, and you can set other properties that determine how your menu commands and buttons appear
and work. To display a menu of customization options, right-click the menu command or button.

Working with Button Images on Command Bars


Most Microsoft Access command bar controls have a button image that is displayed when the control is on a
toolbar, and sometimes appears next to the control when it's on a menu. You can customize these button images
by:
Changing the image to one of a set of predefined images.
Copying a control's image and pasting it into another control.
Copying an image from a graphics program and pasting it into another control.
Editing the image by using the Button Editor dialog box.
Whether a control appears with a button image, text, or both is determined by the setting of the control's Style
property and by whether the control is on a toolbar or a menu. The following table describes how the Style
property works.
Note A control's Caption property text is identical to the text in the Name box on the shortcut menu that appears
when you right-click a control while the Customize dialog box is open.
Note By default, some Microsoft Access command bar controls don't have a button image associated with them
and won't display an image regardless of the Style property setting. However, you can add an image by using one
of the methods described in the following procedure. Also, some Microsoft Access command bar controls have
their Style property set to Text Only (In Menus) by default so that they don't display their image on menus. If you
want to display the image on menus, set the Style property to Default Style.
To customize a button image on a command bar control
1 If the Customize dialog box isn't open, point to Toolbars on the View menu, and then click Customize.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

17

2 If the menu bar, toolbar, or shortcut menu that contains the control you want to work with isn't displayed, open
it.
Right-click the control that has the image you want to use, and then click Copy Button Image. Right-click the
control whose image you are customizing, and then click Paste Button Image.
Copy and paste an image from a graphics program
Open the image you want to copy in a graphics program. Select and copy the image (preferably a 16 x 16 pixel
image or portion). Switch back to Microsoft Access. Right-click the control, and then click Paste Button Image.
Edit the control's current button image
Right-click the control, and then click Edit Button Image. In the Button Editor dialog box, you can change the
color and shape of the image, adjust the image's position on the control, and preview your changes to the image.
When you have finished editing the button image, click OK.
Reset a control to use its original button image
Right-click the control and then click Reset Button Image.
4 When you have finished working with the button image, click Close.
Setting Properties for Command Bar Controls
Microsoft Access provides some additional menu and control properties that you can use to further customize
menus, menu commands, and toolbar buttons. You set each of these properties in the Control Properties dialog
box.
Note Depending on the kind of control you're working with, some properties will not be available.
To set control properties for a menu, a menu command, or a toolbar button
1 If the Customize dialog box isn't open, point to Toolbars on the View menu, and then click Customize.
2 If the menu bar, toolbar, or shortcut menu that contains the control you want to work with isn't displayed, open
it.
3 Right-click the control, and then click Properties.
4 In the Control Properties dialog box, set the properties you want. The following table describes each property.
The name that is displayed for the command. This is identical to the text entered in the Name box on a menu or
control's shortcut menu.
Shortcut Text
The text that is displayed next to a menu command and that indicates its shortcut key; for example, CTRL+P.
This property only creates display text to prompt the user. To define the shortcut key, you must create an
AutoKeys macro as described in "Making Key Assignments" later in this chapter.
ToolTip
The text of the ToolTip that appears when a user rests the pointer on the control. If this setting is blank, Microsoft
Access uses the text from the Caption property as the ToolTip.
On Action
The name of a macro or Visual Basic Function procedure that runs when a user clicks the control. When using a
Function procedure, you must enter the name of procedure as an expression, using the syntax =functionname().
Style
Controls how a command is displayed. The Style property settings are also available from a control's shortcut
menu.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

18

The help file that contains the What's This Tip topic specified by the Help ContextID property.
Help ContextID
The context ID of the topic to display as a What's This Tip for this command.
Parameter
An optional string associated with the control that your application can reference or set. For example, the Visual
Basic Function procedure specified in the On Action property can refer to the Parameter property to determine
how it works, or the Parameter property can be used to store information about the control, much like the Tag
property. The Parameter property isn't generally used by built-in menu and toolbar controls. However, the
Parameter property for a menu command or toolbar button used to add an ActiveX control is set to the ActiveX
control's class identifier(CLSID), which is the Registry value that uniquely identifies that control. If you delete or
modify the CLSID, the command or button won't work. Similarly, the Parameter property for a menu command or
toolbar button used to open a particular database object is set to the name of the object.
Tag
An optional string that can be used later in event procedures.
Begin A Group
Select this check box to indicate the beginning of a group of controls. On menus, a separator bar appears above a
command that has this property set. On toolbars, a vertical separator bar appears in front of the command. If you
resize a floating toolbar and the entire group of controls doesn't fit on the current line, the whole group is bumped
to a new line.
You can also set and read each of the properties in Visual Basic code. Most of the corresponding Visual Basic
property names are the same as those listed in the preceding table, although the words are concatenated; for
example, ShortcutText property. There are two exceptions: the Visual Basic properties that correspond to the
ToolTip and Begin A Group properties in the Control Properties dialog box are ToolTipText and BeginGroup.
Attaching a Custom Menu Bar to a Form or Report
The easiest way to create a menu bar that's attached to a form or report is to create a new menu bar and then
specify that menu bar in the form or report's MenuBar property, so Microsoft Access displays the menu bar
whenever the form or report is active.
To attach a custom menu bar to a form or report
1 Create a custom menu bar as described earlier in this chapter.
2 Open the form or report in Design view.
3

On the toolbar, click Properties.

4 In the MenuBar property box, enter the name of the menu bar you created in step 1.
You can attach the same menu bar to more than one form or report.
Attaching a Custom Shortcut Menu to a Form, a Control on a Form, or a Report
You can attach custom shortcut menus to a form, a control on a form, or a report. After you create the shortcut
menu, set the ShortcutMenuBar property for the form, control, or report to the name of the shortcut menu.
Microsoft Access displays the custom shortcut menu whenever a user right-clicks the form, control, or report.
To attach a custom shortcut menu to a form, a control on a form, or a report
1 Create a custom shortcut menu as described earlier in this chapter.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

19

2 Open the form or report in Design view.


3 Click the form, control, or report to which you want to attach a custom shortcut menu.
4

On the toolbar, click Properties.

5 In the ShortcutMenuBar property box, enter the name of the shortcut menu you created in step 1.
You can attach the same shortcut menu to more than one form, control, or report.
Specifying a Global Menu Bar or Shortcut Menu
You can specify a menu bar to use throughout your application by using the Startup dialog box.
To specify a global menu bar to display when your application starts
1 Create a custom menu bar as described earlier in this chapter.
2 On the Tools menu, click Startup.
3 In the Menu Bar box, enter the name of the menu you created in step 1.
4 Click OK.
The next time you start your application, Microsoft Access displays your custom menu bar instead of the default
menu bar.
You can change the global menu bar while your application is running, without having to restart your computer.
To do so, set the MenuBar property of the Application object to the name of the menu bar.
Using Custom Toolbars
You can use one or more custom toolbars in an application. Create the toolbars you want, and then use the
appropriate method to display your custom toolbars:
If your application has only one custom toolbar, simply use the Toolbars command (View menu) to display it
and it will appear each time your application starts.
If your application has different custom toolbars for different forms or reports, you can specify a toolbar for each
form or report in the form or report's Toolbar property.
If you need to work with more than one custom toolbar for a form or report, or if you want to hide or show
Microsoft Access built-in toolbars, you can use the Visible property of the CommandBar object in Visual Basic
code or the ShowToolbar action in macros to hide and show the toolbars.
If you want your application to display only custom toolbars, you can hide all built-in toolbars by clicking the
Startup command (Tools menu) and clearing the Allow Built-in Toolbars check box.
Attaching a Custom Toolbar to a Form
When using the Orders application, Northwind sales representatives want to click a button on the toolbar to print
the invoice for the current order. You can create a custom toolbar for the Orders form with a button that prints
invoices, and use the custom toolbar instead of the built-in toolbar.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

20

Step One: Create the custom toolbar Create a custom toolbar for the Orders form that includes a button that runs
the PrintInvoice macro, as well as any other commands you want to provide, such as the Cut, Copy, and Paste
commands in the Edit category of the Customize dialog box (View menu, Toolbars submenu). Name this toolbar
Orders Form Toolbar.
Note The custom toolbar attached to the Orders form in the Orders sample application includes a Design View
button. You can use this button to easily switch between Form view and Design view while you're looking at the
sample application. However, if you don't want users to switch to Design view in your own application, don't put
the Design View button on your custom toolbars.
Step Two: Set the form's Toolbar property to the name of the custom toolbar Open the Orders form in Design
view, open the property sheet for the form, and then enter Orders Form Toolbar in the Toolbar property box.
Note There is no need to create event procedures for the Activate and Deactivate events of the form to show and
hide toolbars as was required in previous versions of Microsoft Access. Setting the Toolbar property to a custom
toolbar automatically hides the built-in Form View toolbar when your form is opened, and hides your custom
toolbar when a user closes the form or switches to another form.
Preventing Users from Customizing Your Application's Command Bars
You can control whether users can add or remove commands on all of the menus and toolbars in your application.
To prevent users from customizing all command bars in an application
1 On the Tools menu, click Startup.
2 In the Startup dialog box, clear the Allow Toolbar/Menu Changes check box.
3 Click OK.
The next time your application starts, users won't be able to add or delete menu or toolbar commands. However,
users will still be able to move and resize toolbars.
Note If you want to prevent users from customizing an individual command bar, you can clear the Allow
Customizing check box in the Toolbar Properties dialog box. For more information, see "Creating New Command
Bars and Setting Their Properties" earlier in this chapter.
Working with Command Bars in Code
You can work with menu bars, toolbars, and shortcut menus in Visual Basic code by using the properties and
methods of the CommandBars collection and the objects associated with it. In the command bars object model,
each menu is a CommandBar object. This is true of menus and submenus on all three types of command bars. For
example, to refer to the Tools menu on the standard menu bar, use the following statement:
CommandBars!Tools
The following code uses the Add method and several command bar control properties to add a new, hidden Print
Invoice command to the bottom of the Tools menu.
Private Sub AddInvoiceCommand()
Dim cb As CommandBar, ctl As CommandBarControl
' Set a reference to the Tools menu.
Set cb = CommandBars!Tools
' Create new CommandBarControl object on the Tools menu
' and set a reference to it.
Set ctl = cb.Controls.Add(Type:=msoControlButton)

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

21

' Set properties of the new command.


With ctl
.BeginGroup = True
.Caption = "Pri&nt Invoice"
.FaceID = 0
.OnAction = "=PrintInvoice()"
.Visible = False
End With
End Sub
You refer to a command bar control by name within a command bar's Controls collection. You must use the exact
case and characters specified for the command's Caption property, but you can omit the ampersand (&) that
designates the command's access key. For example, to use the Execute method to carry out the Options command
on the Tools menu, you use the following statement:
CommandBars!Tools.Controls![Options...].Execute
To refer to a command on a submenu, you refer to the submenu as a member of the Controls collection of the
menu that contains it. For example, to use the Execute method to carry out the Add-in Manager command on the
Add-ins submenu, which is on the Tools menu, you use the following statement:
CommandBars!Tools.Controls![Add-ins].Controls![Add-in Manager].Execute
The following code makes the hidden Print Invoice command created with the AddInvoiceCommand Sub
procedure visible.
Private Sub ShowInvoiceCommand()
Dim cb As CommandBar, ctl As CommandBarControl
' Set a reference to the Tools menu.
Set cb = CommandBars!Tools
' Set a reference to the control.
Set ctl = cb.Controls![Print Invoice]
' Make the control visible.
ctl.Visible = True
End Sub

Visual Basic
When you're looking for a level of power and control over your application that goes beyond what you can find in
the Microsoft Access interface, Visual Basic is the place to find it. This chapter shows you how to use Visual
Basic to respond to events on forms and reports, and how to create custom functions. It also introduces you to the
fundamentals of the Visual Basic programming language.

Writing and Editing Code


Editing the code in a module is much like editing text with any text editor. The blinking vertical line, or insertion
point, marks the place on screen where you insert typed or pasted text.
As you begin using the Module window, you can take advantage of the following features designed to help you
write Visual Basic code efficiently:
Automatic statement building When you type certain Visual Basic elements, Microsoft Access automatically
tries to assist you in writing code by displaying a drop-down list of appropriate choices for the code element
you've typed. For example, if you type an object variable, and follow it with a period to indicate that you intend to

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

22

enter a method or property, Microsoft Access automatically displays a list of the methods and properties that
apply to the object. (You use methods to perform operations on an object. You use properties to determine or
change characteristics of an object.)
To complete the statement you're typing, you can either click an item in the list or continue typing your code. If
you continue typing code, the list displays the closest match to what you've typed. To enter the selected item in
the list at any time, press TAB. To make the list disappear, press ESC.
Note You can press ENTER to enter the selected item in the list. However, pressing ENTER also moves the
cursor to the next line, so you'll have to return to the line to enter any arguments or additional information.
To use automatic statement building, select the Auto List Members check box on the Module tab of the Options
dialog box (Tools menu).
When the Auto List Members check box is selected, the statement-building lists appear automatically as you type.
However, you may also want to explicitly cause Microsoft Access to display them, especially for lines of code
you typed previously and want to edit. To display the list of methods and properties for an existing object, rightclick the existing method or property name (to the right of the period) and then click List Properties/Methods on
the shortcut menu.
Some methods take a constant as an argument. To display a list of available constants when you're entering
arguments for a method, right-click in the Module window and then click List Constants on the shortcut menu.
If you've typed part of a property, method, or constant and want Microsoft Access to finish typing it for you, click
Complete Word on the Edit menu.
Quick Info When you type a procedure or method name (followed by a space or an opening parenthesis), a tip
automatically appears underneath the line of code you're writing. The tip gives syntax information about the
procedure, such as the arguments you need to type to use it.
To display syntax tips, select the Auto Quick Info check box on the Module tab of the Options dialog box (Tools
menu).
In addition, you can view information on any variable, constant, or procedure in your code by right-clicking the
item and then clicking Quick Info or Parameter Info on the shortcut menu.
Automatic syntax checking As you move the insertion point off a line, Visual Basic checks the syntax of that
line and displays a message if it finds an error. To enable automatic syntax checking, select the Auto Syntax
Check check box on the Module tab of the Options dialog box (Tools menu).
Drag and drop If you want to move code you've written from one place to another in a module or between
windows, you don't need to bother with the Copy and Paste commands. Just select the code you want to move,
and then drag it to the new location.
To enable drag-and-drop editing, select the Drag-Drop In Editor check box on the Module tab of the Options
dialog box (Tools menu).
Undo command
Microsoft Access keeps track of the changes you make when editing code. By clicking the Undo button one or
more times, you can reverse any changes you've made to a module since you opened it.
Note When several people are using the same database, they use separate versions of the forms, reports, and
modules in the database. If one person changes code, the others must close and reopen the database in order to see
those changes. More than one person can edit the same form, report, or module at the same time. If you attempt to
save a form, report, or module that has already been changed by someone else, Microsoft Access warns you that
the module has changed since you opened the database.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

23

Navigating Between Procedures


When you're editing code in the Module window, you can move between procedures in the module by pressing
CTRL+PAGE DOWN and CTRL+PAGE UP. In addition, by selecting objects and their procedures or events in
the Object and Procedure boxes, you can go directly to a procedure or create a new procedure.
Object box Displays the name of the selected object. Click the arrow to the right of the Object box to display a
list of all objects associated with the form, then click an object in the list to display its procedures or events in the
Procedure box.
Procedure box When (General) appears in the Object box, the Procedure box displays the name of the current
procedure. When an object name appears in the Object box, the Procedure box displays the name of the event for
the current event procedure. Click the arrow to the right of the Procedure box to display all the events for an
object. Events that have event procedures appear in bold in the list. In the following illustration, for example, the
Procedure box displays a list of all the events for the OK command button, and the Click event appears in bold.
Although the Module window normally displays one procedure at a time, you can also view all the procedures in
a module at once. To switch between Procedure view and Full Module view, click the Procedure View and Full
Module View buttons in the lower-left corner of the Module window, as shown in the following illustration.
Using Bookmarks
When working with a large application, it's easy to lose your place as you move between modules and procedures.
To keep track of portions of code that you're working on, you can set a bookmark to mark your place. To set a
bookmark on the current line of code, point to Bookmarks on the Edit menu, and then click Toggle Bookmark. A
blue square in the left margin of the Module window indicates that your bookmark is set.
To return to bookmarks in a module, point to Bookmarks on the Edit menu, and then click Next Bookmark or
Previous Bookmark. To clear all bookmarks in all modules, click Clear All Bookmarks on the Bookmarks

Creating Your First Function


If you discover that you're repeatedly using the same expression in forms, reports, or queries, you may want to
write a custom function that calculates that expression, and then use the function in place of the expression. For
example, suppose that you often need to calculate the date of the first day of the next month (perhaps this is the
date that payment is due or that shipments go out). You can calculate this date with the following expression.
= DateSerial(Year(Now), Month(Now) + 1, 1)
However, this complicated expression is easy to mistype. Instead of typing this expression, you could substitute a
custom Function procedure that performs this calculation. Writing a Function procedure to perform a calculation
has several significant advantages over using the equivalent expression. Using a Function procedure, you can:
Be sure that the calculation is performed the same way every time, without the risk of a typing mistake.
Modify the calculation by changing it in only one place (the module in which the function is defined) rather than
in every place the calculation is used.
Perform complex operations, such as If...Then logic or looping, which are difficult or impossible to handle in a
simple expression.
Handle errors in ways that you define.
Include comments to document complicated expressions.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

24

This section shows you how to create a simple function that calculates the date of the first day of the next month.
You'll use this function to set the value of the BillingDate text box on the Orders form in the Orders sample
application.
If you want to use this function in other forms and reports, you'll want to create a standard module to store it in.
You create a standard module in the same way you create and open other database objects.
To create a standard module
In the Database window, click the Modules tab, and then click New.
Microsoft Access displays a new module in the Module window.
Note When you open a new module, Microsoft Access automatically includes two Option statements in the
Declarations section, as shown in the preceding illustration. These statements tell Microsoft Access how to sort
data when running code and whether to warn you if you don't declare variables. For more information, search the
Help index for "Option."
To create a new function
1 Below the Option Explicit statement (or any empty line in a module), type Function followed by a space and
the name you want to give the function. In this case, name your new function FirstOfNextMonth.
Note It's a good idea to give your functions relatively short names that describe their purpose or the value they
return. Function names can't contain spaces or punctuation marks. For more information on names in Visual
Basic, see "Naming Conventions" later in this chapter.
2 Press ENTER.
When you press ENTER, Microsoft Access scans your typing, checks it for obvious errors, formats it according to
a consistent set of rules for capitalization and spacing, and displays it again. This occurs every time you enter a
new line in the Module window. Microsoft Access also adds a blank line and an End Function statement. The End
Function statement is always the last line in a function.
Note that Microsoft Access adds a set of parentheses after the name of the function. Use these parentheses to
enclose any arguments the function takes, if you decide that the function should take arguments.
Performing Calculations in a Visual Basic Function
You perform calculations in Visual Basic the same way you perform calculations elsewhere in Microsoft Access
by using an expression. The difference is in the way you specify where the result of the expression goes. When
you create an expression for a control on a form or for a field in a query, the result of that expression is assigned
to that control or that field.
When you perform a calculation in Visual Basic, however, it isn't obvious where the results should go. You have
to explicitly assign a destination to the expression. In the case of a function, you want the result of the calculation
to be the value returned by the function, so you assign the calculation to the name of the function.
To make a function return the result of a calculation, add an expression to the function that assigns the calculation
to the name of the function. For the FirstOfNextMonth function, you add the following line of code between the
Function and End Function statements.
FirstOfNextMonth = DateSerial(Year(Now), Month(Now) + 1, 1)
Compiling Your Procedure

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

25

Before you can run a procedure you've written, Microsoft Access must compile it. When it compiles a procedure,
Microsoft Access makes a final check for errors and converts the procedure into executable format. Because
Microsoft Access checks the syntax of each line as you enter it, your procedures compile very quickly.
You don't have to explicitly compile your procedures. If you've written a Function procedure, you can simply use
it in an expression. Then, when Microsoft Access evaluates the expression, it makes sure all the functions in the
expression have been compiled, compiling any uncompiled functions. If any of those functions use other
uncompiled procedures, Microsoft Access compiles those as well, and so on, until it has compiled all the code
required for it to evaluate the expression. If Microsoft Access discovers an error at any point during the
compilation process, it stops compiling and displays an error message.
Although automatic compiling is convenient, you can encounter error messages when you aren't expecting them.
For example, if you write a function and then use it in a form without compiling it first, you may not discover an
error in the function until Microsoft Access attempts to compile it when you try to view data in the form.
To make sure that a procedure has been compiled, you can explicitly compile the code in your database.
To compile code in all currently open forms, reports, and modules
On the Debug menu in the Module window, click Compile Loaded Modules.
Microsoft Access compiles all procedures that are in open modules. If it encounters an error, Microsoft Access
stops compiling, displays a message, and highlights the line of code that contains the error.
To compile all code in the current database
On the Debug menu in the Module window, click Compile All Modules or Compile And Save All Modules.
Microsoft Access compiles all the procedures in the database. This may take time if you have a large number of
procedures or modules.
Tip If you click Compile And Save All Modules, Microsoft Access saves all the code in your database in its
compiled form. It's a good idea to save modules after you compile them, because this allows Microsoft Access to
run them more quickly when you first open them in
the future.
Using Your Function
If you've followed the steps in this section, you now have a working function that you can use in an expression
almost anywhere in Microsoft Access. You may want to use your new function:
In other Visual Basic procedures that you write.
In the expression that defines a calculated field in a form, report, or query.
In the expression that defines the criteria in a query or the condition in a macro.
The following procedure shows you how to create a calculated text box on the Orders form that shows the billing
date of the order. When order takers take a new order, this text box will use the FirstOfNextMonth function to
automatically display the first day of the next month as the order's billing date.
To display the result of a function in a calculated text box
1 Open the Orders form in Design view.
2 Add an unbound text box to the Orders form, and set its Name property to BillingDate.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

26

3 Set its ControlSource property to the following expression:


=FirstOfNextMonth()
Now, when an order taker begins to enter a new order, the BillingDate text box automatically displays the first
day of the month that follows the current month on the computer's system clock.
Note When you use a function in the property sheet, you need to include the parentheses after the function name.
If the function has required arguments, you must include them inside the parentheses. For more information, see
the following section, "Supplying Arguments to Your Function."
Supplying Arguments to Your Function
Functions often take one or more arguments values that you supply when you call the function and that the
function uses to calculate the value it returns. Many of the functions supplied with Microsoft Access take
arguments. The functions you write can take arguments as well.
For example, the FirstOfNextMonth function currently returns the first day of the month that follows the current
month on the computer's system clock. This works fine when an order taker enters a new order, but it's not what
should be displayed in the BillingDate text box for orders that were taken in previous months. Instead, the value
in the BillingDate text box should be the first day of the month that follows the value in the OrderDate text box.
You can change the function so that it accepts an argument and then calculates the first day of the month
following a date you pass to that argument. You specify the arguments for a function by placing them inside the
parentheses that follow the function name.
Function FirstOfNextMonth (dtmAny As Date) As Date
FirstOfNextMonth = DateSerial(Year(dtmAny), Month(dtmAny) + 1, 1)
End Function
In the ControlSource property box of the BillingDate text box, you pass the function the value in the OrderDate
control, so that the function always returns the first day of the month following the month that the order was
taken.
Adding Comments to Your Procedure
Whenever you create new procedures or modify existing code, it's a good idea to add comments that describe
what the code does. Comments don't change what your code does, but they help you and other programmers
understand it and they make your code considerably easier to maintain.
Each line of a comment begins with an apostrophe ( ' ). This symbol tells Visual Basic to ignore any words that
follow on that line. You can enter comments on a line by themselves, as shown in the following code, or at the end
of a line of code.
Function FirstOfNextMonth (dtmAny As Date) As Date
' This function calculates and returns the date of
' the first day of the month following the date passed by
' the argument.
' Note that this works even if Month(dtmAny) = 12.
FirstOfNextMonth = DateSerial(Year(dtmAny), Month(dtmAny) + 1, 1)
End Function

Visual Basic Fundamentals

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

27

In a simple application, you may need to use Visual Basic only to create event procedures and simple functions, as
shown in the previous sections. However, as your applications get larger and more sophisticated, you'll want to
use the full power of the Visual Basic language. This section lays out the fundamental rules for writing Visual
Basic code in Microsoft Access.

Standard Modules and Class Modules


You store your Visual Basic code in modules in a Microsoft Access database. Modules provide a way to organize
your procedures.
Your database can contain two types of modules:
Standard modules You use standard modules to store code you may want to run from anywhere in the
application. You can call public procedures in standard modules from expressions, macros, event procedures, or
procedures in other standard modules.
To create a new standard module, you can either click New on the Modules tab in the Database window, or you
can click Module on the Insert menu.
Class modules You use class modules to create your own custom objects. The Sub and Function procedures that
you define in a class module become methods of the custom object. The properties you define with the Property
Get, Property Let, and Property Set statements become properties of the custom object.
To create a new class module, click Class Module on the Insert menu. Saved class modules appear on the
Modules tab in the Database window with saved standard modules. You can distinguish an open class module
from an open standard module by the title bar of the Module window the title bar for a class module always
includes the label Class Module.
Each form and report in your database can contain an associated form module or report module. Form and report
modules are also class modules, but you can't save them separate from the form or report that they belong to. The
class module that is associated with a form is especially useful because you can use it to create multiple instances
of a form.
Most frequently, you'll use a form or report module to contain event procedures associated with the form or
report. Each module can also contain other procedures that belong to the form or report.
A form or report module is part of the form or report's design. Thus, if you copy a form or report to another
database, its module goes with it; if you delete a form or report, its module is deleted as well. Microsoft Access
creates the form or report module automatically when you first add Visual Basic code to the form or report. All
you need to do is write the event procedures and other procedures you want to store in the module.

What's In a Module?
A module can contain:
Declarations
These are statements that define variables, constants, user-defined types, and external procedures. The
Declarations section of a module is separate from the procedures, and declarations in this section apply to every
procedure in the module. You can also define variables and constants within a procedure, in which case they apply
only to the procedure they're in.
Event procedures

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

28

These are Sub procedures that apply to a specific object; they run in response to a user or system event, such as a
mouse click. Event procedures are always stored with a form or report in the form or report module.
General procedures
These are procedures that aren't directly associated with an object or event. You can include general procedures in
a standard module or a class module. General procedures can be either Sub procedures (procedures that don't
return a value) or Function procedures (procedures that do return a value).
Event Procedures
When Microsoft Access recognizes that an event has occurred on a form, report, or control, it automatically runs
the event procedure named for the object and event. If you want to run code in response to a particular event, you
add code to the event procedure for that event.
When you create an event procedure (by using the procedures described earlier in this chapter), Microsoft Access
automatically creates a code template for the event and adds it to the form or report module. The name of an event
procedure for a form or report is a combination of the word "Form" or "Report," an underscore (_), and the event
name. For example, if you want a form to run an event procedure when it's clicked, use the procedure
Form_Click.
An event procedure for a control uses the same naming convention. For example, if you want a command button
named MyButton to run an event procedure when it's clicked, use the procedure MyButton_Click. If a control
name contains characters other than numbers or letters, such as spaces, Microsoft Access replaces those characters
with an underscore (_) in any event procedures for the control.
Important If you want to change the names of your controls, it's a good idea to do so before you start writing
event procedures for them. If you change the name of a control after attaching a procedure to it, you also must
change the name of the procedure to match the control's new name. Otherwise, Visual Basic can't match the
control to the procedure. When a procedure name doesn't match a control name, Microsoft Access makes it a
general procedure. You can find general procedures in the Module window by clicking (General) in the Object
box, and then clicking the procedure name in the Procedure box.
General Procedures
Microsoft Access runs event procedures in response to a particular event on a form, report, or control. A general
procedure, in contrast, runs only when you explicitly call it. A function, for example, is a type of general
procedure.
Why use general procedures? One reason is to create your own functions to automate tasks you perform
frequently. For example, you can create a function and then either create a custom menu command or custom
toolbar button that runs the function, or use the function in an expression.
Another reason to use general procedures is that you may want several different event procedures to perform the
same actions. A good programming strategy is to put common code in a separate general procedure and have
event procedures call it. This eliminates the need to duplicate code, making the application easier to maintain.
You can create general procedures either in a form or report module or in a standard module. If you want a
general procedure that's always available from anywhere in your application, place it in a standard module. If a
procedure applies primarily to a specific form or report, place it in the module for that form or report.

Creating and Calling Procedures


This section explains the syntax you use to create and call procedures in your application. Procedures can be
either Sub procedures or Function procedures:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

29

Sub procedures perform operations, but they don't return a value and can't be used in expressions. Sub
procedures can accept arguments. An event procedure is a Sub procedure that's attached to a form or report. When
Microsoft Access recognizes that an event has occurred on a form, report, or control, it automatically runs the
event procedure named for the object and event. For example, you can write an event procedure that sets the focus
to a specified control when the user exits another control.
Function procedures return a value, such as the result of a calculation. Because they return values, Function
procedures can be used in expressions. Like Sub procedures, Function procedures can accept arguments. For
example, you can write a function that calculates the first day of the month that follows a date you pass the
function in an argument. Then you can use that function in an expression on a form or report.
Sub Procedures
The syntax for a Sub procedure is:
[Private|Public] [Static] Sub procedurename [(arguments)]
statements
End Sub
The statements are the Visual Basic statements that make up the code you want to run each time the procedure is
called. The arguments are argument names, separated by commas if there are more than one. Each argument looks
like a variable declaration and acts like a variable in the procedure. The syntax for each argument is:
[Optional] [ByVal] variablename [( )] [As type]
Type can be any of the fundamental data types: Byte, Integer, Long, Single, Double, Currency, String, Boolean,
Date, Object, or Variant. If you don't provide a type, the argument takes the Variant type and can contain any kind
of data. Parentheses after variablename indicate that the argument is an array.
By default, arguments to a procedure are passed by reference, meaning that changing the value of the variable
changes it in the calling procedure as well. To pass arguments by value rather than by reference, use the ByVal
keyword.
When you call a Sub procedure, you specify the arguments you want the procedure to use. For example, the
following Sub procedure makes a beep sound the number of times you specify with the intBeeps argument.
Sub MultiBeep(intBeeps As Integer)
Dim intX As Integer, lngY As Long
For intX = 1 To intBeeps
Beep
For lngY = 1 To 100000
' Short delay between beeps.
Next lngY
Next intX
End Sub
The following statement calls the MultiBeep Sub procedure by using an intBeeps argument of 3, making a beep
sound three times.
MultiBeep 3
You don't enclose arguments in parentheses when you call a Sub procedure, as you do when you declare one.
Note To make your code more readable, you can pass arguments to Sub or Function procedures by name. For
example, the following call to the MultiBeep Sub procedure passes the intBeeps argument by name:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

30

MultiBeep intBeeps:=3
When you pass multiple arguments by name, you can include them in any order you want. For more information
on passing arguments by name, search the Help index for "named arguments."
Function Procedures
The syntax for a Function procedure is:
[Private|Public] [Static] Function procedurename [(arguments)] [As type]
statements
End Function
The arguments for a Function procedure work in exactly the same way as the arguments for a Sub procedure, and
have the same syntax. Function procedures differ from Sub procedures in three ways:
You enclose arguments in parentheses both when declaring and when calling a Function procedure.
Function procedures, like variables, have data types that determine the type of the return value.
You return a value by assigning it to the procedurename itself. The value returned by the Function procedure can
then be used as part of a larger expression.
For example, you could write a Function procedure that calculates the third side, or hypotenuse, of a right triangle
given the other two sides.
Function Hypotenuse (dblA As Double, dblB As Double) As Double
Hypotenuse = Sqr(dblA ^ 2 + dblB ^ 2)
End Function
You call a Function procedure the same way you call any of the built-in functions in Visual Basic. For example:
dblResult = Hypotenuse(dblWidth, dblHeight)
Tip If you aren't interested in the result of a Function procedure, you can call it without including parentheses or
assigning it to a variable, as you would a Sub procedure. For example, you can use the following code to call a
function called DisplayForm and ignore its return value:
DisplayForm strMessage
Using a Variable Number of Arguments
You can declare optional arguments in a procedure definition with the Optional keyword. An optional argument is
one that doesn't have to be passed every time you call the procedure. You must declare optional arguments after
any required arguments in the argument list. They can be of any type.
If you include an optional argument in a procedure definition, then you need to consider what happens in the
procedure when the argument is not passed. You can initialize an optional argument to a default value when you
declare the argument, so that if the optional argument is not included when the procedure is called, the default
value is used. If you don't initialize the argument to a default value, Microsoft Access initializes it as it would
initialize a variable of that type. If the argument is a number type, then it is initialized to zero. If it is a string, then
it is initialized to a zero-length string ("").

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

31

In the following example, if a value is not passed for the optional argument, this argument is assigned the default
value of 100.
Sub DisplayError(strText As String, Optional intNumber As Integer = 100)
If intNumber = 100 Then
MsgBox strText
Else
MsgBox intNumber & ": " & strText
End If
End Sub
You can call the procedure with either of the following lines of code.
DisplayError "Invalid Entry"
DisplayError "Invalid Entry", 250
Note If an optional argument is of type Variant, then you can use the IsMissing function to determine whether an
optional argument was included when the procedure was called. The IsMissing function only works with
arguments of type Variant.
To write a procedure that accepts an arbitrary number of arguments, use the ParamArray keyword to pass an array
of arguments with the Variant data type. With the ParamArray keyword, you can write functions like Sum, which
calculates the sum of an arbitrary number of arguments.
Function Sum(ParamArray varNumbers() As Variant) As Double
Dim dblTotal As Double, var As Variant
For Each var In varNumbers
dblTotal = dblTotal + var
Next var
Sum = dblTotal
End Function
You can call the Sum function with the following line of code.
dblSum = Sum(1, 3, 5, 7, 8)
Calling Procedures from Other Modules
Unless you specify otherwise, general procedures you create are public, meaning that you can call them from
anywhere in your application.
Tip If you know you will use a procedure only within its module, you should declare it with the Private keyword
to avoid confusion and to speed up compilation of your code. Event procedures are always declared with the
Private keyword, because they normally apply only to the form or report in which they are stored.
When you call a procedure that isn't in the current module, Microsoft Access searches other modules and runs the
first public procedure it finds that has the name you called. If the name of a public procedure isn't unique in the
database, you can specify the module it's in when you call the procedure. For example, to run a Sub procedure
called DisplayMsg that's stored in a module called Utility, you use the following code:
Utility.DisplayMsg
You can call procedures in a class module from other modules as well. To do this, specify the name of the class
module along with the procedure name. For example, to run a Function procedure called AddValues in a class
module called Class1 and print the result to the Debug window, use the following code:
Debug.Print Class1.AddValues

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

32

Because form and report modules are also class modules, you call a procedure in a form or report module in the
same way. To call a procedure in a form or report module, specify the name of the form or report module along
with the procedure name. The name of the form or report module includes the qualification Form_ or Report_
followed by the name of the form or report. For example, to run a Sub procedure called DisplayRecords that's
stored with the Orders form, use the following code:
Form_Orders.DisplayRecords
Alternatively, you can call a procedure in a class module or a form or report module by referring to an object
variable that points to an instance of either the class or the form or report. For example, the following code opens
an instance of the Orders form, and then runs the DisplayRecords procedure.
Dim frmOrders As New Form_Orders
' Declare an object variable.
frmOrders.Visible = True
' Open and display the Orders form.
frmOrders.DisplayRecords
' Call the form's procedure.
.
Set frmOrders = Nothing
' Close the Orders form.
By storing the DisplayRecords procedure in the Orders form module and making it public, you in effect create a
custom method of the Orders form.
Sub DisplayRecords
' This procedure can be called from another form
' to cause the Orders form to update itself.
.
.
.
End Sub

Using Variables
Often you store values temporarily when performing calculations with Visual Basic. For example, you may want
to calculate several values, compare them, and perform different operations on them, depending on the result of
the comparison. You want to retain the values so you can compare them, but because you need to store them only
while your code is running, you don't want to store them in a table.
Visual Basic uses variables to store values. Variables are like fields except that they exist within Visual Basic
rather than in a table. Like a field, a variable has a name, which you use to refer to the value the variable contains,
and a data type, which determines the kind of data the variable can store. Before you use a variable, it's a good
idea to declare it with a Dim statement, which tells Microsoft Access to set aside space for the variable.
For example, in the following procedure, dtmAny, dtmYear, and dtmMonth are variables with the Date data type.
Function DueDate(dtmAny As Date)
Dim dtmYear As Date, dtmMonth As Date
dtmYear = Year(dtmAny)
dtmMonth = Month(dtmAny) + 1
DueDate = DateSerial(dtmYear, dtmMonth, 1)
End Function
Naming Conventions
While you are writing your Visual Basic code, you declare and name many elements (Sub and Function
procedures, variables and constants, and so forth). The names of the procedures, variables, and constants you
declare in your Visual Basic code must:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

33

Begin with a letter.


Contain only letters, numbers, and the underscore character (_); punctuation characters and spaces aren't allowed.
Be no longer than 255 characters.
Contain no keywords.
A keyword is a word that Visual Basic uses as part of its language. This includes predefined statements (such as If
and Loop), functions (such as Len and Abs), methods (such as Close and FindFirst), and operators (such as Or and
Mod).
Controlling Execution
Visual Basic has several commands that help you control the execution of your code. For example, you can define
groups of statements that may or may not be run, depending on the value of an expression, or you can define
groups of statements that Visual Basic runs repeatedly.

IF Then Else
To run code conditionally, use the following statements:
If...Then
If...Then...Else
Select Case
To run one or more lines of code repetitively, use the following statements:

Do..Loops
Do...Loop
For...Next
While...Wend
Using the DoCmd Object to Perform Macro Actions
Many common actions you perform in an application don't have a corresponding command in the Visual Basic
language. To perform the equivalent of a macro action, use methods of the DoCmd object. The syntax for the
DoCmd object is:
[Application.]DoCmd.method [arguments]
Replace method with the name of a macro action. How many and what kind of arguments come after method
depends on the specific macro action you want to run. Specifying the Application object is optional; you can start
a line with the DoCmd object.
For example, the Close method, which corresponds to the Close action, takes two arguments that specify the type
and name of the database object you want to close. You use commas to separate the arguments when a method
takes multiple arguments.
DoCmd.Close acForm, "Add Products"

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

34

The first argument, acForm, is a Microsoft Access intrinsic constant specifying that the object to be closed is a
form. Microsoft Access automatically declares a number of intrinsic constants that you can use to represent a
variety of objects, actions, or data types. For example, you often use intrinsic constants with methods of the
DoCmd object to specify action arguments that you can enter in the lower part of the Macro window.
Some methods of the DoCmd object take optional arguments. If you leave these arguments unspecified, Microsoft
Access uses their default values. For example, if you leave both arguments for the Close method unspecified,
Microsoft Access closes the active database object (whatever it may be).
DoCmd.Close
If you omit an optional argument but specify an argument that follows that argument, you must include a comma
as a placeholder for the omitted argument. For example, the syntax for the MoveSize method is:
DoCmd.MoveSize [right] [, down] [, width] [, height]
The following code uses the default (current) settings for its right and down arguments, while using the specified
values for its width and height arguments.
DoCmd.MoveSize , , 5000, 3000
You can use methods of the DoCmd object to perform most macro actions, including the RunMacro action (which
runs an existing macro as if it were a procedure). However, eight macro actions have no equivalent methods of the
DoCmd object. In most cases, Visual Basic provides equivalent functionality with built-in statements or functions.
Using the RunCommand Method to Perform Menu Commands
Occasionally, you may want your application to perform a command that's on a Microsoft Access menu or
toolbar. To perform a built-in command just as if the user clicked it, use the RunCommand method. The syntax for
the RunCommand
method is:
RunCommand command
Command is a constant that corresponds to the Microsoft Access command you want to run. For example, the
following line of code performs the Options command, causing Microsoft Access to display the Options dialog
box:
RunCommand acCmdOptions

Using Forms to Collect, Filter, and Display Information


Forms that work well with each other make your application easy to use. This chapter presents a variety of
techniques you can use to automate your forms and to make individual forms work together as a unified
application.
Customizing a Command Button Created with a Wizard
Using the Command Button Wizard is a great way to create most general-purpose command buttons on your
forms. However, a command button created with the wizard may not do exactly what you want it to do. Rather
than creating a command button from scratch, you can use the Command Button Wizard to create the button and
an event procedure for its Click event, and then customize the way the button works by editing the event

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

35

procedure. For example, you can use the Command Button Wizard to create a button that opens a form, and then
you can edit the event procedure so that it opens the form for read-only access instead of for editing data.
The event procedure that the Command Button Wizard creates includes simple Visual Basic statements that
perform the action you specify. In addition, the wizard adds a simple method of error handling to respond
appropriately to run-time errors. If an error occurs, the error-handling code displays the Microsoft Access error
message and then exits the event procedure. The following illustration shows the event procedure the Command
Button Wizard creates for a command button that opens a form.
To change or enhance the way the button works, you edit the lines of code that are highlighted in the previous
illustration. Unless you want to change the way the event procedure handles errors, you don't need to edit any
other lines in the procedure.
Using Variables to Refer to Object Names
You may have noticed that the Command Button Wizard's event procedure uses a Dim statement to declare a
variable that holds the name of the form the event procedure opens. A variable is a placeholder that temporarily
stores data, such as the name of an object or the value in a field, while your procedure runs. Once you declare a
variable and assign it a value, you can use the variable throughout your procedure to refer to the value.
' Creates a variable that can hold a string value.
Dim strDocName As String
' Assigns the form name to it.
strDocName = "ProductsPopup"
' Opens the ProductsPopup form.
DoCmd.OpenForm strDocName
You don't have to use a variable to open a form. The following line of code performs the exact same action as the
previous three lines.
DoCmd.OpenForm "ProductsPopup"
If you refer to a form only once in a procedure, it's probably easiest to just use the form name directly, as in the
preceding line of code. Use a variable when you want to perform more than one operation on an object. In these
cases, using a variable makes your code easier to write and maintain. If you want to change the form that the
procedure opens, for example, you just change the form name once in the statement that assigns it to the variable.
Customizing a Command Button That Opens a Form
Customers sometimes ask Northwind sales representatives for further information about a product on their order.
You can provide a command button on the Orders form to open a pop-up form that shows details about the
product selected in the Orders subform.
Step One: Create the button that opens the form The Orders sample application includes a form named
ProductsPopup that displays details about Northwind products. You can open the Orders form in Design view and
use the Command Button Wizard to add a command button named Details that opens the ProductsPopup form.
The Command Button Wizard creates the command button and writes an event procedure for the command
button's Click event.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

36

However, the pop-up form displays details about a value on the subform, not the main form. Because the wizard
can't filter based on a subform value, you'll need to modify the command button's event procedure yourself so the
pop-up form shows information from the correct product.
Step Two: Open the event procedure In Design view, right-click the command button and then click Build Event
on the shortcut menu. Microsoft Access opens the form module and displays the button's Click event procedure.
Step Three: Change the code so it filters records You want to filter the records in the ProductsPopup form to
show the record whose ProductID is equal to the ProductID of the current record in the Orders subform. To do
this, you need to apply a filter to the form when you open it.
The Command Button Wizard automatically creates a variable in the Click event procedure that makes it easy for
you to set a filter for the form you're opening. The following line of code sets the strLinkCriteria variable to the
appropriate filter string.
strLinkCriteria = "ProductID = Forms!Orders!OrdersSubform!ProductID"
Add this line of code to the Click event procedure that opens the form, as shown in the following example:
Dim strDocName As String
Dim strLinkCriteria As String
strDocName = "ProductsPopup"
strLinkCriteria = "ProductID = Forms!Orders!OrdersSubform!ProductID"
DoCmd.OpenForm strDocName, , , strLinkCriteria
This event procedure passes the filter string you added (strLinkCriteria) as the wherecondition argument of the
OpenForm method that opens the form. Microsoft Access then automatically sets a filter on the ProductsPopup
form and makes the form display the correct record.
Compile your edited code and test it to make sure it works as expected. Now when you click the command button
on the Orders form, the ProductsPopup form opens, showing values in the record for the current product in the
Orders subform.
Note When the Details_Click event procedure opens the ProductsPopup form, the ProductsPopup form becomes
the active form. In addition to the code that opens the ProductsPopup form, the Details_Click event procedure
contains code that moves the focus from the pop-up form to the Orders form, so that sales representatives can
continue filling out the order without having to click the Orders form. This code uses the SetFocus method, first to
set the focus to the Orders form, and then to set the focus to the Orders subform rather than to the Details
command button (the last control on the form to have the focus). To see this code, open the Orders form in Design
view. Right-click the Details command button, and then click Build Event on the shortcut menu.
Keeping Two Forms Synchronized
You can keep the ProductsPopup form synchronized with the Orders form, so that when you move from record to
record in the Orders subform, the ProductsPopup form always shows details about the current product in the
subform. To do this, write an event procedure for the subform's Current event that sets the pop-up form's Filter
property. (You can also do this by writing a macro you specify as the subform's OnCurrent event property setting.)
To see this event procedure in the Orders sample application, look at the Form_Current event procedure for the
Orders subform. Open the Orders subform in Design view, and then click Code on the View menu to open its
form module. In the Object box, click Form, and then click Current in the Procedure box.

Assigning Values to Controls and Properties at Run Time


By assigning values to controls and properties on a form or report while your application is running, you can
make your forms and reports more responsive to your users' needs.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

37

For example, after a user selects a customer for an order, you may want to fill in the controls that contain the
shipping address with the customer's address. You do this by assigning values to the shipping controls. Or you
may want to disable some controls on the form in response to values the user enters in other controls. You do this
by setting the Enabled property of each control to False when the user enters the values.
In a macro, use the SetValue action, as shown in the following illustrations.
If the control to which you assign a value is located on a form or report other than the one that runs the macro,
you must enter its full identifier as the Item argument.
If you want to use Visual Basic to assign a value to a control or property, use a simple assignment statement. To
refer to a control that is located on the form or report from which you're running the event procedure, you don't
need to use its full identifier.
ShipCity = City
Details.Enabled = False
To refer to a control on a different form or report, use its full identifier.
Forms!ShipForm!ShipCity = City
Note Although properties with more than one word appear in the property sheet with spaces between words, you
must concatenate them when you refer to them in Visual Basic. For example, to refer to the RecordSource
property, which appears in the property sheet as Record Source, you would use the following line of code:
Forms!Orders.RecordSource = "OrdersQuery"
Disabling a Command Button at Run Time
As described previously in this chapter, the Orders form in the Orders sample application includes a command
button named Details that opens the ProductsPopup form. This pop-up form shows details about the current
product in the Orders subform. The Details button works fine as long as the ProductID field in the current record
of the subform contains a value. But if the user clicks in the new record of the Orders subform or starts a new
order, then the wherecondition argument in the button's DoCmd.OpenForm statement returns no record, and the
ProductsPopup form opens with no record in it. To avoid having the ProductsPopup form show no information,
you can disable the Details button when there's no value in the ProductID field for the current record of the
subform, and enable it when there is a value in the ProductID field.
For example, the Current event of the Orders subform occurs when the Orders form first opens, every time the
focus moves from one record to another on the subform, and every time you requery the subform. You can write
an event procedure for the Current event that disables the Details button when there's no value in the ProductID
field for the current record of the subform, and enables it when there is one.
Private Sub Form_Current()
If IsNull(ProductID) Then
Me.Parent!Details.Enabled = False
Else
Me.Parent!Details.Enabled = True
End If
End Sub
The statements in this event procedure use the Parent property of the Orders subform to refer to the Orders form.
If there's no value in the ProductID field in the subform, the first statement sets the Enabled property of the
Details button on the Orders form to False (the same as No in the property sheet). If there is a value in the field,
the second statement sets the Enabled property to True (the same as Yes in the property sheet).

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

38

When you use the Parent property to refer to the main form from an event procedure attached to a subform, the
code runs only when the main form is open. If you open the subform by itself as a separate form, the code that
refers to the main form doesn't work.
Note As described previously in this chapter, the Form_Current event procedure for the Orders subform also
contains code that updates the record values in the ProductsPopup form to match the current record in the
subform.

Using Pop-up Forms and Dialog Boxes


You can use pop-up forms and dialog boxes in your application to:
Display information to the user.
Prompt the user for data needed by the application to continue.
A pop-up form stays on top of other open forms, even when another form is active. For example, the property
sheet in form Design view is a pop-up form. Another example of a pop-up form is the ProductsPopup form in the
Orders sample application, which is discussed earlier in this chapter.
The ProductsPopup form is modeless, which means you can access other objects and menu commands in
Microsoft Access while the form is open and make them active. In contrast, a modal pop-up form prevents you
from accessing other Microsoft Access objects or menu commands until you close or hide the form. Use a
modeless pop-up form when you want the user to be able to shift the focus between the pop-up form and other
elements of the user interface without having to close the pop-up form. For example, use a modeless pop-up form
if you want to be able to view information on the pop-up form while accessing menu commands and toolbar
buttons.
A familiar example of a modal pop-up form is a dialog box. For example, the PrintInvoiceDialog form is a dialog
box in the Orders sample application that prompts the user to specify an order invoice to print.
Use a dialog box (a modal pop-up form) when you want the user to close the dialog box or respond to its message
before proceeding, such as when you collect information your application needs to complete an operation.
To create a modeless pop-up form
Create the form and set its PopUp property to Yes (the default setting of the Modal property is No).
To create a modal pop-up form (such as a dialog box)
Create the form and set its PopUp and Modal properties to Yes.
Note You use the BorderStyle property to set the style of the border for a pop-up form or dialog box. To display
a dialog box with a typical thick border that has no Minimize and Maximize buttons, set the BorderStyle property
to Dialog. For more information on the different border styles you can use, search the Help index for "BorderStyle
property." For information on the interaction between the BorderStyle, MinMaxButtons, and ControlBox
properties when creating pop-up forms and dialog boxes, search the Help index for "pop-up forms."
You can also open any form as a dialog box by setting the windowmode argument of the OpenForm method to
acDialog (or in a macro, by setting the Window Mode argument of the OpenForm action to Dialog). The
following line of code opens a dialog box named FilterDialog:
DoCmd.OpenForm "FilterDialog", windowmode:=acDialog

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

39

When you use this technique, Microsoft Access suspends execution of the code or macro until the dialog box
form is hidden or closed.
Tip If you simply want to display a short message to the user or prompt the user for short text input, the easiest
approach is to use a message box or input box instead of a pop-up form. For more information on displaying a
message in a message box, see "Using a Message Box to Display a Message" later in this chapter. For more
information on displaying an input box to prompt the user for text input, search the Help index for "InputBox
function."
Creating a Pop-up Form to Display Details About an Item in a Subform
The Orders form in the Orders sample application includes a command button named Details that opens a form
showing details about the current product in the subform.
Because you want users to be able to continue filling out an order while answering customers' questions about the
product, you need the form opened by the Details button to stay on top of the Orders form.
Step One: Create the pop-up form Use the Form Wizard to create the pop-up form, specifying the product,
supplier, and category information you want to display. Name the form ProductsPopup.
Step Two: Set the form's properties Open the form in Design view and set properties to make the form look and
work like a dialog box. The following table shows the property settings for the form.
Because users can't edit data on this form, a shortcut menu isn't needed.
Note Menu commands the user chooses when a pop-up form is open don't apply to the pop-up form. For
example, when the ProductsPopup form is open on top of the Orders form, menu commands can perform
operations on the Orders form and its data, but they don't affect the ProductsPopup form. A pop-up form can't
have a custom menu bar associated with it.

Using a Custom Dialog Box to Collect Information


You use a dialog box in your application to display important messages or to collect information your application
needs to complete an operation. Typically, the user responds to a dialog box by providing information and then
closing the dialog box by clicking a command button labeled OK or Cancel. For dialog boxes that give the user
more than one choice, you can use command buttons with captions that indicate what action each button takes.
You can make your dialog box easier to use with the keyboard by specifying default and cancel buttons:
The default button is chosen when the user presses ENTER without first clicking a different button. To specify the
default button for a dialog box, set the button's Default property to Yes in the property sheet (or to True in Visual
Basic). Only one command button on a form can have its Default property set to Yes. It's a good idea to choose a
button that won't cause a problem if the user presses ENTER by accident. For example, in a dialog box that
deletes data, it's safer to make the default button Cancel rather than OK.
The cancel button is chosen when a user presses the ESC key without first clicking a different button. To specify
the cancel button for a dialog box, set the command button's Cancel property to Yes in the property sheet (or to
True in Visual Basic). Only one command button on a form can have its Cancel property set to Yes.
Tip When a user clicks a command button in a dialog box, you often want to hide the dialog box without closing
it. That way, you can use the values entered in the dialog box to complete the actions the command button
performs. To hide a dialog box, set the form's Visible property to False. After your macro or event procedure has
completed the actions that use the information from the dialog box, you can use the Close method or action to
close the dialog box.
Creating a Dialog Box to Print Invoices

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

40

Northwind sales representatives frequently print an invoice for an order immediately after taking the order. You
can create a dialog box to make it easier for the sales representatives to print the invoice.
Users open the dialog box using a button on a custom toolbar attached to the Orders form.
Step One: Create the query for the list box If the list box will display values from more than one table, you must
first create a query that displays the appropriate rows. Create a query called OrderList that includes three fields
from the Customers and Orders tables, as shown in the following illustration.
Step Two: Create the dialog box and the list box Create a blank form and add an unbound list box to it using the
List Box Wizard. Specify the OrderList query as the row source for the list box, and include all three fields as
columns in the list box. Select OrderID as the list box's bound column. Adjust the column widths so the data in all
three columns is visible at the same time. The List Box Wizard creates the list box and sets its properties. The
following table shows some of the properties for the list box in the Orders sample application.
Step Three: Create buttons for the dialog box Add three command buttons to the form using the Command
Button Wizard: a Preview button, a Print button, and a Cancel button. For the first button, tell the wizard that you
want it to preview the Invoice report. Tell the wizard to display the text "Preview" instead of a picture on the
button. Follow the same steps to create a Print button that prints the report and a Cancel button that closes the
form.
Step Four: Modify the event procedures that the wizard creates When the user chooses to preview or print the
report, you want to hide the dialog box before opening the report. Additionally, you want the report to print only
the record the user selects in the dialog box.
Open the event procedures for the Preview and Print buttons. In the procedures, hide the dialog box by setting its
Visible property to False. To filter records in the report so only the selected record is included, pass the criteria
identifying the record in the wherecondition argument of the OpenReport method.
The event procedure for the Preview command button with the new lines of code is:
Private Sub Preview_Click()
On Error GoTo Err_Print_Click
Dim strDocName As String
Dim strLinkCriteria As String
Me.Visible = False
strDocName = "Invoice"
strLinkCriteria = "OrderID = " & OrderID
DoCmd.OpenReport strDocName, acViewPreview, , strLinkCriteria
.
.
.
End Sub
The event procedure for the Print command button is identical, except that it passes the acViewNormal constant
(instead of acViewPreview) to the OpenReport method.
Note If you prefer, you can use macros instead of event procedures to make the dialog box work. In the macros
for the Preview and Print buttons, use the SetValue action to set the dialog box's Visible property to No, and use
the OpenReport action to preview or print the report, setting the Where Condition argument as follows:
OrderID = Forms!PrintInvoiceDialog!OrderID
In the macro for the Cancel button, use the Close action to close the dialog box. In the macro for the list box's
DblClick event, use the RunMacro action to run the Preview button's macro.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

41

Step Five: Set properties to make the form behave like a dialog box Set the form's PopUp and Modal properties
to Yes, and then save the form with the name PrintInvoiceDialog.
Prevents users from working with other objects while the dialog box is visible.
Displays the form with thick borders that can't be resized and with no Minimize or Maximize buttons. (This is the
standard border style for a dialog box.)
Step Six: Create the macro that opens the dialog box Create a macro to open the dialog box. Add two actions to
it: an OpenForm action that opens the PrintInvoiceDialog form, and a SetValue action that sets the OrderID list
box on the form to the ID number of the current order on the Orders form. Save the macro with the settings as
shown in the following illustration, and name it PrintInvoice.
[Forms]![Orders]![OrderID]
Step Seven: Add a toolbar button to run the macro Create a custom toolbar for the Orders form, and add a button
that runs the PrintInvoice macro.
When a user clicks the button, the macro opens the dialog box. If you want, you can also include the Print Invoice
command on a custom menu bar for the form. To see an example of a custom menu bar, use the Customize dialog
box (View menu, Toolbars submenu) to view the OrdersMenuBar toolbar in the Orders sample application.
Tip To make the dialog box even easier to use, you can write an event procedure that lets users double-click an
order in the list box to choose the order and the Preview button at the same time. To do this, write an event
procedure for the list box's DblClick event that calls the Preview command button's Click event procedure, as
follows:
Private Sub OrderID_DblClick(Cancel As Integer)
Preview_Click
End Sub
Using a Message Box to Display a Message
When you want to display a brief message such as an error, warning, or alert, you can use a predefined dialog box
called a message box. You specify the message to display and when the message box is to appear. The following
illustration shows a typical message box.
Note When a user has the Office Assistant displayed, message box information displays in a speech balloon
associated with the Assistant. If the Office Assistant is hidden or wasn't installed, a message box displays as a
dialog box, as shown in the preceding illustration.
You can use an action or a function to display a message box. The following table describes each method.
Display a message in a message box using a macro.
MsgBox function
Display a message in a message box using Visual Basic. It can return a value to Visual Basic indicating which
command button a user has clicked.
To display the message box in the previous illustration using a macro, you add a MsgBox action to the macro and
set the arguments as shown in the following table.
To display the same message with Visual Basic, use the following code:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

42

MsgBox "The order is saved.", , "Save Order"


Displaying a Two-Line Message in a Message Box
You can display your message on two or more lines in a message box and specify where you want a new line to
start. To do this when you're using the MsgBox function in Visual Basic code, use the vbCrLf constant where you
want the new line to start. The vbCrLf constant is equivalent to the concatenation of the special characters for a
carriage return and line feed, Chr(13) and Chr(10).
For example, to use the MsgBox function to display "New Product:" on one line and "Enter a 5-digit Product ID"
on the next line, you use the following code:
MsgBox "New Product:" & vbCrLf & "Enter a 5-digit Product ID"
However, when you're using the MsgBox action in a macro, you must concatenate the special characters for
carriage return and line feed into the message expression. For example, to produce the same result, you enter the
following expression in the Message argument of a MsgBox action:
= "New Product: " & Chr(13) & Chr(10) & "Enter a 5-digit Product ID"

Filtering and Sorting Data in Forms and Reports


One of the most important functions of a database application is to make it easy for users to find the data they
need to use or change. For example, in the Orders sample application, users need a way to find existing orders
easily. Microsoft Access provides powerful tools for users to find, filter, and sort records. Depending on your
users' needs, you can allow them to use existing tools, or provide your own ways to accomplish these tasks.
This section describes the following approaches to filtering and sorting data in your application:
Using Standard Filter and Sort Commands If you make the Filter By Selection, Filter By Form, and Sort
commands available on your application's forms, users can easily filter and sort records themselves. If you want,
you can customize these features by responding to events.
Opening a Form or Report with a Filter You can use a number of methods to open forms or reports in your
application so that they show a subset of records.
Changing the Filter or Sort Order of a Form or Report After a form or report is open, you can change the filter or
sort order in code, or apply or remove the filter.
If you have special needs for finding and filtering data in your application, you may want to provide your own
filtering interface. In this case, you'll use a combination of the previous techniques, creating your own forms
where users can specify the records they want to see. For example, you may know that there are only a few fields
the user wants to filter on. By displaying your own form, you can provide a straightforward interface for filtering
on important fields while ignoring others.
Using Standard Filter and Sort Commands
Unless you set properties or change menus and toolbars to make them unavailable, the following filter and sort
commands are available on the Records menu and the toolbar in Form view:
Filter By Selection
Filter By Form
Advanced Filter/Sort

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

43

Apply Filter/Sort
Remove Filter/Sort
Sort Ascending
Sort Descending
Using these commands, users of your application can easily filter and sort records themselves. When a user
applies a filter or sort order using these commands, Microsoft Access sets the Filter, FilterOn, OrderBy, and
OrderByOn properties for the form accordingly, and requeries records on the form.
Note When a user changes the filter or sort order of a form and then closes the form, Microsoft Access saves this
information. The last sort order saved is reapplied automatically the next time the form is opened, and the user can
reapply the last filter saved by clicking the Apply Filter button .
Disabling Filter and Sort Features
In some cases, you don't want users of your application to filter records. To disable the standard filtering
commands on a form, set the AllowFilters property to No.
If you want to disable sorting, or if you want to disable some filtering features while allowing others, create
custom menu bars and toolbars for your form that include the commands and buttons you want and leave off the
ones you don't want to make available.
Responding to Filter Events
If you want to change the way that standard filtering and sorting commands work, you can write event procedures
for the Filter and ApplyFilter events. For example, you may want to display a message each time a user uses the
Filter By Form or the Advanced Filter/Sort command for a form to remind the user to specify criteria and then
apply the filter.
If you want to display a message or take other action when a user uses the Filter By Form or Advanced Filter/Sort
command, write an event procedure for the Filter event. To help you respond to each command, the Filter event
procedure has a FilterType argument that tells you which of these commands was selected.
If you want to cancel the filtering command the user chose, you can set the Cancel argument for the event
procedure to True.
The following event procedure compares the FilterType argument to the acFilterByForm and acFilterAdvanced
constants to display a different message to the user depending on which filtering command was chosen.
Private Sub Form_Filter(Cancel As Integer, FilterType As Integer)
Dim strMsg As String
Select Case FilterType
Case acFilterByForm
strMsg = "Specify the records you want to see by choosing "
strMsg = strMsg & "from the lists, then click Apply Filter."
Case acFilterAdvanced
strMsg = "Drag fields to the filter design grid, specify "
strMsg = strMsg & "criteria and sort order, then click "
strMsg = strMsg & "Apply Filter."
End Select
MsgBox strMsg
' Display the message.
End Sub

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

44

If you want to display a message or take other action when a user applies or changes a filter, write an event
procedure for the ApplyFilter event. This event occurs whenever a user chooses the Apply Filter/Sort, Remove
Filter/Sort, or Filter By Selection command, and whenever the user closes the Filter window without applying the
filter. The ApplyFilter event procedure has an ApplyType argument that tells you which of these actions was taken
so you can respond in different ways.
If you want to cancel the filtering command the user chose, you can set the Cancel argument for the event
procedure to True, and the filter won't be applied.
The following event procedure displays a message if the user is applying a filter. The message shows the setting
of the Filter property and gives the user a chance to cancel the operation.
Private Sub Form_ApplyFilter(Cancel As Integer, ApplyType As Integer)
Dim strMsg As String, intResponse As Integer
If ApplyType = acApplyFilter Then
strMsg = "You've chosen to filter for the following criteria:"
strMsg = strMsg & vbCrLf & Me.Filter
End If
' Display the message box and get an OK or Cancel response.
intResponse = MsgBox(strMsg, vbOkCancel + vbQuestion)
If intResponse = vbCancel Then Cancel = True
End Sub
Customizing the Standard Filtering Interface
When a Northwind sales representative starts the Orders application, the Orders form opens for data entry (no
existing records are available). To find an existing order in the database, the employee clicks the Filter Orders
button on the Orders toolbar.
You want the filtering interface on the Orders form to be as straightforward as possible. Because most fields aren't
appropriate for setting criteria, you don't want the employee to worry about them. To make them less obtrusive,
you can disable all the fields except those you expect a user to set criteria for.
The Filter event procedure for the Orders form disables all but three fields (BillTo, EmployeeID, and OrderDate).
Private Sub Form_Filter(Cancel As Integer, FilterType As Integer)
' If Filter By Form, disable all but three fields.
If FilterType = acFilterByForm Then
BillTo.SetFocus
Dim ctl As Control
For Each ctl In Me.Controls
Select Case ctl.ControlType
Case acTextBox, acComboBox, acOptionGroup, acSubForm
Select Case ctl.Name
Case "BillTo", "EmployeeID", "OrderDate"
Case Else
ctl.Enabled = False
End Select
End Select
Next ctl
DoCmd.ShowToolbar "Orders Form Toolbar", acToolbarNo
End If
End Sub
If you set properties in the Filter event procedure, be sure to reset them in the ApplyFilter event procedure.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

45

Private Sub Form_ApplyFilter(Cancel As Integer, ApplyType As Integer)


' Reset fields after filtering.
Dim ctl As Control
For Each ctl In Me.Controls
Select Case ctl.ControlType
Case acTextBox, acComboBox, acOptionGroup, acSubForm
Select Case ctl.Name
Case "OrderID", "Subtotal", "Total"
Case Else
ctl.Enabled = True
End Select
End Select
Next ctl
BillTo.SetFocus
DoCmd.ShowToolbar "Orders Form Toolbar", acToolbarYes
End Sub

Combo Boxes

Controlling Combo Boxes in Filter By Form


You can control whether text boxes on a form display suggested values in drop-down combo boxes in the Filter
By Form window. For example, if you have a large set of records or if your data is stored on a network, you may
want to prevent Microsoft Access from running queries to fill all the lists. By default, Filter By Form displays lists
of values for indexed and non-indexed fields if the record source for the form is in the current database or a linked
table, but doesn't display them if the record source is a linked Open Database Connectivity (ODBC) database. To
change this behavior, set the FilterLookup property for the text box in form Design view, or set Filter By Form
options on the Edit/Find tab of the Options dialog box (Tools menu).
Opening a Form or Report with a Filter
When you use Visual Basic code or a macro to open a form or report, you may want to specify which records to
display. This is especially useful for reports, because users can't directly filter records in a report.
When your application provides a customized way for users to open a form or print a report, you can specify the
records to display in the form or report in several ways. A common approach is to display a custom dialog box
where the user enters criteria for the form or report's underlying query. To get the criteria, you refer to the controls
in the dialog box. The following sections describe three ways you can use criteria entered in a custom dialog box
to filter records.
Using the wherecondition Argument
The wherecondition argument of the OpenForm or OpenReport method or action is the simplest way to get
criteria in situations where a user is providing only one value. For example, the PrintInvoiceDialog form in the
Orders sample application prompts users to select an OrderID for the invoice they want to print. If you're using an
event procedure, you can apply a filter that displays only one record by adding an argument to the OpenReport
method, as shown in the following line of code:
DoCmd.OpenReport "Invoice", acViewPreview, , "OrderID = " & OrderID
The "OrderID = " in the filter expression refers to the OrderID field in the Invoice report's underlying query. The
OrderID on the right side of the expression refers to the value the user selected from the OrderID list box in the
dialog box. The expression concatenates the two, causing the report to include only the invoice for the record the
user selected.
Note If you're using a macro, you can use the following Where Condition argument in the OpenReport action
that prints the report.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

46

OrderID = [Forms]![PrintInvoiceDialog]![OrderID]
The wherecondition argument is applied only by the event procedure or macro specified for the OnClick event of
the button that runs the OpenForm or OpenReport method or action. This gives you the flexibility of using any
number of different dialog boxes to open the same form or report and applying different sets of criteria depending
on what the user wants to do. For example, the user may want to print an invoice for a certain customer or view
orders only for a certain product. If users open the form or report in the Database window rather than through
your dialog box, however, no criteria are applied to the query and all its records are displayed or printed. To
prevent this, you can hide the Database window by clearing the Display Database Window check box in the
Startup dialog box (Tools menu).
You can use the wherecondition argument to set criteria for more than one field, but if you do, the argument
setting quickly becomes long and complicated. In those situations, specifying criteria in a query may be easier.
Using a Query as a Filter
A separate query, sometimes called a filter query, can refer to the controls on your dialog box to get its criteria.
Using this approach, you filter the records in a form or report by setting the filtername argument of the OpenForm
or OpenReport method or action to the name of the filter query you create. The filter query must include all the
tables in the record source of the form or report you're opening. Additionally, the filter query must either include
all the fields in the form or report you're opening, or you must set its OutputAllFields property to Yes.
For example, to create a filter query for the Invoice report, make a copy of the report's underlying query and save
it under another name. Then, add criteria to the OrderID field in the filter query that refers to the control on the
dialog box. (If the filter query's OutputAllFields property is set to Yes, this is the only field you need to include in
the filter query as long as you include all the tables that contain fields on the report.)
After you create and save the query you'll use as a filter, set the filtername argument of the OpenReport method or
action to the name of the filter query. The filtername argument applies the specified filter query each time the
OpenReport method or action runs.
Using a query as a filter to set the criteria has advantages similar to using the wherecondition argument of the
OpenForm or OpenReport method or action. A filter query gives you the same flexibility of using more than one
dialog box to open the same form or report and applying different sets of criteria depending on what a user wants
to do. If users open the form or report in the Database window rather than through your dialog box, however, no
criteria are applied to the query and all its records are displayed or printed. To prevent this, you can hide the
Database window by clearing the Display Database Window check box in the Startup dialog box (Tools menu).
Directly Referring to Dialog Box Controls in a Form or Report's Underlying Query
You can also refer to the dialog box controls directly in the form or report's underlying query instead of through
the arguments of the OpenForm or OpenReport method or action. For example, instead of referring to the control
on the PrintInvoiceDialog form in a filter query's criteria as shown in the previous illustration, you can set the
exact same criteria in the Invoice report's underlying query, Invoices.
Using this approach, the OpenForm or OpenReport method or action requires no wherecondition or filtername
argument. Instead, each time you open a form or report, its underlying query looks for the dialog box to get its
criteria. However, if a user opens the form or report in the Database window rather than through your dialog box,
Microsoft Access displays a parameter box prompting the user for the dialog box value. To prevent this, you can
hide the Database window by clearing the Display Database Window check box in the Startup dialog box (Tools
menu).
Changing the Filter or Sort Order of a Form or Report

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

47

After a form or report is open, you can change the filter or sort order in response to users' actions by setting form
and report properties in Visual Basic code or in macros. For example, you may want to provide a menu command
or a toolbar button that users can use to change the records that are displayed. Or you may have an option group
control on a form that users can use to select from common sorting options.
To set the filter of a form or report, set its Filter property to the appropriate wherecondition argument, and then set
the FilterOn property to True. To set the sort order, set the OrderBy property to the field or fields you want to sort
on, and then set the OrderByOn property to True. If a filter or sort order is already applied on a form, you can
change it simply by setting the Filter or OrderBy properties.
When you apply or change the filter or sort order by setting these properties, Microsoft Access automatically
requeries the records in the form or report. For example, the following code changes the sort order of a form
based on a user's selection in an option group:
Private Sub SortOptionGrp_AfterUpdate()
Const conName = 0, conDate = 1
Select Case SortOptionGrp
Case conName
Me.OrderBy = "LastName, FirstName" ' Sort by two fields.
Case conDate
Me.OrderBy = "HireDate DESC"
' Sort by descending date.
End Select
Me.OrderByOn = True
' Apply the sort order.
End Sub
Whether the filter and sort order get set in code or by the user, you can apply or remove them by setting the
FilterOn and OrderByOn properties to True or False. For example, you could add a button to a report's custom
toolbar that runs the following macro to apply or remove a filter you specified when opening the report.
Synchronizing Records by Changing the Filter
The Orders sample application controls which record appears in the ProductsPopup form by setting the form's
Filter property. You can keep the ProductsPopup form synchronized with the Orders form; this will make sure the
ProductsPopup form always shows details about the current product when you move from record to record in the
Orders subform.
To do this, write an event procedure for the subform's Current event that sets the pop-up form's Filter property.
(You can also do this by writing a macro you specify as the subform's OnCurrent event property setting.) The
following code example shows the event procedure for the Current event of the Orders subform. This event
procedure uses the IsLoaded function from the UtilityFunctions module that is included with the Orders sample
application.
Private Sub Form_Current()
.
.
.
If IsLoaded("ProductsPopup") Then
' If there's no current product record, display a blank pop-up window,
' otherwise filter to show the current product.
If IsNull(ProductID) Then
strFilter = "ProductID = 0"
Else
Forms!ProductsPopup.Filter = "ProductID = " & ProductID
End If
Forms!Orders.SetFocus
' Set focus back to Orders subform.
Forms!Orders!OrdersSubform.SetFocus

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

48

End If
.
.
.
End Sub
Each time a user changes records in the subform, the procedure resets the Filter property of the pop-up form,
causing it to display the corresponding record.

Populating Controls on a Form


You can populate (fill in) and update the value of controls on a form while the user is using the form in one of two
ways. You can either assign values to the controls in a macro or event procedure, or you can have Microsoft
Access look up the values and display them automatically using AutoLookup.
You assign a value to a control when you want to give a control either the same value as another control or field,
or a value derived from another control or field.
You use AutoLookup when you want a form to display related values from a table that's on the "one" side of a
one-to-many relationship with the form's primary table. For example, the Orders form is used to add new orders
to the Orders table. It also displays values from the Customers table, which is on the "one" side of a one-to-many
relationship with the Orders table. When you include the fields from the Customers table in the Order form's
underlying record source or query, Microsoft Access automatically displays the correct customer information
when you select the customer for the current order.

Assigning Values to Controls on the Orders Form


The Orders table in the Orders sample application includes fields for the shipping address, which is often (but not
always) the same as the billing address. You can automatically fill in the shipping controls with a customer's
billing address by assigning them the same value as the corresponding billing controls.
Step One: Assign a control the value from another control To assign the values, you write an event procedure for
the AfterUpdate event of the BillTo combo box. In the combo box's property sheet, select the AfterUpdate
property, and then click the Build button beside the property. In the Choose Builder dialog box, double-click
Code Builder. Microsoft Access opens the form module and displays a template for the control's AfterUpdate
event procedure. Enter the following code in the AfterUpdate event procedure template:
Private Sub BillTo_AfterUpdate()
ShipName = CompanyName
ShipAddress = Address
ShipCity = City
ShipRegion = Region
ShipPostalCode = PostalCode
ShipCountry = Country
End Sub
The references on the left side of the statements are the names of shipping controls. The references on the right
side of the statements are the names of billing controls, which are bound to fields in the Customers table. Each
time the user selects a customer for the order from the combo box, this event procedure fills in the shipping
controls with the new customer information from the billing controls.
Note In addition to setting the values in the shipping controls, the BillTo_AfterUpdate event procedure in the
Orders sample application sets the Enabled property of the customer information controls to Yes, so sales
representatives can update the customer's address or other information. In the Form_Current event procedure for
the Orders form, these controls are disabled if the user is in a new record; the sales representative must select a
customer before editing the customer information controls.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

49

Step Two: Assign a control a value from a table The UnitPrice control in the Orders subform is bound to the
UnitPrice field in the Order Details table. Once the user selects a product, you want to fill in the UnitPrice control
in the subform with the current value from the Products table.
Because the UnitPrice field in the Products table isn't on the form or in its record source, you can't refer to it
directly. Instead, you can use the DLookup function to look up the value in the Products table and assign it to the
UnitPrice control in the subform. Use the following event procedure for the AfterUpdate event of the ProductID
control on the subform:
Private Sub ProductID_AfterUpdate()
.
.
.
' Look up product's price and assign it to the UnitPrice control.
strFilter = "ProductID = Forms!Orders!OrdersSubform!ProductID"
Me!UnitPrice = DLookup("UnitPrice", "Products", strFilter)
.
.
.
End Sub
The DLookup function has three arguments. The first specifies the field you're looking up (UnitPrice); the second
specifies the table (Products); and the third specifies which value to find (the value for the record where the
ProductID is the same as the ProductID on the current record in the Orders subform).
Note Because each call to the DLookup function causes Microsoft Access to run a query, you may want to limit
how often you use it. You can often avoid using the DLookup function by including the field you want to look up
in the underlying query or record source for the form or report. In the previous example, you could include the
UnitPrice field from the Products table in the record source for the subform. Because it would involve joining the
two tables, this strategy would cause the subform to open more slowly; however, it would allow you to look up
the UnitPrice value much more quickly and easily than with the DLookup function.
Using AutoLookup to Fill In Customer Information on the Orders Form
The Orders form in the Orders sample application displays the customer's billing address and contact information,
which is stored in the Customers table. By including the fields from the Customers table in the underlying record
source for the Orders form, you can have the billing information fill in automatically when the user selects the
customer from the BillTo combo box.

If the Orders and Customers tables are set up correctly, it's easy to create this type of form with the Form Wizard.
Because the Orders table includes a Lookup field that relates data in the two tables, the Form Wizard sets up the
lookup automatically. When the wizard adds the CustomerID Lookup field to the form, it creates a combo box
control that displays the customer's name, but is bound to the CustomerID field in the Orders table.
When you create the form, just tell the Form Wizard to include all fields in both the Orders and Customers tables.
After you create the form with the Form Wizard, resize and arrange the fields as you see fit. Additionally, rename
the CustomerID Lookup field (and its label) to BillTo, so you can distinguish it from the CustomerID field bound
to the Customers table.
Because the Customers table is on the "one" side of the one-to-many relationship between the Customers and
Orders tables, you don't have to do anything special to make the text boxes display the information from the
Customers table. When the user picks a customer from the combo box, which is bound to the CustomerID field in

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

50

the Orders table, Microsoft Access uses AutoLookup to automatically look up the information in the Customers
table and display it in the text boxes. If users change the data in the customer fields, their changes are saved in the
Customers table when they save the order.
Because all the customer fields are included on the Orders form, users can add or modify customer information
directly on the Orders form, without having to go to a separate Customers form.
Note To create this form without the Form Wizard, create a query that includes all the fields from the Orders and
Customers tables. Then, create a form based on the query and add the fields from both tables.
Additionally, you can use AutoLookup even if you don't have a Lookup field in the underlying table by adding a
combo box bound to the CustomerID field in the Orders table, and then setting its properties so that it displays
customer names.

Adding a Row to a Combo Box List


Combo boxes are commonly used to display a list of values in a table or query for a user to select from. By
responding to the NotInList event, you can provide a way for the user to add values that aren't in the list.
Often the value displayed in a combo box is looked up from a record in a related table. Because the list is derived
from a table or query, you must provide a way for the user to enter a new record in the underlying table. Then you
can use the Requery method to requery the list, so it contains the new value.
When a user types a value in a combo box that isn't in the list, the NotInList event of the combo box occurs as
long as the combo box's LimitToList property is set to Yes, or a column other than the combo box's bound column
is displayed in the box. You can write an event procedure for the NotInList event that provides a way for the user
to add a new record to the table that supplies the list's values. The NotInList event procedure includes a string
argument named NewData that Microsoft Access uses to pass the text the user enters to the event procedure.
The NotInList event procedure also has a Response argument where you tell Microsoft Access what to do after
the procedure runs. Depending on what action you take in the event procedure, you set the Response argument to
one of three predefined constant values:
acDataErrAdded If your event procedure enters the new value in the record source for the list or provides a way
for the user to do so, set the Response argument to acDataErrAdded. Microsoft Access then requeries the combo
box for you, adding the new value to the list.
acDataErrDisplay If you don't add the new value and want Microsoft Access to display the default error
message, set the Response argument to acDataErrDisplay. Microsoft Access requires the user to enter a valid
value from the list.
acDataErrContinue If you display your own message in the event procedure, set the Response argument to
acDataErrContinue. Microsoft Access doesn't display its default error message, but still requires the user to enter
a value in the field. If you don't want the user to select an existing value from the list, you can undo changes to the
field by using the Undo method.
For example, the following event procedure asks the user whether to add a value to a list, adds the value, then
uses the Response argument to tell Microsoft Access to requery the list:
Private Sub ShipperID_NotInList(NewData As String, Response As Integer)
Dim intAnswer As Integer
Dim dbs As Database, rst As Recordset
intAnswer = MsgBox("Add " & NewData & " to the list of shippers?", _
vbQuestion + vbYesNo)
If intAnswer = vbYes Then
' Add shipper stored in NewData argument to the Shippers table.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

51

Set dbs = CurrentDb


Set rst = dbs.OpenRecordset("Shippers")
rst.AddNew
rst!CompanyName = NewData
rst.Update
Response = acDataErrAdded
' Requery the combo box list.
Else
Response = acDataErrDisplay
' Require the user to select
' an existing shipper.
End If
rst.Close
End Sub
Adding a Row to the Combo Box's List on the Orders Form
When taking a new order in the Orders sample application, a user, typically a sales representative, starts by
looking up the customer in the BillTo combo box at the top of the Orders form. If the customer is new and doesn't
appear in the list, the user needs a way to add the customer to the Customers table and update the combo box so it
displays the new customer in the list.
You can let the user add a new customer by simply typing the new customer's name in the combo box. To do this,
write an event procedure for the NotInList event of the combo box.
Step One: Write the event procedure for adding a new customer This event procedure asks for confirmation that
the user wants to add a new customer (and hasn't just typed the name of an existing customer incorrectly), and
then provides a way to do it.
The following code example shows the event procedure. An explanation of what it does follows the code
example.
Private Sub BillTo_NotInList(NewData As String, Response As Integer)
' Allows user to add a new customer by typing the customer's name
' in the BillTo combo box.
Dim intNewCustomer As Integer, strTitle As String
Dim intMsgDialog As Integer, strMsg As String
Const conClrWhite = 16777215
Const conNormal = 1
' Check if user has already selected a customer.
If IsNull(CustomerID) Then
' Display message box asking if the user wants to add a new customer.
strTitle = "Customer Not in List"
strMsg = "Do you want to add a new customer?"
intMsgDialog = vbYesNo + vbExclamation
intNewCustomer = MsgBox(strMsg, intMsgDialog, strTitle)
If intNewCustomer = vbYes Then
' Remove text user entered from the combo box and assign
' it to the CompanyName control and the ShipName control.
BillTo.Undo
CompanyName.Enabled = True
CompanyName = NewData
ShipName = NewData
' Enable and move focus to CustomerID.
CustomerID.Enabled = True
CustomerID.Locked = False
CustomerID.BackColor = conClrWhite
CustomerID.BorderStyle = conNormal

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

52

CustomerID.SetFocus
' Enable the other customer information controls.
Address.Enabled = True
City.Enabled = True
Region.Enabled = True
City.Enabled = True
PostalCode.Enabled = True
Country.Enabled = True
ContactName.Enabled = True
ContactTitle.Enabled = True
Phone.Enabled = True
Fax.Enabled = True
MsgBox "Enter the new customer's ID, address, and contact information."
' Continue without displaying default error message.
Response = acDataErrContinue
Else
' Display the default error message.
Response = acDataErrDisplay
End If
Else
' User has already picked a customer; display a message and undo the field.
strMsg = "To modify this customer's company name, edit the name in the "
strMsg = strMsg & "box below the Bill To combo box. To add a new customer, "
strMsg = strMsg & "click Undo Record on the Records menu and then type the "
strMsg = strMsg & "new company name in the Bill To combo box."
MsgBox strMsg
BillTo.Undo
' Continue without displaying default error message.
Response = acDataErrContinue
End If
End Sub
In this code, you use the MsgBox function to ask if the user wants to add a new customer. If the user chooses Yes,
the event procedure uses the Undo method to remove the text from the combo box. It then uses the NewData
argument to assign the text the user entered in the combo box to the CompanyName control and the ShipName
control. With the value cleared from the combo box, you can move the focus down to the customer controls.
Because the user can't use the Orders form to change the CustomerID for an existing customer, the CustomerID
control is normally locked, disabled, and displayed with a background that matches the form's background, so it
doesn't look like a control the user can edit. Now that the user is entering a new customer, your code unlocks and
enables the control, and displays it with a white background and borders. It also enables the other customer
information controls. An event procedure for the form's AfterUpdate event, which occurs after the record is saved,
locks and disables the CustomerID control again, and displays it without a white background.
Note In the Orders form in the Orders sample application, all the fields from the Customers table are located on
the Orders form, so users can add a complete record for a new customer directly in the fields on the Orders form.
If you don't want to include all the fields from the underlying table on your form, you can still let users add a new
record to it. When the user wants to add a row to a combo box, display a separate form with all the fields from the
underlying table on it. After the user saves and closes this separate form, you can requery the combo box so the
new item appears in its list. For an example of this approach, see the Developer Solutions sample application.
Step Two: Write the event procedure that updates the combo box Your NotInList event procedure lets the user
add a new customer and a new order at the same time. Once the order is saved and the new customer record is in
the Customers table, you can update the BillTo combo box so it includes the new customer. You do this by writing
an event procedure for the form's AfterUpdate event that requeries the combo box using the Requery method, as
follows:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

53

Private Sub Form_AfterUpdate()


BillTo.Requery
End Sub
Tip If your combo box list includes a large number of rows, requerying the list every time you save a record may
slow down your form's performance. In this case, you can improve performance by opening a separate form for
the user to add a new customer, and then requerying the combo box only when the customer information on the
separate form is saved.
Using the Tab Control to Organize Forms
You can use a tab control to present several pages of information on a single form. A tab control is useful when
your form contains information that can be sorted into two or more categories. For example, the Employees form
in the Northwind sample application uses a tab control to display two pages of information about employees.
The tab control provides a user interface similar to Windows tabbed dialog boxes. Users switch between pages by
clicking the corresponding tab on the top of the page.
Adding a Tab Control to a Form
You add a tab control to a form in much the same way as you add other controls, except that each page on a tab
control contains other controls. A tab control can't contain other tab controls, but it's possible to have two or more
separate tab controls on a form.
Note When you add a tab control to a form, Microsoft Access assigns default values to the Name property for
each page: Page1 and Page2. Each page in a tab control is treated as a separate control on the form, and every
control must have a unique value for its Name property. For this reason, if you add a second tab control to a form,
Microsoft Access won't assign Page1 as the default value for the Name property of the first page in the second tab
control, or allow you to change the value to Page1. For the same reason, no other control on the same form can
have its Name property set to Page1. However, you can use the same text on more than one page's tab by setting
the page's Caption property.
To add a tab control to a form
1 Open a form in Design view.
2

In the toolbox, click the Tab Control tool and then click the form where you want to place the control.

Microsoft Access adds a tab control with two pages. The Page1 tab is on top.
3 Add controls to Page1 of the tab control by clicking a tool in the toolbox, and then click on Page1 where you
want to place the control. You can add any type of control except another tab control.
Note You can also copy controls from another part of a form or from another page and paste them onto a tab
control page. However, you can't drag controls from another part of a form or from another page.
4 To add controls to Page2, click the Page2 tab, and then use the toolbox to add the controls.
5 Use the following table for other tasks you may want to perform.
Right-click the tab whose name you want to change, click Properties on the shortcut menu, and then specify a
new name in the Caption property. If you don't specify a name in the Caption property, Microsoft Access uses the
text in the Name property.
Add or delete pages, or change the page order of tabs

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

54

Right-click the tab, and then click Insert Page, Delete Page, or Page Order on the shortcut menu.
You can also insert a page by copying and pasting an existing page. This copies the entire page, including the
controls on it. You can also delete a page by clicking the page, and then pressing DELETE.
Change the tab order of controls on a page
Right-click the page and click Tab Order on the shortcut menu.
Change the font name, font size, or font style (weight, italic, and/or underline) of all pages
Right-click the border of the tab control, click Properties on the shortcut menu, and then set the appropriate
properties. The property settings you select apply to the fonts on all pages of the tab control.
Note The font color is determined by the Color setting for 3D Objects specified on the Appearance tab of the
Display Properties dialog box, which is available from Windows Control Panel.
6 Size the tab control as appropriate. Click each tab to make sure all the controls fit well within each tab.
Note Microsoft Access won't crop controls when you size the tab control. You may need to move controls before
you make the tab control smaller.
7 Switch to Form view and test the tab control.
Additional Tab Control and Tab Control Page Properties
You can further customize how a tab control and its pages look and work by setting their properties. Tab control
properties affect the way the tab control as a whole looks and works, and in many cases apply to all the pages
within the control. For example, you can set the TabFixedHeight and TabFixedWidth properties to set the size of
all tabs on a tab control. You can set most tab control properties in the tab control property sheet; however, some
properties can only be set or referenced by using Visual Basic. To display the tab control property sheet, rightclick the border of the tab control and click Properties on the shortcut menu.
The following table lists the most commonly used tab control properties. For information on other properties,
press F1 when the insertion point is in the property box.
Specifies whether a tab control can have more than one row of tabs. If the MultiRow property is set to No,
Microsoft Access truncates the tabs if they exceed the width of the tab control and adds a scroll bar. The default
setting is No.
Specifies whether the pages in the tab control are transparent. When set to Normal, the color of pages is
determined by the 3D Objects color specified on the Appearance tab of the Display Properties dialog box, which
is available from Windows Control Panel. When set to Transparent, the color of pages is determined by the
BackColor property of the detail section and the Picture property of the form (if any) showing through them. The
tabs in a tab control are always solid and use the 3D Objects color set in the Windows Control Panel. The default
setting is Normal.
Specifies what to display at the top of the tab control. You can display tabs, command buttons (in the same
positions as tabs), or nothing. The default setting is Tabs.
You may want to display nothing if you want to use command buttons on the form outside the tab control to
determine which page has the focus. To do this, set the tab control's Style property to None. Then add an event
procedure to the button's OnClick event that sets the tab control's Value property to the page you want to display.
Specifies the height of tabs in inches. When set to 0, each tab is tall enough to fit its contents. The minimum
height is .05 inches. The default setting is 0.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

55

Specifies the width of tabs in inches. When set to 0, each tab is wide enough to fit its contents and, if there is more
than one row of tabs, the width of each tab is increased so that each row of tabs spans the width of the tab control.
If the setting is greater than 0, all tabs have an identical width as specified by this property. The minimum width is
0.5 inches. The default setting is 0.
In addition to the properties that apply to the tab control as a whole, there are also properties that apply to
individual pages. Tab control page properties affect the way a page looks and works. All tab control page
properties can be set in the page property sheet. To display the page property sheet, right-click the tab, and then
click Properties on the shortcut menu.
The following table lists the most commonly used tab control page properties. For information on other
properties, press F1 when the insertion point is in the property box.
Specifies the name of the page. Use this name when referring to a tab control page in Visual Basic. The default
name is Page1 for the first page, Page2 for the second page, and so on.
Specifies the display text that appears on a tab. If you don't specify a name in the Caption property, Microsoft
Access uses the text in the Name property.
Use to add a graphic to a tab. The graphic is displayed to the left of the tab name specified in the Caption
property. If you want to display only a picture and no name, enter a space in the Caption property.
If you're going to use a tab control in a custom dialog box, you may want to set additional properties so that your
form looks and works like a Windows dialog box.
Referring to Tab Control Objects in Visual Basic
In most ways, a tab control works like other controls on a form and can be referred to as a member of a form's
Controls collection. For example, to refer to a tab control named TabControl1 on a form named Form1, you can
use the following expression:
Form1.Controls!TabControl1
However, because the Controls collection is the default collection of the Form object, you don't have to explicitly
refer to the Controls collection. That is, you can omit the reference to the Controls collection from the expression,
like this:
Form1!TabControl1
Referring to the Pages Collection
A tab control contains one or more pages. Each page in a tab control is referenced as a member of the tab control's
Pages collection. Each page in the Pages collection can be referred to by either its PageIndex property setting
(which reflects the page's position in the collection starting with 0), or by the page's Name property setting. There
is no default collection for the TabControl object, so when referring to items in the Pages collection by their index
value, or to properties of the Pages collection, you must explicitly refer to the Pages collection.
For example, to change the value of the Caption property for the first page of a tab control named TabControl1 by
referring to its index value in the Pages collection, you can use the following statement:
TabControl1.Pages(0).Caption = "First Page"
Because each page is a member of the form's Controls collection, you can refer to a page solely by its Name
property without referring to the tab control's name or its Pages collection. For example, to change the value of
the Caption property of a page with its Name property set to Page1, use the following statement:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

56

Page1.Caption = "First Page"


Note If a user or code changes a page's PageIndex property, the reference to the page's index and the page's
position in the page order change. In this case, if you want to maintain an absolute reference to a page, refer to the
page's Name property.
The Pages collection has one property, Count, that returns the number of pages in a tab control. Note that this
property is not a property of the tab control itself, but of its Pages collection, so you must explicitly refer to the
collection. For example, to determine the number of pages in TabControl1, use the following statement:
TabControl1.Pages.Count
Referring to and Changing the Current Page
A tab control's default property is Value, which returns an integer that identifies the current page: 0 for the first
page, 1 for the second page, and so on. The Value property is only available in Visual Basic code or in
expressions. By reading the Value property at run time, you can determine which page is currently on top. For
example, the following statement returns the value for the current page of TabControl1:
TabControl1.Value
Note Because the Value property is the default property for a tab control, you don't have to refer to it explicitly.
For this reason, you could omit .Value from the preceding example.
Setting a tab control's Value property at run time changes the focus to the specified page, making it the current
page. For example, the following statement moves the focus to the third page of TabControl1:
TabControl1 = 2
This is useful if you set a tab control's Style property to None (which displays no tabs) and want to use command
buttons on the form to determine which page has the focus. To use a command button to display a page, add an
event procedure to the button's OnClick event that sets the tab control's Value property to the integer that
identifies the appropriate page.
By using the Value property with the Pages collection, you can set properties at run time for the page that is on
top. For example, you can hide the current page and all of its controls by setting the page's Visible property to
False. The following statement hides the current page of TabControl1:
TabControl1.Pages(TabControl1).Visible = False
Each page in a tab control also has a PageIndex property that specifies the position of a page within the Pages
collection using the same numbering sequence as the tab control's Value property: 0 for the first page, 1 for the
second page, and so on. Setting the value of a page's PageIndex property changes the order in which pages appear
in the tab control. For example, if you wanted to make a page named Page1 the second page, you'd use the
following statement:
Page1.PageIndex = 1
The PageIndex property is more typically set at design time in a page's property sheet. You can also set the page
order by right-clicking the border of a tab control and then clicking Page Order on the shortcut menu.
Referring to Controls on a Tab Control Page
The controls you place on a tab control page are part of the same collection as all controls on the form. For this
reason, each control on a tab control page must have a name that's unique with respect to all other controls on the

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

57

same form. You can refer to controls on a tab control page by using the same syntax used for controls on a form
without a tab control. For example, a fully qualified reference to the HomePhone text box on the Personal Info tab
of the Employees form in the Northwind sample application would read as follows.
Forms!Employees!HomePhone
Because each control on a form has its own Controls collection, you can also refer to the controls on a tab control
as members of its Controls collection. For example, the following code enumerates (lists) all the controls on the
tab control of the Employees form in the Northwind sample application. Because the EmployeeName text box in
the header section of the form is not a member of this collection, it isn't listed.
Sub ListTabControlControls()
' Declare object variables.
Dim tabCtl As TabControl, ctl As Control
' Return reference to tab control on Employees form.
Set tabCtl = Forms!Employees!TabCtl0
' List all controls on the tab control in the Debug window.
For Each ctl In tabCtl
Debug.Print ctl.Name
Next ctl
End Sub
Additionally, each page on a tab control has its own Controls collection. By using a page's Controls collection,
you can refer to controls on each page. The following code enumerates the controls for each page of the tab
control on the Employees form in the Northwind sample application.
Sub ListPageControls()
' Declare object variables.
Dim tabCtl As TabControl, pg As Page, ctl As Control, intPageNum As Integer
' Return reference to tab control on Employees form.
Set tabCtl = Forms!Employees!TabCtl0
' List all controls for each page on the tab control in the Debug window.
For Each pg In tabCtl.Pages
intPageNum = intPageNum + 1
Debug.Print "Page " & intPageNum & " Controls:"
For Each ctl In pg.Controls
Debug.Print ctl.Name
Next ctl
Debug.Print
Next pg
End Sub

Working with Variables, Data Types, and Constants


So far, you've been introduced to the essential components of Visual Basic the modules and procedures that
contain your application's Visual Basic code. You've also learned some of the elements of Visual Basic procedures
and have seen examples of some of the programming tools you can use to control how your code runs. This
chapter takes you one step further, showing you the basics of declaring and using variables, data types, and
constants in your Visual Basic code.

Declaring Variables
As you saw earlier," you use variables to store values while your Visual Basic code is running. Within a procedure
or module, you declare a variable with the Dim statement. The syntax for the Dim statement is:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

58

Dim variablename [As type]


In the Dim statement, you supply a name for the variable. Like other things you create and name in Visual Basic,
variable names:
Must begin with a letter.
Can't contain an embedded period or an embedded type-declaration character.
Must not exceed 255 characters.
Must be unique within the same scope.
You use the optional As type clause in the Dim statement to define the data type of the variable you're declaring.
A data type is a designation of the type of information a variable can store. Examples of data types include String,
Integer, and Currency. If you omit the As type clause, Visual Basic makes the variable a Variant, which is the
default data type (unless you change the default with the Deftype statement). Data types, including Variant, are
discussed later in this chapter. Variables can also contain objects from Microsoft Access, such as Form, Report,
and Control objects, or objects from other applications.
Visual Basic variables are not case-sensitive; when you specify a variable, you don't need to match the exact
uppercase and lowercase letters you used in declaring the variable. Visual Basic automatically changes any
explicitly declared variable to the case used in the declaration. An implicitly declared variable is given the same
case it had the last time it was used.
Tip When you name variables, you may find it useful to include prefixes to indicate each variable's data type.
For example, you might name a variable dblTemp to indicate that its data type is Double. This practice makes
your code easier to read and helps you avoid type-mismatch errors.
Implicit Declaration
Although it's not recommended, you can use a variable without explicitly declaring it. For example, you could
write a function as follows:
Function SafeSqr(ByVal dblNum As Double) As Double
dblTemp = Abs(dblNum)
SafeSqr = Sqr(dblTemp)
End Function
Here, you haven't declared dblTemp as a variable before you use it in the function. In cases such as this, Visual
Basic implicitly creates a variable with that name, so you can use the variable as if you had explicitly declared it.
Although implicit declarations are convenient, they can lead to subtle errors in your code if a variable name is
misspelled. For example, suppose you wrote the following function:
Function SafeSqr(ByVal dblNum As Double) As Double
dblTemp = Abs(dblNum)
SafeSqr = Sqr(dblTmp)
' dblTemp is misspelled.
End Function
At first glance, this looks the same as the previous function. But because the dblTemp variable is misspelled, this
function always returns zero. When Visual Basic encounters a new variable name, it can't determine if you
actually meant to create a new variable or if you just made a mistake typing an existing variable name. Because it
assumes you want to create a variable, it creates a new variable named dblTmp.
Explicit Declaration

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

59

To avoid the problem of misspelling variable names, specify an Option Explicit statement in the Declarations
section of your modules. The Option Explicit statement forces the explicit declaration of all variables in a module.
If Visual Basic encounters a name not explicitly declared as a variable, it generates an error message.
If you had specified an Option Explicit statement for the module containing the SafeSqr function shown earlier,
Visual Basic would have recognized dblTemp and dblTmp as undeclared variables and would have generated
errors for both of them. You would then explicitly declare dblTemp, as shown in the following code:
Function SafeSqr(ByVal dblNum As Double) As Double
Dim dblTemp As Double
dblTemp = Abs(dblNum)
SafeSqr = Sqr(dblTmp)
End Function
Now Microsoft Access would display an error message for the incorrectly spelled dblTmp and the problem would
be clear. Because the Option Explicit statement helps you catch these kinds of errors, you should use it with all
your code.
You can also force variables to be explicitly declared in any new modules by clicking Options (Tools menu),
clicking the Module tab, and then selecting the Require Variable Declaration check box. This automatically inserts
the Option Explicit statement in any new modules.
Note The Option Explicit statement operates on a per-module basis; it must be placed in the Declarations section
of every form, report, and standard module for which you want Visual Basic to enforce explicit variable
declarations. If you select the Require Variable Declaration check box, Visual Basic inserts the Option Explicit
statement in all subsequent form, report, and standard modules, but doesn't add it to existing code. You must
manually add the Option Explicit statement to any existing modules within an application.
Scope and Lifetime of Variables
When you declare a variable within a procedure, only code within that procedure can read or change the value of
that variable: Its scope is local to that procedure. Sometimes, however, you want to use a variable with a broader
scope, one whose value is available to all procedures within the same module, or even to all the procedures in all
modules. With Visual Basic, you can specify the scope of a variable when you declare it.
Scope of Variables
Depending on how you declare a variable, it's either a procedure-level or a module-level variable.
Procedure-level variables are recognized only in the procedure in which they're declared. These are also known as
local variables. You declare them with the Dim or Static keywords, as follows:
Dim intTemp As Integer
Static intPermanent As Integer
Local variables declared with the Dim keyword exist only as long as the procedure is running. Local variables
declared with the Static keyword exist the entire time your application is running.
Because of the advantages they provide, consider using local variables for any kind of temporary calculation. For
example, you can create a dozen different procedures containing a variable called intTemp. As long as each
intTemp is declared as a local variable, each procedure recognizes only its own version of intTemp. Any one
procedure can alter the value in its local intTemp without affecting intTemp variables in other procedures.
Note Implicitly declared variables always have a local scope.
Variables Used Within a Module

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

60

By default, a module-level variable is available to all the procedures in that module, but not to code in other
modules. You create module-level variables in form, report, and standard modules by declaring them with the
Dim or Private keywords in the Declarations section at the top of the module. For example, use either of the
following statements:
Dim intTemp As Integer
Private intTemp As Integer
At the module level, there is no difference between Private and Dim. However, using Private is recommended
because it readily contrasts with Public and makes your code easier to read.
Variables Used by All Modules
To make a module-level variable available to other modules, use the Public keyword to declare the variable. (The
Public statement has replaced the Global statement used for declaring variables in Microsoft Access version 2.0.)
The values in public variables are available to all procedures in all modules in your application. Like all modulelevel variables, public variables are declared in the Declarations section at the top of the module, as shown in the
following example:
Public intX As Integer
Note You can't declare public variables within a procedure.
In a form, report, or standard module, if you want to refer to a public variable in a different form or report
module, you must qualify the reference by using the name of the form or report, such as Forms!Orders.intX.
You can declare public variables in any module. Generally speaking, each public variable should be located in the
module of which it's a logical member. However, if you have public variables that are used for purely global
purposes and are not specifically related to a particular module, you can put them all in one module. This makes
the variables easier to find and your code easier to read.
Scope and Variable Names
A variable can't change scope while your code is running. However, you can use the same name for different
variables under certain conditions.
If public variables in different modules share the same name, it's possible to differentiate between them in code.
For example, if a public Integer variable intX is declared in both of the standard modules Module1 and Module2,
you can refer to them as Module1.intX and Module2.intX to get the correct values.
Public vs. Local Variables
You can also have a variable with the same name at a different scope. For example, you could have a public
variable named intX and then, within a procedure, declare a local variable named intX. References to the name
intX within the procedure would access the local variable, while references to intX outside the procedure would
access the public variable. The module-level variable can be accessed from within the procedure by qualifying the
variable with the module name.
' This code is in the form module for Form1. Within the Test Sub
' procedure, the name intX always refers to the local variable, not
' to the variable declared in the Public statement at the top.
Public intX As Integer
Sub Test()
Dim intX As Integer
intX = 2
' intX has a value of 2 (even though

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

61

MsgBox Forms!Form1.intX
' MsgBox shows the value of intX in
End Sub
' Form1, which is 1).
Private Sub Form_Load()
intX = 1
End Sub
Private Sub Command1_Click()
Test
' intX has a value of 2.
End Sub
In general, when variables have the same name but a different scope, the more local variable shadows (is accessed
in preference to) less local variables. So, if you also had a private module-level variable named intX, it would
shadow the public variable intX within that module (and the local intX would shadow the private module-level
intX within that procedure).
Using Variables and Properties with the Same Name
Shadowing rules treat form and report properties and controls, user-defined types, constants, and procedures as
module-level variables in the form or report module. Thus, all module-level shadowing rules apply to each of
these. You can't have a form or report property or control with the same name as a module-level variable,
constant, user-defined type, or procedure because both are in the same scope.
Within the form or report module, local variables with the same names as controls on the form or report shadow
the controls. You need to either qualify the control with a reference to the form or report, or you need to use the
Me keyword to set or get its value or any of its properties. The following code shows one way to do this:
Private Sub Form_Click()
Dim Text1, Caption
' Assume there is also a control on the form called Text1.
Text1 = "Variable"
' Variable shadows control.
Me.Text1 = "Control"
' Must qualify with Me to get control.
Text1.Top = 0
' This causes an error!
Me.Text1.Top = 0
' Must qualify with Me to get control.
Caption = "Orders"
' Variable shadows property.
Me.Caption = "Orders"
' Must qualify with Me to get form property.
End Sub
Using Variables and Procedures with the Same Name
You may also have conflicts with the names of your private module-level and public module-level variables and
the names of your procedures. A variable in the module can't have the same name as any procedures or types
defined in the module. It can, however, have the same name as public procedures, types, or variables defined in
other modules. In this case, when the variable is accessed from another module, it must be qualified with the
module name.
While these shadowing rules are not complex, shadowing can be confusing and lead to subtle problems in your
code. To avoid these problems, it's good programming practice to keep the names of your variables distinct from
each other. For example, in form or report modules, try to have unique names for properties that are different from
names of controls on those forms or reports. This applies to procedure names as well.
Lifetime of Variables
In addition to scope, variables also have a lifetime, which is the time during which a variable retains its value. The
values in module-level and public variables are preserved as long as the database is open (unless you reinitialize
your code). This is true for form and report module-level variables even if you close the form or report. However,
local variables declared with the Dim keyword exist only while the procedure in which they are declared is
running. Usually, when a procedure has finished, the values of its local variables are discarded and the memory

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

62

used by the local variables is reclaimed. The next time the procedure is run, all of its local variables are
reinitialized. However, you can make Visual Basic preserve the value of a local variable by making the variable
static.
Static Variables
To make a local variable in a procedure static, use the Static keyword to declare the variable exactly as you would
using the Dim statement.
Static intX As Integer
For example, the following function calculates a running total by adding a new value to the total of previous
values stored in the static variable dblAccumulate.
Function RunningTotal(ByVal dblNum As Double) As Double
Static dblAccumulate As Double
dblAccumulate = dblAccumulate + dblNum
RunningTotal = dblAccumulate
End Function
If you declare dblAccumulate with the Dim keyword instead of the Static keyword, the previous accumulated
values aren't preserved across calls to the function, and the function simply returns the value with which it was
called.
You can produce the same result by declaring dblAccumulate in the Declarations section of the module, making it
a module-level variable. However, after you change the scope of a variable in this way, the procedure no longer
has exclusive access to that variable. If other procedures access the value of the variable and change it, the
running totals would be unreliable.
Declaring All Local Variables as Static
To make all local variables in a procedure static, place the Static keyword at the beginning of a procedure
declaration, as shown in the following example:
Static Function RunningTotal(ByVal dblNum As Double) As Double
This makes all the local variables in the procedure static, regardless of whether they are declared with the Static or
Dim keywords, or declared implicitly. You can place the Static keyword in front of any Function or Sub procedure
heading, including event procedures and those that are also declared as Private.
Fundamental Variable Data Types
When you declare a variable, you can also supply a data type for the variable. All variables have a data type that
determines what kind of data they can store. By default, if you don't supply a data type (or if you declare the
variable implicitly), the variable is given the Variant data type.
The Variant Data Type
The Variant data type can store many kinds of data. Like a text box control on a form, a Variant variable is equally
capable of storing numbers, strings of text, dates and times, or the Null value. You don't have to convert between
these types of data when assigning them to a Variant variable; Visual Basic automatically performs any necessary
conversion, as shown in the following example:
Dim varX
varX = "17"
varX = varX - 15

' Variant by default.


' varX contains the two-character string "17".
' varX now contains the numeric value 2.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006


varX = "U" & varX

63

' varX now contains the string "U2".

Although you can perform operations on Variant variables without much concern for what kind of data they
actually contain, there are some pitfalls you'll want to avoid:
If you perform arithmetic operations on a Variant, or use a Variant in an arithmetic function, the variable must
contain a valid number. For example, you can't perform any arithmetic operations on the value "U2" even though
it contains a numeric character, because the entire value isn't a valid number. Likewise, you can't perform any
calculations on the value "1040EZ". However, you can perform numeric calculations on the values "+10" and "1.7E62" because they are valid numbers.
You can use the IsNumeric function to determine if the value contained by a Variant variable can be used as a
valid number in an expression. For example:
If IsNumeric(varX) And IsNumeric(varY) Then
varZ = varX * varY
Else
varZ = Null
End If
If you're concatenating strings, use the & operator instead of the + operator. The result of the + operator can be
ambiguous when used with two Variant values.
If both of the Variant values contain numbers, then the + operator performs addition. If both of the Variant values
contain strings, then the + operator performs string concatenation. However, if one of the values is a number and
the other is a string, the situation becomes more complicated. Visual Basic first attempts to convert the string into
a number. If the conversion is successful, then the + operator adds the two values; if unsuccessful, it generates a
"Type mismatch" error.
To make sure that concatenation occurs, regardless of the representation of the value in the variables, use the &
operator. For example, the following code:
Sub Test()
Dim varX As Variant, varY As Variant
varX = "6"
varY = "7"
Debug.Print varX + varY, varX & varY
varX = 6
Debug.Print varX + varY, varX & varY
End Sub
produces the following result in the Debug window:
Important When typing your code, it's important to leave a space between any variable name and the & operator.
If you don't leave a space, Visual Basic assumes you intended to use the & as the type-declaration character for
the variable name.
In addition to strings and numbers, Variant variables can also contain date/time values. For example:
Function Century() As Integer
Dim varToday As Variant
varToday = Now
If varToday >= #1/1/2001# Then
Century = 21
Else

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

64

Century = 20
End If
End Function
In the same way that you can use the IsNumeric function to determine if a Variant variable contains a valid
numeric value, you can use the IsDate function to determine if a Variant variable contains a valid date/time value.
For example:
Function Century (ByVal varDate As Variant) As Variant
If IsDate(varDate) Then
Century = ((Year(varDate) - 1) \ 100) + 1
Else
Century = Null
End If
End Function
Objects, including Automation objects, can also be stored in Variant variables. This can be useful when you need
to handle a variety of data types, including objects. For example, all the elements in an array must have the same
data type. Setting the data type of an array to Variant allows you to store objects alongside other data types in an
array.
The Empty Value
Sometimes you need to know if a Variant variable has ever been assigned a value since the variable was created.
A Variant variable has the Empty value before it's assigned a value. The Empty value is a special value different
from 0, a zero-length string (""), or the Null value. You can use the IsEmpty function to determine if a Variant
variable has the Empty value.
If IsEmpty(varX) Then varX = 0
When you use a Variant in an expression, an Empty value is treated as either 0 or a zero-length string, depending
on the expression. The Empty value disappears as soon as any value is assigned to a Variant variable (including
the value of 0, the zero-length string, and the Null value). You can set a Variant variable back to the Empty value
by assigning the Empty keyword to the Variant.
The Null Value
The Variant data type can contain one other special value: Null. Null is commonly used in database applications to
indicate unknown or missing data. Fields and controls that haven't been initialized have a default value of Null.
You can use the IsNull function to determine if a Variant variable contains the Null value.
If IsNull(varX) And IsNull(varY) Then
varZ = Null
Else
varZ = 0
End If
A Null value has some unique characteristics:
Expressions involving Null always result in Null. Thus, Null is said to propagate through expressions; if any part
of the expression evaluates to Null, the entire expression evaluates to Null.
Most functions return Null if you pass them Null, a Variant containing Null, or an expression that evaluates to
Null as an argument.
Null values propagate through intrinsic (built-in) functions that return Variant data types.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

65

You can assign a Null value by using the Null keyword. For example:
varZ = Null
Only Variant variables can contain Null values. If you assign Null to a variable of any data type other than
Variant, a trappable error occurs. Assigning Null to a Variant variable doesn't cause an error, and Null will
propagate through expressions involving Variant variables (though Null doesn't propagate through certain
functions). For example, the following code:
Sub Test()
Dim varX As Variant, varY As Variant
varX = "6"
varY = Null
Debug.Print varX + varY, varX & varY
End Sub
produces this result in the Debug window:
Null 6
In addition, you can return Null from any Function procedure that has a Variant return value.
Tip The fact that Null propagates makes it useful as an error value. If you write Function procedures that return
Null when an error occurs, and then combine these functions in expressions, you can use the IsNull function to
test the final result of the expression to see if an error has occurred. Because Null propagates, the final result is
Null if an error has occurred in any of the functions; you don't have to test the result of each function separately.
Other Fundamental Data Types
The Variant data type handles all types of fundamental data and converts between them automatically. However,
you can usually create more concise, faster code by using other data types where appropriate. For example, if a
variable will always contain small integer values, you can save several bytes, and significantly increase the speed
of arithmetic operations on the variable, by declaring that variable to be Integer instead of Variant.
The following table lists the fundamental data types in Visual Basic,
including Variant.
Date/time, floating-point number, integer, string, or object. 16 bytes, plus 1 byte for each character if a string
value.
Date values: January 1, 100 to December 31, 9999
Numeric values: same range as Double
String values: same range as String
When you declare a variable by using a Dim, Public, Private, or Static statement, you use the As type clause to
specify the data type of the variable. For example, the following statements declare Integer, Currency, Double,
and String variables, respectively:
Dim intX As Integer
Public curBillsPaid As Currency
Private dblAmt As Double
Static strName As String
A declaration statement can combine multiple declarations, as in the following statements:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

66

Dim intX As Integer, dblAmt As Double


Dim strName As String, curBillsPaid As Currency
Dim varTest, intY As Integer, varAmount
Important In a multiple declaration statement, you must use the As type clause for each variable whose data type
you want to specify. If you don't specify a data type, Visual Basic declares the variable as Variant. In the last line
of code in the preceding example, Visual Basic declares only the variable intY as Integer. The variables Test and
Amount are each declared as Variant.
Most of the Visual Basic data types match the data types for fields that contain data. The few field data types that
aren't directly matched by a Visual Basic data type can be handled by another Visual Basic data type.
Note SQL data types are also used in Microsoft Access queries. For information on these data types, search the
Help index for "ANSI SQL data types."
If a variable must be able to accept Null values, declare it as a Variant rather than one of the other fundamental
data types. The Variant data type can accept Null values, while the other fundamental data types cannot.
Numeric Data Types
If you know that a variable always stores whole numbers (for example, 12) rather than fractional numbers (for
example, 3.57), declare it as an Integer or Long data type. Operations are faster with integers, and Integer and
Long use less memory than Variant, Double, or Currency. Integers are especially useful as the counter variables in
For...Next loops.
If the variable contains a fraction, declare it as a Single, Double, or Currency variable. The Currency data type
supports up to 4 digits to the right of the decimal point and 15 to the left; it's a fast and accurate fixed-point data
type suitable for monetary calculations. Floating-point (Single and Double) numbers have much larger ranges
than Currency, but are subject to small rounding errors.
Note Floating-point values can be expressed as mmmEeee, in which mmm is the mantissa and eee is the
exponent (a power of ten). The highest positive value of a Single data type is 3.402823E+38, or 3.4 times 10 to
the 38th power; the highest positive value of a Double data type is 1.79769313486231E+308, or about 1.8 times
10 to the 308th power.
If the variable contains binary data, declare it as an array of the Byte data type. Using Byte variables to store
binary data preserves the data during format conversions. When String variables are converted between
ANSI/DBCS and Unicode formats, any binary data in the variable may be corrupted. Visual Basic will
automatically convert between ANSI/DBCS and Unicode in any of the following circumstances:
Reading from files
Writing to files
Calling DLLs
All arithmetic operators work with the Byte data type.
All numeric variables can be assigned to each other and to variables of the Variant data type. Visual Basic rounds
off (doesn't truncate) the fractional part of a floating-point number before assigning it to an integer.
The String Data Type
If you have a variable that will always contain a string and never a numeric value, you can declare it as a String
data type.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

67

Dim strAny As String


You can then assign strings to this variable and manipulate it by using string functions.
strAny = "Database"
strAny = Left(strAny, 4)
Fixed-length strings in standard modules can be declared as Public or Private. In form and report modules, fixedlength strings must be declared as Private.
By default, a string variable or argument is a variable-length string, which means the string grows or shrinks as
you assign new data to it. You can also declare strings that have a fixed length. You specify a fixed-length string
with the following syntax:
String * size
For example, the following code declares a string that is always 50 characters long:
Dim strEmpName As String * 50
If you assign a string of fewer then 50 characters, strEmpName is padded with enough trailing spaces to total 50
characters. If you assign a string that is too long for the fixed-length string, the extra characters are truncated. For
example, the following code:
Dim strJust4 As String * 4
Dim strAny As String
strAny = "Database"
Debug.Print strAny
strJust4 = strAny
Debug.Print strJust4
produces the following result in the Debug window:
Database
Data
Because fixed-length strings are padded with trailing spaces, you may find the Trim, LTrim, and RTrim functions
useful when working with them.
Visual Basic compares strings in one of several different ways, depending on the Option Compare statement
specified in the Declarations section of your modules. You can specify either Option Compare Database, Option
Compare Binary, or Option Compare Text to determine the relative ordering used in a comparison, and whether or
not string comparisons are case-sensitive.
Microsoft Access automatically inserts an Option Compare Database statement in the Declarations section of a
new module, specifying that the string comparisons in that module are based on the database sort order. If no
Option Compare statement is specified in a module, Visual Basic does case-sensitive Binary comparisons in that
module based on the character's relative order of appearance in the ANSI character set.
The Boolean Data Type
If you have a variable that will contain simple yes/no or on/off information, you can declare it as a Boolean data
type. The default value of Boolean is False. In the following example, blnCreditExceeded is a Boolean variable
that stores a simple True or False setting.
Dim blnCreditExceeded As Boolean

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

68

' Add all charges.


Do Until rstCharges.EOF
curAmt = curAmt + rstCharges("Amount").Value
rstCharges.MoveNext
Loop
' Ask if the credit limit is exceeded.
If curAmt > curLimit Then blnCreditExceeded = True
The Date Data Type
Date and time values can be contained both in the specific Date data type and in Variant variables. The same
general characteristics apply to dates in both types.
When other numeric data types are converted to Date, values to the left of the decimal represent date information,
while values to the right of the decimal represent time. Midnight is 0, and noon is 0.5. Negative whole numbers
represent dates before December 30, 1899.
The Object Data Type
Object variables are stored as 32-bit (4-byte) addresses that refer to objects within an application or within some
other application. A variable declared as Object can subsequently be assigned (by using the Set statement) to refer
to any actual object recognized by the application. For example:
Const conFilePath As String = "C:\Program Files\Microsoft Office\Office\Samples\"
Dim objDb As Object
Set objDb = OpenDatabase(conFilePath & "Northwind.mdb")
When declaring object variables, instead of using a Variant data type or the generic Object data type, declare
objects as they are listed in the Classes box in the Object Browser. Visual Basic can resolve references to the
properties and methods of objects with specific types at compile time rather than at run time. This catches
common errors sooner, and makes your code run faster.
Tip You may want to think of the objects listed in the Classes box in the Object Browser as additional data types
that are available to you. You can declare objects from other applications and other applications can declare
objects from your application in the same way that you declare ordinary object data types in Visual Basic.
Argument Data Types
The arguments for procedures you write have the Variant data type by default. However, you can declare other
data types for arguments. For example, the following function accepts a String and an Integer:
Function Reverse (strAny As String, ByVal intChars As Integer) As String
' Reverses the first intChars characters in strAny.
Dim strTemp As String, intCount As Integer
If intChars > Len(strAny) Then intChars = Len(strAny)
For intCount = intChars To 1 Step - 1
strTemp = strTemp + Mid(strAny, intCount, 1)
Next
Reverse = strTemp + Right(strAny, Len(strAny) - intChars)
End Function
If you specify a data type for an argument, you must either:
Pass a value of that type for the argument.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

69

Declare the argument by using the ByVal keyword to specify that the argument is passed by value rather than by
reference. When you pass a variable by value, changes to the variable in the procedure don't affect its value in the
calling procedure.
For example, you can't pass a Variant by reference to a string argument. Thus, the following code produces an
error:
Dim varTest As Variant
varTest = "Testing"
Debug.Print Reverse(varTest, 4)

' Error: argument type mismatch.

One way to avoid this problem is to pass an expression, rather than a Variant, for an argument. Visual Basic then
evaluates the expression and, if it can, passes it as the required data type. The simplest way to turn a variable into
an expression is to enclose it in parentheses. For example:
Debug.Print Reverse((varTest), 4) ' Makes the variable an expression.
However, the best way to ensure that arguments are passed correctly is to declare the arguments with the ByVal
keyword, as illustrated by the second argument, ByVal intChars As Integer, in the preceding Reverse function
example. Thus, you can pass a Variant as the second argument to the Reverse function. For example:
Dim strTest As String, varTest As Variant
strTest = "Testing"
varTest = "2"
Debug.Print Reverse(strTest, varTest)
' Works!
When you pass a variable to a procedure by reference, the variable's value can be changed by that procedure. On
the other hand, when you pass the variable by value, only a copy of the variable is passed to the procedure;
therefore, if the procedure changes that value, the change affects only the copy and not the variable itself. This is
important in the Reverse function; if the second argument isn't declared with the ByVal keyword, bugs could
appear in the code. For example, suppose the second argument wasn't declared with the ByVal keyword, and you
called it as follows:
Dim intTest As Integer, strTest As String
intTest = 10
strTest = "Testing"
Debug.Print Reverse(strTest, intTest) ' Now intTest = 7 (length of strTest).
You don't usually expect a function to modify its arguments, as happens here. To avoid this kind of side effect in
any procedures that modify their arguments, declare those arguments with the ByVal keyword.
Function Return Data Types
The value returned by a function has a data type. When you define the function, you can declare the data type of
the value the function returns. For example, the Reverse function in the preceding section returns a String.
As with variables, Visual Basic can work more efficiently with functions if you explicitly declare a data type for
the values they return. If you don't declare a data type, functions use the Variant data type. For example, if you
don't set a return value for a function (by assigning a value to the name of the function), the function returns a
Variant containing the Empty value.
If you declare the function to return a String, as in the Reverse function example, the function returns a zerolength string ("") if you don't assign a return value. If you declare the function to return a numeric data type, such
as Integer or Double, it returns zero if you don't explicitly assign a return value.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

70

Creating Your Own Data Types


You can combine variables of several different types to create user-defined types (known as structures in the C
programming language). User-defined types are useful when you want to create a single variable that records
several related pieces of information.
Declaring a User-Defined Type
You create a user-defined type with the Type statement, which must be placed in the Declarations section of a
standard module. User-defined types can be declared as Private or Public with the appropriate keyword, as shown
in the following examples:
Private Type YourType
Public Type YourType
For example, you could create a user-defined type that records information about a computer system by placing
the following code in the Declarations section of any standard module:
Public Type SystemInfo
varCPU As Variant
lngMemory As Long
intVideoColors As Integer
curCost As Currency
dtePurchase As Variant
End Type
Declaring Variables of a User-Defined Type
You can declare local, private module-level, or public module-level variables of the same user-defined type. For
example:
Dim sysMine As SystemInfo, sysYours As SystemInfo
The following table illustrates where, and with what scope, you can declare user-defined types and their variables.
Procedure/Module

Assigning and Retrieving Values


Assigning and retrieving values from the elements of a user-defined variable is similar to setting and getting
properties.
sysMine.varCPU = "486"
If sysMine.dtePurchase > #1/1/92# Then
You can also assign one variable to another if they are both of the same user-defined type. This assigns all the
elements of one variable to the same elements in the other variable.
sysYours = sysMine
User-Defined Types That Contain Arrays
A user-defined type can contain an ordinary (fixed-size) array, as shown in the following example:
Type SystemInfo
varCPU As Variant

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006


lngMemory As Long
strDiskDrives(25) As String
intVideoColors As Integer
curCost As Currency
dtePurchase As Variant
End Type

71

' Fixed-size array.

It can also contain a dynamic array:


Type SystemInfo
varCPU As Variant
lngMemory As Long
strDiskDrives() As String
intVideoColors As Integer
curCost As Currency
dtePurchase As Variant
End Type

' Dynamic array.

You can access the values in an array within a user-defined type in the same way that you access the property of
an object. For example:
Dim sysMine As SystemInfo
sysMine.strDiskDrives(0) = "1.44 MB"
You can also declare an array of user-defined types:
Dim sysAll(100) As SystemInfo
Follow the same rules to access the components of each element in the array:
sysAll(5).varCPU = "386SX"
sysAll(intX).strDiskDrives(2) = "100M SCSI"
Declaring Procedure Arguments
You can declare procedure arguments with a user-defined type.
Sub FillSystem(sysAny As SystemInfo)
sysAny.varCPU = "486"
sysAny.lngMemory = "24"
sysAny.curCost = "$3000.00"
sysAny.dtePurchase = Now
End Sub
Note If you want to pass a user-defined type in a form or report module, the procedure must be private.
You can return user-defined types from functions, and you can pass a user-defined type variable to a procedure as
one of the arguments. Because user-defined types are always passed by reference, the procedure can modify the
argument and return it to the calling procedure, as illustrated in the previous example.
User-Defined Types That Contain Objects
User-defined types can also contain objects. For example:
Private Type AccountPack
frmInput As Form

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

72

dbsPayRollAccount As Database
End Type
Tip Because the Variant data type can store many different types of data, a Variant array can be used in many
situations where you may expect to use a user-defined type. A Variant array is actually more flexible than a userdefined type, because you can change the type of data you store in each element at any time, and you can make
the array dynamic so that you can change its size as necessary. However, a Variant array always uses more
memory than an equivalent user-defined type.
Nesting Data Structures
Nesting data structures can get as complex as you want. In fact, user-defined types can contain other user-defined
types, as shown in the following example. To make your code more readable and easier to debug, try to keep all
the code that defines user-defined types in one module. For example:
Type DriveInfo
Type As String
Size As Long
End Type
Type SystemInfo
varCPU As Variant
lngMemory As Long
strDiskDrives(26) As DriveInfo
curCost As Currency
dtePurchase As Variant
End Type
The following code demonstrates how you can refer to a nested user-defined type:
Dim sysAll(100) As SystemInfo
sysAll(1).strDiskDrives(0).Type = "Floppy"
Constants
Your code may contain unchanging values that appear over and over. Or your code may depend on certain
numbers that are difficult to remember numbers that, in and of themselves, have no obvious meaning.
In these cases you can greatly improve the readability of your code and make it easier to maintain by using
constants. A constant is a meaningful name that takes the place of a number or string that doesn't change. You
can't modify a constant or assign a new value to it as you can to a variable. Constants have one of two sources:
Intrinsic or system-defined constants are provided by applications. Microsoft Access, Visual Basic, and Data
Access Objects (DAO) constants are all available in Microsoft Access, along with intrinsic constants from other
applications that provide object libraries.
Symbolic or user-defined constants are declared by using the Const statement.
Creating Your Own Constants
You use the Const statement to create your own symbolic constants. The syntax for the Const statement is:
[Public | Private] Const constantname [As type] = expression
The argument constantname is a valid symbolic name (the rules are the same as the rules for creating variable
names), and expression is composed of numeric or string constants and operators (you can't use function calls in
expression).

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

73

A Const statement can represent a mathematical or date/time quantity. For example:


Const conPi = 3.14159265358979
Public Const conMaxPlanets = 9
Const conReleaseDate = #1/1/95#
You can also use the Const statement to define string constants:
Public Const conVersion = "07.10.A"
Const conCodeName = "Enigma"
You can place more than one constant declaration on a single line, if you separate them with commas:
Public Const conPi = 3.14, conMaxPlanets = 9, conWorldPop = 6E+09
The expression on the right side of the equal sign (=) is often a number or literal string, but it can also be an
expression that results in a number or string as long as the expression doesn't contain calls to functions. You can
even define constants in terms of previously defined constants:
Const conPi2 = conPi * 2
After you define constants, you can place them in your code to make it much more readable, as shown in the
following example:
Static SolarSystem(1 To conMaxPlanets)
If lngNumPeople > conWorldPop Then Exit Function
Defining the Scope of User-Defined Constants
A Const statement has scope just as a variable declaration does, and the same rules apply:
To create a constant that's available only within a procedure, declare it within that procedure.
To create a constant that's available to all procedures within a module, but not to any procedure outside that
module, declare the constant in the Declarations section of the module.
To create a constant that's available throughout the application, declare the constant in the Declarations section
of a standard module and place the Public keyword before Const. Public constants cannot be declared in a form or
report module.
Avoiding Circular References
It's important to be aware of one possible complication when you're using public constants: Because constants can
be defined in terms of other constants, you must be careful not to set up a circular reference between two or more
constants. A circular reference occurs when you have two or more public constants, each defined in terms of the
other, as in the following example:
' In Module 1.
Public Const conA = conB * 2
' In Module 2.
Public Const conB = conA / 2
If you create a circular reference, Visual Basic generates an error when it compiles your code. You can't compile
your code until you resolve the circular reference. To avoid creating a circular reference, restrict all your public
constants to a single module or, at most, to a small number of modules.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

74

Note You can't define a public constant in a form or report module, only in a standard module. You can,
however, declare a private constant in a form or report module.
Intrinsic Constants
In addition to the constants you declare with the Const statement, Microsoft Access automatically declares a
number of intrinsic constants. These constants are available in all modules.
Because you can't disable intrinsic constants, the constants you create can't have the same names as the intrinsic
constants. Also, you can't redeclare or set intrinsic constants to different values.
Important Because the values represented by the intrinsic constants may change in future versions of Microsoft
Access, you should use the constants instead of their actual values.
You can use intrinsic constants wherever you can use user-defined constants, including in expressions. The
following example shows how you might use the intrinsic constant vbCurrency to determine whether varTest is a
Variant of VarType 6 (Currency).
If VarType(varTest) = vbCurrency Then
Debug.Print "varTest contains Currency data."
Else
Debug.Print "varTest does not contain Currency data."
End If
The intrinsic constants for Microsoft Access, as well as those for the Visual Basic for Applications and Data
Access Objects (DAO) libraries, are listed in the Object Browser. Other applications that provide object libraries,
such as Microsoft Excel and Microsoft Project, also provide a list of constants you can use with their objects,
methods, and properties. To see these constants, click the appropriate object library in the Project/Library box of
the Object Browser, and then click Constants in the Classes box.
Important In order for constants from other applications to appear in the Object Browser, you must set a
reference to the application's object library. Open a module, and then in the References dialog box (Tools menu),
select the check box for the object library you want to set a reference for.
Intrinsic constant names are in a mixed-case format, with a two-character prefix indicating the object library that
defines the constant. Constants from Microsoft Access are prefaced with "ac"; for example, acDataErrContinue.
Constants from the VBA object library are prefaced with "vb" ; for example, vbTileHorizontal. Constants from
the DAO object library are prefaced with "db" ; for example, dbRelationUnique. All of these intrinsic constants
are available in Microsoft Access without having to be declared.
Note In Microsoft Access versions 2.0 and earlier, constant names appeared in all capital letters with underscores
for example, A_REFRESH. Microsoft Access 97 supports intrinsic constants from these early versions. To see
them, click the Access object library in the Project/Library box of the Object Browser, and then click
OldConstants in the Classes box.
Arrays
If you have programmed in other languages, you're probably familiar with the concept of arrays. You use arrays to
refer to a series of variables by the same name while using a number (an index) to tell them apart. This helps you
create smaller and simpler code in many situations because you can set up loops that deal efficiently with multiple
cases by using the index number. Arrays have both upper and lower bounds, and the elements of the array are
contiguous within those bounds. Because Visual Basic allocates space for each index number, avoid declaring an
array larger than you need it to be.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

75

All the elements in an array have the same data type. Of course, when the data type is Variant, the individual
elements may contain different kinds of data (strings, numbers, date/time values, or objects). You can declare an
array with any of the fundamental data types, including user-defined types, and object variables.
Declaring Fixed-Size Arrays
You can declare an ordinary (fixed-size) array in three ways, depending on the scope you want the array to have:
To create a public array, use the Public statement in the Declarations section of a module to declare the array.
To create a module-level array, use the Private or Dim statement in the Declarations section of a module to
declare the array.
To create a local array, use the Dim or Static statement within a procedure to declare the array.
There are additional rules when you create a dynamic array (an array whose size can change at run time).
Setting Upper and Lower Bounds
When declaring an array, follow the array name with the upper bound in parentheses. The upper bound must be a
Long data type (in the range -231 to 231). For example, the following array declarations can appear in the
Declarations section of a module:
Dim intCount(14) As Integer
' Declares an array with 15
' elements (0 through 14).
Dim dblSum(20) As Double
' Declares an array with 21
' elements (0 through 20).
To create a public array, you use Public in place of Dim (or Private):
Public intCount(14) As Integer
Public dblSum(20) As Double
To create a local array, use the Dim (or Static) statement:
Dim intCount(14) As Integer
Static dblSum(20) As Double
The first declaration creates an array with 15 elements, with index numbers running from 0 through 14. The
second creates an array with 21 elements, with index numbers running from 0 through 20. The default lower
bound is 0. However, you can change the default lower bound to 1 by placing the following Option Base
statement in the Declarations section of a module:
Option Base 1
Another way to specify the lower bound is to provide it explicitly by using the To keyword. For example:
Dim intCount(1 To 15) As Integer
Dim dblSum(100 To 120) As Double
In the preceding declarations, the index numbers of intCount run from 1 through 15 (15 elements), and the index
numbers of dblSum run from 100 through 120 (21 elements).
Note With some versions of Basic, you can use an array without first declaring it. With Visual Basic, this isn't
possible; you must declare an array before using it.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

76

Multidimensional Arrays
With Visual Basic, you can declare arrays of up to 60 dimensions. For example, the following statement declares
a two-dimensional 10-by-10 array within a procedure:
Static dblMatrix(9, 9) As Double
Either or both dimensions can be declared with explicit lower bounds:
Static dblMatrix(1 To 10, 1 To 10) As Double
You can extend this to more than two dimensions, as in the following example:
Dim intMultiD(3, 1 To 10, 1 To 15) As Integer
This declaration creates a three-dimensional 4-by-10-by-15 array. The total number of elements is the product of
these three dimensions, or 600.
Note Because the total storage needed by the array increases dramatically when you start adding dimensions to
it, be sure to use multidimensional arrays with care. Be especially careful with Variant arrays, because Variant
arrays are larger than arrays containing other data types.
Using Loops to Manipulate Arrays
Loops often provide an efficient way to manipulate arrays. For example, the following loop initializes all
elements in the array to 5:
Static intCount(1 To 15) As Integer
Dim intX As Integer
For intX = 1 To 15
intCount(intX) = 5
Next intX
You can efficiently process a multidimensional array by using nested For loops. For example, the following
statements initialize every element in dblMatrix to a value based on its location in the array:
Dim intX As Integer, intY As Integer
Static dblMatrix(1 To 10, 1 To 10) As Double
For intX = 1 To 10
For intY = 1 To 10
dblMatrix(intX, intY) = intX * 10 + intY
Next intY
Next intX
Dynamic Arrays
Sometimes you may not know exactly how large to make an array. You may want to be able to change the size of
the array at run time.
A dynamic array can be resized at any time. Dynamic arrays are among the most flexible and convenient features
in Visual Basic, and they help you to manage memory efficiently. For example, you can use a large array for a
short time and then free up memory to the system when you're no longer using the array.
The alternative is to declare an array with the largest anticipated size and then to ignore array elements you don't
need. However, if overused, this approach may cause Microsoft Access to run low on memory.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

77

To create a dynamic array


1 Declare the array with a Public, Private, or Dim statement at the module level (if you want the array to be
public or module-level), or with a Static or Dim statement at the procedure level (if you want the array to be
local). You declare the array as dynamic by giving it an empty dimension list. For example:
Dim intDynArray() As Integer
2 Allocate the actual number of elements with a ReDim statement. For example:
ReDim intDynArray(intX + 1)
The ReDim statement can appear only in a procedure. Unlike the Dim and Static statements, ReDim makes the
application carry out an action at run time.
The ReDim statement supports the same syntax as that used for fixed arrays. Each ReDim statement can change
the number of elements, as well as the lower and upper bounds for each dimension. However, the number of
dimensions in the array can't change from the number used in the first ReDim statement.
For example, you create the dynamic array intMatrix by first declaring it at the module level:
Dim intMatrix() As Integer
A function then allocates space for the array:
Function CalcValuesNow () As Integer
.
.
.
ReDim intMatrix(19, 29)
End Function
The ReDim statement allocates a matrix of 20-by-30 integers (for a total size of 600 elements). Alternatively, the
bounds of a dynamic array can be set by using variables:
ReDim intMatrix(intX, intY)
Preserving the Contents of Dynamic Arrays
It's important to note that each time you run the ReDim statement, all the values currently stored in the array are
lost. Visual Basic resets the values to the Empty value (for Variant arrays), to zero (for numeric arrays), to a zerolength string ("") (for string arrays), or to Nothing (for arrays of objects).
This is useful when you want to prepare the array for new data or when you want to shrink the size of the array to
take up minimal memory. However, you may sometimes want to change the size of the array without losing the
data in the array. You can do this by using the ReDim statement with the Preserve keyword. For example, you can
enlarge an array by one element without losing the values of the existing elements, as shown in the following
code:
ReDim Preserve intMatrix(UBound(intMatrix) + 1)
Only the upper bound of the last dimension in a multidimensional array can be changed when you use the
Preserve keyword. If you change any of the other dimensions or the lower bound of the last dimension, a run-time
error occurs. Therefore, you can use code as follows:
ReDim Preserve intMatrix(10, UBound(intMatrix, 2) + 1)

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

78

But you cannot use the following code:


ReDim Preserve intMatrix(UBound(intMatrix, 1) + 1, 10)

Working with Objects and Collections


A Microsoft Access database is made up of different kinds of objects. Some kinds of objects are used to display
the data in your database, while others are used to store and manage the data itself, or to assist you in
programming in Visual Basic. You can create a database using tables, queries, forms, reports, and macros without
writing any Visual Basic code. If your needs are more sophisticated, however, you can use Visual Basic to create,
control, and manage all of the different types of objects in a Microsoft Access database. This chapter explains
how to program with objects in Visual Basic.
Understanding Objects and Collections
As you've seen in earlier chapters of this book, you can produce powerful applications in Microsoft Access
without programming. However, when you need a more sophisticated application, writing Visual Basic code gives
you a greater degree of control over what your application does. When you program in Visual Basic, you work
with objects that correspond to different aspects of your database. Collections are sets of objects of the same type.
Programming with objects and collections gives you added flexibility in that you can design your application to
respond to user actions and input in a customized way.
Objects available to you in Microsoft Access come from four different sources:
Microsoft Access provides objects that you use to display your data, such as Form, Report, and Control objects.
Microsoft DAO provides Data Access Objects (DAO), such as the TableDef and QueryDef objects, which
determine the structure of your database and which you can use to manipulate data in Visual Basic.
Visual Basic provides objects that give you more flexibility in programming, such as the Debug object and the
Err object.
Microsoft Office provides objects that you can use to customize the interface of your application, such as the
CommandBar and FileSearch objects.
Many of the objects that you work with in Visual Basic correspond to specific parts of your Microsoft Access
database. For example, the Microsoft Access Form and Report objects correspond to your forms and reports,
while the DAO TableDef and QueryDef objects correspond to your tables and queries. The Microsoft Office
CommandBar object corresponds to the toolbars and menu bars you see in Microsoft Access.
Other objects you may work with in Visual Basic are more abstract. For example, the Visual Basic Err object
contains information about errors that occur while Visual Basic code is running, rather than corresponding to a
specific part of your database.
Even though the objects available to you in Microsoft Access come from several different sources and perform
different functions, you can work with them in similar ways. You'll find that the concepts you need to understand
in order to program with one object apply to most objects.
For example, every object has properties and methods associated with it. Once you understand how to work with
one object's properties and methods, you can use those same concepts to work with any object's properties and
methods. You use properties to determine or change some characteristic of an object. For example, you use a
form's Visible property to determine whether or not the form is visible to the user. You use methods to perform a
particular operation on an object. For example, the Repaint method completes any pending screen updates on a
specified form.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

79

Organization of Objects and Collections


Each application that supplies objects to Microsoft Access provides an object library. The object library contains
information about an application's objects and their properties and methods. Microsoft Access includes four builtin object libraries, which are described in the following sections.
Microsoft Access objects and DAO objects are organized in object hierarchies. In an object hierarchy, certain
objects contain other objects and collections. A collection is a special type of object that is actually a set of all
objects of a given type. When you refer to a collection in code, you are referring to all the objects in the set. For
example, the Microsoft Access Application object contains a Forms collection, which contains individual Form
objects. A Form object contains a Controls collection, which in turn contains individual Control objects. The
following illustration shows this relationship.
You can think of a collection as an array of objects that's already declared by Microsoft Access. Collections, like
arrays, have elements, and you refer to the objects in a collection as elements of the collection. You can refer to an
element of a collection by its name or by its position within the collection.
The following sections describe the four object libraries included in Microsoft Access.
Microsoft Access Objects
You're probably already familiar with some of the objects in the Microsoft Access 8.0 object library. Microsoft
Access Form, Report, and Control objects correspond to your forms, reports, and controls. You use these objects
to control the way you display the data in your database. The Application object and the Reference object make it
easier to work with objects in other applications. The Module object gives you control over the Visual Basic
modules in your database. You use the DoCmd object to include macro actions in your code and the Screen object
to refer to the active object on the screen.
Of the objects listed in the preceding table, the Control, Form, Module, Reference, and Report objects belong to
collections. The Application object is the top-level object in the hierarchy, and all other objects exist beneath it.
The following illustration shows the hierarchical organization of Microsoft Access objects and collections.
DAO Objects
The Microsoft DAO 3.5 object library provides DAO objects, which you can use to work with the Microsoft Jet
database engine. Some DAO objects represent the structure of your database and the relationships between the
data in it. These objects include the Database, TableDef, QueryDef, Field, Index, Parameter, Property, and
Relation objects. Other objects are responsible for the security of your database, including the Container,
Document, User, Group, and Workspace objects. The Recordset object provides you with direct access to the data
in the database. The DBEngine object gives you control over the Microsoft Jet database engine itself. The
Connection object represents a network connection to an Open Database Connectivity (ODBC) database and is
available only when you're working with an ODBCDirect Workspace object.
Active Microsoft Jet session
Every object in the Microsoft DAO 3.5 object library belongs to an associated collection, except the DBEngine
object. The DBEngine object is the top-level object that gives you access to all other objects in the collection, like
the Microsoft Access Application object in the Microsoft Access object hierarchy. The following illustration
shows the hierarchical organization of the DAO objects. For simplicity, it shows only the DBEngine object and
the collections for all other objects in the object hierarchy.
Each DAO object also contains a Properties collection. The Properties collection contains Property objects that
represent the properties available for each object in the DAO object hierarchy.
Visual Basic Objects

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

80

The Visual Basic for Applications object library provides three objects to Microsoft Access, but these objects
aren't organized in an object hierarchy. None of them belong to a collection of other objects. The following table
describes the objects provided by the Visual Basic for Applications object library.
Information about Visual Basic errors
Microsoft Office Objects
The Microsoft Office 8.0 object library provides objects you can use to customize the appearance of your
application or to implement some features common to Microsoft Office applications. For example, you can create
customized toolbars and menu bars in code by using the CommandBar object. You can perform custom file
searches by using the FileSearch object. You can also customize the Office Assistant to respond to the user's
actions.
Note In order to use objects in the Microsoft Office 8.0 object library from Visual Basic, you must first set a
reference to the object library. When you set a reference to an object library, you notify Visual Basic that you may
want to use the objects in that library. To set a reference to the Microsoft Office 8.0 object library, open a module
and click References on the Tools menu. Then select the Microsoft Office 8.0 Object Library check box in the
Available References box.
Not all of the objects in the Microsoft Office 8.0 object library are useful in Microsoft Access. The following table
describes some of the objects in the Microsoft Office 8.0 object library which Microsoft Access developers may
find useful.
Files found through file search operation
Note The Office Assistant is not available in Microsoft Access run-time applications.

Working with Objects and Collections


Now that you're familiar with the objects available in Microsoft Access, you can begin working with them in
Visual Basic. The following sections provide you with the information you need to begin working with objects.
Referring to Objects
To use an object in Visual Basic, you must specify which object it is that you intend to use. There are two types of
objects with which you need to be concerned: objects that exist individually and don't belong to collections, and
objects that belong to collections.
Some objects, such as the Microsoft Access Application object, are not members of a collection. Most of the time
you can refer to these objects directly in your code. For example, you refer to the Application object in Visual
Basic as follows:
Application
Other objects belong to collections, and you need to distinguish which object in the collection you want to work
with, and which collection contains the object. For example, the DAO TableDef, QueryDef, Recordset, and
Relation objects all have a Fields collection. If you refer to a Field object, you need to specify to which collection
it belongs. Also, it's likely that there's more than one Field object in the Fields collection of one of these objects.
To refer to a particular Field object, you must provide either its name or its position in the collection.
There are three ways to refer to an object in a collection. The fastest way is to provide the name of the collection
followed by the name of the object to which you are referring, as shown in the following examples:
Forms!Employees

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

81

QueryDefs![Current Product List]


Use the ! operator to separate the name of the collection from the name of the particular object within it. Also, if
the name of the object contains spaces, you must enclose it in brackets. Finally, keep in mind that the Forms
collection includes only forms that are currently open. If the Employees form isn't open when you run the code in
the preceding example, an error occurs. The same is true for the Reports collection.
In most cases, you'll know the name of the object to which you're referring, and you should use this syntax.
Occasionally, however, you may not know the name of the object until the procedure is running. In this case, you
can use a string variable to represent the name of the object. In the following examples, strFormName and
strQueryDefName are string variables that contain the name of a Form object and a QueryDef object.
Forms(strFormName)
QueryDefs(strQueryDefName)
If the value of strFormName is "Employees" and the value of strQueryDefName is "Current Product Name", then
the previous example is equivalent to the following lines of code:
Forms("Employees")
QueryDefs("Current Product Name")
You can also refer to an object in a collection by its index number. Like the elements of an array, each object in a
collection has an index number that refers to its position in the collection. The following examples use the index
number to refer to a particular object in a collection.
Forms(0)
QueryDefs(1)
Most collections are indexed beginning with zero. That is, the first object in a collection has an index of 0, the
second has an index of 1, and so on. The first line of code in the previous example refers to the first open Form
object in the Forms collection. In most cases, Form objects are indexed according to the order in which they were
opened.
Note Some collections, such as the Microsoft Office CommandBars collection, are indexed beginning with 1
rather than 0. To determine how a particular collection is indexed, search the Help index for the name of that
collection.
The second line refers to the second QueryDef object in the QueryDefs collection. The QueryDefs collection
includes all saved queries in the database, regardless of whether they are open. In most cases, QueryDef objects
and other objects are indexed according to the order in which they were created in the database.
When you refer to an object in code in any of these ways, Visual Basic returns an object reference. An object
reference points to the place in memory where a particular object exists. When you work with an object in Visual
Basic, you're actually working with a reference to that object in memory.
Referring to Objects in a Default Collection
Many objects in Microsoft Access contain one or more collections of lower-level objects, and one of these
collections is generally designated as the default collection for that object. For example, a Form object contains a
Controls collection, which is the collection you're most likely to use with a Form object. Since the Controls
collection is the default collection of a Form object, you can refer to the collection without explicitly specifying
its name.
The following line of code returns an object reference to a control called LastName on the Employees form using
the default collection.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

82

Forms!Employees!LastName
You can also use the full reference to the control, as shown in the following line of code:
Forms!Employees.Controls!LastName
Databases

Declaring and Assigning Object Variables


The preceding sections have shown how to return a reference to an object in order to work with that object in
Visual Basic. It's possible to use an object reference throughout your code each time you need to refer to a
particular object. However, your code runs more quickly if you declare an object variable to represent the object
instead. An object variable is a variable that represents an object in Visual Basic.
To create an object variable, you first declare it as you would declare any variable, by using a Dim, ReDim,
Static, Private, or Public statement. You can declare an object variable as a specific type of object or as the more
generic type Object. You can also assign a variable of type Variant to an object. Whenever possible, declare an
object variable as a specific type of object, because this makes your code run faster. The following line of code
declares an object variable as type Form.
Dim frm As Form
Once you've declared an object variable, you assign an object reference to it. An object reference, as discussed in
the previous section, refers to an object in memory. Each time you use an object reference, Visual Basic looks up
the object in memory. When you assign the object reference to an object variable, it's stored in that variable so
that Visual Basic doesn't have to look it up again. If you need to refer to an object more than once, it's a good idea
to create an object variable.
To assign an object reference to an object variable, use the Set statement. The following line of code assigns a
reference to the Employees Form object to the object variable declared in the preceding example.
Set frm = Forms!Employees
There is a key difference between using the Set statement with an object variable and assigning a value to other
types of variables, such as variables of type String or Integer. Ordinary variables store a value. Even if two
variables store the same value, they are stored in different locations in memory. Object variables, however, refer
to actual physical objects in the database or in memory. An object variable stores a reference to an object, not the
actual object itself or a copy of the object. It is this reference to the object that is assigned to the variable when
you use the Set statement. You always work with the object reference in your code, never with the object itself.
In the preceding example, Forms!Employees returns a reference to the Employees Form object, and it is this
reference that is assigned to the variable frm. You can also say that the variable frm points to the Employees Form
object.
One advantage to this system of storing objects is that all variables assigned the same object reference refer to the
same object. Therefore, even if an object is changed in some way, all variables that refer to the object reflect the
change and represent the same information. You can also point the variable to a different object of the same type
by using the Set statement again; you don't necessarily have to create another variable. The variable simply stores
an object reference to the new object.
The Nothing Keyword
An object variable doesn't require much memory or system resources until you assign it to an object. Once it's
pointing to an object, it uses much more. With the Nothing keyword, you can free the memory that's being
consumed by an object variable. You use the Nothing keyword with the Set statement to disassociate an object

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

83

variable from the object to which it's been pointing once you are no longer using it. For example, if you are no
longer using an object variable that points to a Form object, you can free that variable as follows:
Set frm = Nothing

' Where frm is a Form object variable.

When you set an object variable to the Nothing keyword, you are no longer storing a reference to a particular
object. The variable still exists, and you can assign another object to it when you need it.
Using Objects and Collections in Code
Once you understand how to refer to objects in Visual Basic and how to create object variables to represent them,
you can begin using objects in code. The following sections present concepts that may be useful to you as you
begin working with objects and collections.
Navigating the Object Hierarchy
As explained earlier in this chapter, in order to work with an object that belongs to a collection, you must refer to
that object in its collection. Since objects are related to one another in an object hierarchy, you must also make
clear where the object and collection exist in the overall hierarchy. In other words, if the object is a member of a
collection, you must qualify the object with the name of its collection. If that collection belongs to another object,
you must qualify the collection with the name of that object, and so on.
When you create an object variable and assign an object to it, the information about that object's position within
the object hierarchy is stored with the variable. An object variable becomes a sort of shorthand for all the objects
preceding the one you want to work with in the object hierarchy.
The following example shows how you can work within the Microsoft Access object hierarchy to access
individual objects. The procedure returns a reference to the Employees Form object, which is a member of the
Forms collection, and assigns it to an object variable. Then it returns a reference to the LastName Control object,
which is a member of the Controls collection of the Form object, and assigns it to an object variable. Finally it
uses the ControlType property of the Control object to determine what type of control this is. If the control is a
text box, the procedure sets its Locked property to True.
Sub LockControl()
Dim frm As Form, ctl As Control
' Declare object variables.
Set frm = Forms!Employees
' Return reference to Form object.
Set ctl = frm!LastName
' Return reference to Control object.
If ctl.ControlType = acTextBox Then
' Check ControlType property.
ctl.Locked = True
' Lock control if it's a text box.
End If
Set frm = Nothing
End Sub
Although the Forms collection is a member of the Microsoft Access Application object, you don't need to refer to
the Application object when you refer to the Forms collection or to other Microsoft Access objects and
collections. The Application object is implicitly understood.
You work with objects and collections in the DAO object hierarchy in a similar manner. The next example
navigates through the DAO object hierarchy and prints the name of each field in the Employees table.
Sub ListTableFields()
' Declare object variables.
Dim dbs As Database, tdf As TableDef, fld As Field
' Return reference to current database.
Set dbs = CurrentDb
' Return reference to Employees table.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

84

Set tdf = dbs.TableDefs!Employees


' Print out all fields in the table.
For Each fld In tdf.Fields
Debug.Print fld.Name
Next fld
Set dbs = Nothing
End Sub
Enumerating the Objects in a Collection
The previous example shows another concept that's important when working with collections. In order to print out
all the fields in the table, the procedure must loop through, or enumerate, all the Field objects in the Fields
collection of the TableDef object. You accomplish this by using the For Each...Next statement. You can use the
For Each...Next statement to perform the same operation on each member of a collection.
To use the For Each...Next statement, you must first identify which objects you want to enumerate and in which
collection they reside. Next, you declare a variable of that type of object. The previous example declares the
variable fld as type Field. Within the For Each...Next statement, that variable refers to each object in the Fields
collection. By using this variable, you can perform a method or set or return a property on each object in the
collection, without knowing how many objects the collection contains.
Adding New DAO Objects to a Collection
As stated earlier in this chapter, some DAO objects represent the structure of the database, and others provide a
means for you to work with the data stored in the database. Objects that represent the structure of the database are
saved with the database. Objects that you use to work with the data in the database generally are not saved, but
are created each time you need them.
When you create a new DAO object to be saved with the database, you must append it to the appropriate
collection of saved objects. The following example creates a new TableDef object named ArchivedInvoices with a
new Field object named OrderID. It appends the new Field object to the Fields collection of the new TableDef
object, and it appends the TableDef object to the TableDefs collection of the Database object representing the
current database. After you run this code, the new table appears on the Tables tab of the Database window.
Sub AddTable()
' Declare object variables.
Dim dbs As Database, tdf As TableDef, fld As Field
' Assign the current database to the database variable.
Set dbs = CurrentDb
' Create new table and field, and assign to table and field variables.
Set tdf = dbs.CreateTableDef("ArchivedInvoices")
Set fld = tdf.CreateField("OrderID", dbLong)
' Add field to table's Fields collection.
tdf.Fields.Append fld
' Add table to database's TableDefs collection.
dbs.TableDefs.Append tdf
' Refresh TableDefs collection.
dbs.TableDefs.Refresh
Set dbs = Nothing
End Sub
Note The preceding example uses the CurrentDb function to return a reference to the current database, and
assigns this reference to an object variable of type Database. Anytime you're writing code to work with the
database that's currently open, you should use CurrentDb to return a reference to the current database.
The Properties Collection

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

85

DAO objects and Microsoft Access Form, Report, and Control objects all contain a Properties collection. Each
Property object in the Properties collection corresponds to a property of the object. You can use an object's
Properties collection either to determine which properties apply to a particular object or to return their settings.
For example, the following procedure loops through the properties that apply to the Database object, which
represents the current database, and to the Employees Form object. The procedure displays the name of each
property in the Debug window.
Sub DisplayProperties()
' Declare variables.
Dim dbs As Database, frm As Form, prp As Property
' Return reference to current database.
Set dbs = CurrentDb
Debug.Print "Current Database Properties"
' Enumerate Properties collection.
For Each prp In dbs.Properties
Debug.Print prp.Name
Next prp
' Print blank line.
Debug.Print
Debug.Print "Employees Form Properties"
' Open Employees form in Form view.
DoCmd.OpenForm "Employees", acWindowNormal
' Return reference to Employees form.
Set frm = Forms!Employees
' Enumerate Properties collection.
For Each prp In frm.Properties
Debug.Print prp.Name
Next prp
Set frm = Nothing
Set dbs = Nothing
End Sub
Note If you're looping through the Properties collection of a table or query, some properties aren't displayed
because they're added to the collection only when they have a value.
Working with CommandBar Objects
Creating new CommandBar objects is somewhat different from creating other new objects in Microsoft Access.
To create a new CommandBar object, you use the Add method of the CommandBars collection. The following
example creates a new CommandBar object and adds a button to it:
Sub CreateNewCommandBar()
Dim cmb As CommandBar, cbc As CommandBarControl
' Create new CommandBar object and return reference to it.
Set cmb = CommandBars.Add("NewCommandBar", msoBarFloating, msoBarTypeNormal)
' Create new CommandBarControl object and return reference to it.
Set cbc = cmb.Controls.Add(msoControlButton)
' Set properties of new command bar control.
With cbc
.Caption = "Button1"
.DescriptionText = "First button in NewCommandBar"
.TooltipText = "Button1"
.Visible = True
End With
' Make command bar visible.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

86

cmb.Visible = True
Set cmb = Nothing
End Sub
Note In order to use objects in the Microsoft Office 8.0 object library from Visual Basic, you must first set a
reference to the object library. When you set a reference to an object library, you notify Visual Basic that you may
want to use the objects in that library. To set a reference to the Microsoft Office 8.0 object library, open a module
and click References on the Tools menu. Then select the Microsoft Office 8.0 Object Library check box in the
Available References box.
Creating New Objects with Class Modules
Every object that you use in Microsoft Access is derived from a unique definition for that object. The definition
for an object includes its name, its inherent characteristics, and its properties, methods, and events. The definition
for an object is known as a class.
To simplify the concept of a class, you can think of a class as a cookie cutter, and an object as the cookie that it
makes. You can create multiple objects from a single class, just as you can make multiple cookies with a single
cookie cutter. Each individual object has the same characteristics, just as each cookie has the same shape and
pattern.
An individual object can also be referred to as an instance of a class. An instance of a class is like a single cookie
cut from the cookie cutter. When you create an instance of a class, you create a new object and return an object
reference to it. You then work with the instance by setting its properties and applying its methods.
In addition to the objects provided by Microsoft Access and its associated object libraries, you can define your
own custom objects in class modules. A class module is a module that can contain the definition for a new object.
To create a definition for a new object in a class module
1 Define the purpose for your new object. Think of the object in terms of the methods and properties it should
have. For example, calling functions in a dynamic-link library (DLL) is often tricky. You can create an object that
has methods that contain those function calls. Then, when you want to call a particular function, you can simply
call the method that contains it, rather than calling the complex function.
2 Create a new class module by clicking Class Module on the Insert menu. Choose a name for your class and
save the class module with that name.
3 Add procedures to the class module. Any Sub or Function procedures that you define in a class module
become custom methods of your new object. Any Property Get, Property Let, or Property Set procedures that you
define become custom properties of your new object.
4 If you want certain code to run when an instance of the class is created, add that code to the class's Initialize
event procedure. If you want certain code to run when an instance of the class is removed from memory, add it to
the class's Terminate event procedure.
5 Test the new class by creating an instance of it and applying its methods and setting its properties. To create an
instance of your class, use the New keyword to declare an object variable of type classname, where classname
represents the name of your class. The New keyword creates a new instance of the class.
For example, if your class is named NewClass, you can declare a new instance of it as shown in the following line
of code:
Dim obj As New NewClass
If you've defined a method called ListNames within the class module, you can then apply that method as follows:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

87

Obj.ListNames
You can view the new class and its variables, methods, and properties in the Object Browser, which is available
through the View menu. In the Project/Library box, click the name of your project, and then click the name of the
class in the Classes box. You can determine the name of your project by checking the value in the Project Name
box on the Advanced tab of the Options dialog box (Tools menu).
Creating Multiple Instances of Forms and Reports
Form modules and report modules are also class modules. They are identical to the class modules on the Modules
tab of the Database window, except that they are associated with forms and reports. Since form and report
modules are class modules, you can create one or more instances of a form or report class. This is useful if you
want to display more than one instance of a form or report at a time.
When you create a new instance of a form or report class, the new instance has all the properties and methods of a
Form or Report object, and its properties are set to the same values as those in the original Form or Report object.
Additionally, any procedures that you have written in the form or report class module behave as methods and
properties of the new instance.
To create a new instance of a form or report class, you declare a new object variable with the name of the form or
report's class module and the New keyword. The name of the class module appears in the title bar of the module.
It indicates whether the class is associated with a form or a report and includes the name of the form or report. For
example, the class name for an Orders form is Form_Orders. The following line of code creates a new instance of
the Orders form:
Dim frmInstance As New Form_Orders
By creating multiple instances of a Orders form class, you could show information about one order on one form
instance, and information about another order on another form instance.
When you create an instance of a form class by using the New keyword, it is hidden. To show the form, set the
Visible property to True.
You should declare the variable that represents the new instance of a form class at the module level. If you declare
the variable at the procedure level, the variable goes out of scope when the procedure finishes running, and the
new instance is removed from memory. The instance exists in memory only as long as the variable to which it is
assigned remains in scope.
Note When you create a new form or report in Microsoft Access, the form or report doesn't automatically have
an associated module. Forms and reports without associated modules load more quickly. If you're working in form
or report Design view, Microsoft Access automatically creates the form or report module when you click Code on
the View menu. Once you enter code in the module, Microsoft Access saves the module with the form or report.
Whether or not the form or report module exists is determined by the setting of the HasModule property. When a
form or report is created, the HasModule property is automatically set to False. When you create a form or report
module by clicking Code on the View menu, Microsoft Access sets the HasModule property to True. If you refer
to the Module property of a form or report, the HasModule property is also automatically set to True. For more
information on the HasModule property, search the Help index for "HasModule property."

Working with Properties and Methods


To describe an object's characteristics, you use its properties. You can set properties to change their values, or read
properties to get information about the object. To control how an object behaves, you use its methods. An object's
methods determine what operations you can perform on that object.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

88

Because a collection is also an object, each collection in Microsoft Access has its own properties and methods.
You can set or read the properties of a collection, or apply its methods, in the same manner that you would for any
object.
Setting and Reading Properties
Visual Basic provides a standard syntax for setting and reading properties in code. When you set a property, you
give it a new value. You can use the following syntax to set a property for any type of object:
object.property = setting
The following line of code sets the Caption property of the Employees form:
Forms!Employees.Caption = "Employees Form"
When you read the value of a property, you determine its current value. In order to read the property, you can
assign its value to a variable or to another property, or you can display it in the Debug window, in a dialog box, or
in a control on a form or report. The following example assigns the value of the Caption property to a variable and
then displays the value of that property in a dialog box.
Dim strCaption As String
strCaption = Forms!Employees.Caption
MsgBox strCaption
Properties That Return Objects
Sometimes you may want your code to refer to whatever object happens to be in a particular state at the time a
procedure is running, rather than to a specific object. Writing code in this way can make your application more
flexible. For instance, you may want to change the caption of the active form, without knowing the form's name.
Or you may want to hide the control that has just lost the focus.
Rather than determining an object's characteristics, some properties of an object represent another object that is
related in some way. These properties return an object reference that you can work with directly or assign to an
object variable, just as you would any object reference.
The Section Property
The Section property returns a reference to a section of a form or report. For example, you can use the Section
property to return a reference to the detail section of a form. Once you've returned a reference to a section, you
can set the section's properties. The following example uses the Section property to set a property of the detail
section on an Employees form.
Forms!Employees.Section(acDetail).Visible = False
The Me Property
The Me property returns an object reference to the Form or Report object in which the code is currently running.
You can use the Me property to refer to a Form or Report object from within that object, without needing to know
the name of the form or report. You can also use it to pass a Form or Report object to a procedure that takes an
argument of type Form or Report.
For example, the following code uses the Me property to return a reference to the Employees form, the form in
which the code is running. It then passes this reference to the ChangeDetailColor procedure which it calls when
the form's Current event occurs. It also uses the Me property to return references to the Employees form in order
to set a property and to return the values of the FirstName and LastName controls on the form. Note that the .
(dot) operator is used to set the property, and the ! operator is used to refer to the controls on the form.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

89

' Place this procedure in a standard module.


Sub ChangeDetailColor(frm As Form)
frm.Section(acDetail).BackColor = RGB(Rnd * 256, Rnd * 256, Rnd * 256)
End Sub
' Place this procedure in the form module associated with the Employees form.
Private Sub Form_Current()
ChangeDetailColor Me
Me.Caption = Me!FirstName.Value & " " & Me!LastName.Value
End Sub
In most cases, the form or report represented by the Me property is the same form or report represented by the
ActiveForm or ActiveReport property of the Screen object. However, the ActiveForm and ActiveReport properties
represent the active form or report, whereas the Me property represents the form or report in which the code is
running. For example, a Timer event can occur on a form called Customers, even if the Customers form isn't
active. In a Timer event procedure for the Customers form, Screen.ActiveForm represents the active form,
whatever it is, and Me always represents the Customers form.
Using Methods
Methods are built-in operations that you can perform on an object. There are two kinds of methods: those that
return a value or an object, as a function does, and those that perform a specific operation, as a statement does. To
apply a method to an object, you use the following syntax:
object.method [[(] arg1, arg2...[)]]
Many methods take one or more arguments. An argument provides the method with additional information for its
operation. If the method returns a value or an object, you must enclose its argument list in parentheses; otherwise
you should omit the parentheses.
The following example shows the syntax for several different methods. The OpenRecordset method creates a new
Recordset object and returns a reference to the new object. You can assign this object reference to an object
variable by using the Set statement. Because the OpenRecordset method returns a value, you must enclose its
arguments in parentheses. The FindFirst method, on the other hand, doesn't return a value. It simply sets the
current record pointer to the first record that matches the criteria given in the FindFirst argument. Since this
method doesn't return a value, you don't need to enclose its arguments in parentheses. The same is true for the
Print method of the Debug object. Finally, the Close method of the Recordset object doesn't take any arguments.
Sub FindEmployee()
Dim dbs As Database, rst As Recordset
Set dbs = CurrentDb
' Requires parentheses.
Set rst = dbs.OpenRecordset("Employees", dbOpenDynaset)
rst.FindFirst "[HireDate] >= #1-1-93#"
' No parentheses needed.
Debug.Print rst!LastName
' No parentheses needed.
rst.Close
' Doesn't take arguments.
Set dbs = Nothing
End Sub
Performing Multiple Actions on an Object
You'll often need to perform several different actions on the same object. For example, you may need to set
several properties for the same object within a single procedure. Instead of using many separate statements to do
this, you can use the With...End With statement. The following example uses the With...End With statement to set
several properties for a command button named HelpButton in a form's Load event.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

90

Private Sub Form_Load()


With Me!HelpButton
.Caption = "Help"
.Visible = True
.Top = 200
.Left = 5000
.Enabled = True
End With
End Sub
Using the Object Browser
The Object Browser is a tool that provides information about objects and their methods, properties, events, and
constants. The Object Browser displays all objects available to Microsoft Access, including Microsoft Access
objects, DAO objects, Visual Basic objects, and objects you've defined within your application.
The object information that you see in the Object Browser comes from an application's object library. Each
application that supplies objects to Microsoft Access has an object library that contains information about the
application's objects, methods, properties, events, and constants.
Note Some objects show up in the Object Browser automatically when you start Microsoft Access. Others, such
as the Microsoft Office objects, show up only after you have set a reference to the object library that contains
them.
To use the Object Browser
1 Open a module.
2 On the View menu, click Object Browser.
3 In the Project/Library box, click the object library whose objects you want to see, or click <All Libraries> to
view the objects of all referenced libraries together.
The Classes box lists all the objects in the object library you selected.
4 In the Classes box, click an object.
The Members Of box lists the methods, properties, events, and constants associated with the object you selected.
For example, if you click Access in the Project/Library box, all the objects in the Microsoft Access object library
are displayed in the Classes box. If you then click an object in the Classes box, you can view the object's members
the methods, properties, events, and constants associated with that object in the Members Of box. A class
definition includes all of an object's members. For example, if you click Control in the Classes box, you see the
methods and properties of that Control object displayed in the Members Of box.
From the Object Browser, you can get help on a particular object, method, property, or event. Just click the item
you're interested in and then click the Help button on the Object Browser's toolbar. You can also copy a particular
item to the Clipboard so that you can paste it into your code. Click the item you want to copy and click the Copy
button on the Object Browser's toolbar.
You can also view procedures that you've created yourself in Microsoft Access in the Object Browser. The
Project/Library box displays the name of the current database and any other referenced databases, in addition to
the other referenced object libraries. If you click a referenced database in the Project/Library box, the Classes box
displays all standard modules in the database and their public procedures. If you select the current database, it also
displays any class modules and their methods and properties.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

91

Responding to Events
Rather than running entire programs line by line, Microsoft Access applications run macros and event procedures
in response to specific events for particular objects, such as a change to data in a field, or a mouse click on a
command button. Understanding the events Microsoft Access recognizes can help you create powerful, flexible
applications. This chapter describes the Microsoft Access event model and shows you how to manage events in
your applications.
Working with Events
Microsoft Access applications are event-driven; this means that the way they behave depends on how objects are
designed to respond to events. Objects in Microsoft Access respond to the following types of events:
Mouse clicks
Changes in data
Keystrokes a user types
Objects receiving or losing the focus
Forms being opened, closed, or resized
Reports being printed or formatted
Run-time errors
The focus is the application's ability to receive input or respond to a user's mouse or keyboard actions. In
Microsoft Windows, only one item at a time can have the focus. For example, when a user types, characters
appear in a text box only if the text box has the focus. Which object or control receives the focus is determined by
a user's actions, such as clicking in a text box or pressing TAB to move to a control. Before a user acts, settings
made at design time determine which control has the focus. For example, when a user first opens or switches to a
form, the control that has the focus is the one with the lowest TabIndex property setting. You can also explicitly
set the focus in code by using the SetFocus method.
You can see the events that are generated in Microsoft Access by opening the ShowEvents form in the Orders
sample application. The ShowEvents form, a special version of the Orders form, records each event as it occurs.
An accompanying EventHistory form lists the name of the event and the type or name of the object on which the
event occurred, using the format object_event. For example, if a Click event occurs on the ShowEvents subform,
the line "[Subform]Form_Click" is added to the Events list on the EventHistory form.
The EventHistory form lists events in reverse order, with the most recent event at the top of the list. The form
lists all events except MouseMove events, which occur each time you move the mouse pointer, and would quickly
fill up the list if they were included.
By default, Microsoft Access automatically responds to events with built-in behaviors defined for each object. For
example, when a user enters or changes data in a text box, Microsoft Access automatically checks to make sure
the data is of the right type.
In addition, each object in Microsoft Access has a set of event properties that correspond to each event to which
the object can respond. For example, the following table lists some of the event properties and corresponding
events for a check box.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

92

You can specify a further response to an event by setting the object's corresponding event property. When an
event occurs that an object can respond to, Microsoft Access uses the setting of the object's corresponding event
property to determine how to respond:
If the event property is blank, Microsoft Access responds to the event only with its built-in behavior.
If the event property is set to the name of a macro, and the event can't be canceled, Microsoft Access first
performs the built-in behavior, and then runs the macro. If the event property is set to the name of a macro and the
event can be canceled, Microsoft Access first runs the macro, and then performs the built-in behavior.
If the event property is set to [Event Procedure], and the event can't be canceled, Microsoft Access first performs
the built-in behavior, and then calls the appropriate event procedure. If the event property is set to [Event
Procedure], and the event can be canceled, Microsoft Access first runs the macro, and then performs the built-in
behavior.
For example, when you click a command button whose OnClick event property is set to a macro, Microsoft
Access:
1. Makes the button appear pressed in momentarily the built-in behavior when a Click event occurs on a
command button. Note that the Click event can't be canceled.
2. Runs the macro.
When the event property is set to [Event Procedure], Microsoft Access responds to the event by running the
appropriate event procedure in addition to performing its built-in behavior. Event procedures are named for the
event and the object for which they occur, in the format object_event. For example, if a user clicks the Products
command button, Microsoft Access:
1. Makes the Products command button appear pressed in momentarily its built-in behavior.
2. Runs the Products_Click event procedure.

Note When you create an event procedure, Microsoft Access automatically sets the appropriate event property to
[Event Procedure] if the property doesn't already have a setting. As an alternative, you can set the property to
[Event Procedure], and then create the event procedure separately. For more information on creating an event
procedure, see Chapter 2, "Introducing Visual Basic," or search the Help index for "event procedures, creating."
You can also have Microsoft Access call a function in response to an event. To do so, add to the appropriate event
procedure an expression that calls the function, or type an equal sign (=) followed by the function name as the
event property setting in the property sheet.
For example, to call the CheckValues function when a form opens, you can type the following OnOpen property
setting in the form's property sheet:
=CheckValues()
Note Using an expression that calls a function as an event property setting for a form or control is useful when
you want to use code and the form's HasModule property is set to No so that it loads more quickly. For more
information, see "Optimizing Form Loading and Paging" in Chapter 13, "Optimizing Your Application."
The macros and Visual Basic code that Microsoft Access runs in response to events control how the objects in
your application work together. By managing events, and the macros and Visual Basic code that Microsoft Access
runs in response to events, you can create powerful, flexible database applications.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

93

Managing Events in Your Application


Most operations in Microsoft Access involve a sequence of events. For example, the process of opening a form
usually includes the following sequence:
1. When the form opens, but before the first record is displayed, the Open event occurs.
2. When the form opens and its records are displayed, the Load event occurs.
3. When the form becomes the active window, the Activate event occurs.
Other events also occur when the previously active window becomes inactive and the focus moves to an object in
the new active window. The full sequence of events in typical situations is explored later in this chapter.
Because each step in opening a form is a separate event, your application can run a macro or a procedure exactly
when you want it to. For example, your application can close another window or preset the focus in the new
active window before the first record is displayed. Or it can display a custom toolbar on the form when its
window becomes active.
You can also cancel many events. For example, you can prevent a form from opening if certain conditions are not
met by including code in the form's Open event procedure that cancels the Open event when an expression
evaluates to True.
AfterDelConfirm, AfterInsert, AfterUpdate, BeforeDelConfirm, BeforeInsert, BeforeUpdate, Change, Current,
Delete, NotInList, Updated
A user or code enters, deletes, or changes data in a form or control, or moves the focus from one record to another.
Click, DblClick, MouseDown, MouseMove, MouseUp

Microsoft Access or the Jet database engine encounters an error, or a specified time interval passes.
Working with Forms and Controls
Opening a form triggers a sequence of events, including the Open, Load, Resize, and Activate events. In addition,
if no control on the form can receive the focus, a GotFocus event occurs for the form itself. Other events occur as
you work with the form and its controls. You can write macros or Visual Basic code for any of these events, so
you have a fine degree of control over how your application behaves.
Because opening forms, moving between forms, and working with controls are some of the most common
operations in a Microsoft Access application, understanding the order of these events is one of the keys to
effective application development. This section describes the sequence of events for some common form and
control operations.

Opening and Closing a Form


When you first open a form that contains an active control one that can receive the focus the following sequence
of events occurs for the form:
If there are no active controls on the form, Microsoft Access also triggers a GotFocus event for the form, after the
Activate event, but before the Current event.
When you close a form that contains an active control, Microsoft Access triggers the following sequence of events
for the form:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

94

If there are no active controls on the form, Microsoft Access triggers a LostFocus event for the form after the
Unload event, but before the Deactivate event.
Entering and Exiting a Control
When you open a form that contains one or more active controls, an Enter event occurs, followed by a GotFocus
event, for the control receiving the focus. These events occur after the form's Activate and Current events:
Both events occur when a control first receives the focus. If you switch to a different form and then return to the
same control on the first form, Microsoft Access triggers a GotFocus event for the control, but not an Enter event.
When you exit a control for example, when you select another control on the same form the following events
occur for the control:
Switching Between Open Forms
When you switch between two open forms that contain active controls, Microsoft Access triggers a Deactivate
event on the first form and an Activate event on the second form:
Note An Open event doesn't occur on a form that is already open but not activated, whether you switch to the
form or run a macro that specifies the form in an OpenForm action. If you want your application to run the code
in a form's Open event procedure when the form is already open, you can:
Add the code to the form's Activate event procedure instead of the Open event procedure, if timing isn't critical.
The Activate event occurs both when you open a form and when you make it the active form, so the code is sure
to run.
Determine if the form is open by checking the value returned by the IsLoaded function in the UtilityFunctions
module of the Orders sample application. Do this before running the macro that contains the OpenForm action.
If there are no active controls on the forms, Microsoft Access also triggers the LostFocus and GotFocus events:
Switching Between Controls on Different Forms
This example shows the sequence of events that are triggered in a typical scenario while you work with forms and
controls.
Step One: Open a form Open the form Form1, whose first active control is Control1.
Step Two: Open a second form Open the form Form2, whose first active control is Control2.
There is no Exit(Control1) event, because the object that receives the focus is on a different form.
Step Three: Return to the first form Click on the first form.
Control1 now has the focus. There is no Enter(Control1) event because Control1 had the focus when Form1 was
last active.
Step Four: Click another control on the first form Click a different control, Control3, on the second form.
Step Five: Click another control on the second form Click a different control, Control4, on the second form.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

95

Responding to Keystrokes
When you press a key, Microsoft Access triggers the KeyDown, KeyPress, and KeyUp events for the form or
control that has the focus. For example, when a control has the focus, you'll normally want the control to receive
all keystrokes when changing data in a text box.
In some cases, however, you'll want to respond to specific keys pressed in a form, regardless of which control has
the focus. For example, you may want to perform some action whenever the user presses a key combination such
as CTRL+Y. You can make sure that the form receives all key events, even those that occur in controls, by setting
the KeyPreview property for the form to Yes. With this property setting, all key events occur first for the form,
and then for the control that has the focus.
You can respond to specific keys in the form's KeyPress, KeyDown, or KeyUp events. The KeyPress event
responds only to the ANSI characters generated by the keyboard. ANSI characters are generated by the following
keys and key combinations: any printable keyboard character, CTRL+A through CTRL+Z, ENTER,
CTRL+ENTER, BACKSPACE, CTRL+BACKSPACE, and TAB. The KeyPress event ignores all other
keystrokes. In most cases, it is simplest to use only the KeyPress event to respond to keyboard events.
The following sample code demonstrates how to respond to the CTRL+Y key combination in a form by using the
KeyPress event. Note that you can prevent the control from getting keystrokes you respond to by setting the
KeyAscii variable to zero.
Private Sub Form_KeyPress (KeyAscii As Integer)
Const conCtrlYCode = 25
' ANSI character code for CTRL+Y.
If KeyAscii = conCtrlYCode Then
MsgBox "You pressed Ctrl+Y"
KeyAscii = 0
' Do not send key on to control.
End If
End Sub
The KeyDown and KeyUp events work on a lower level by responding to events generated by the keys
themselves being pressed and released. Use KeyDown and KeyUp events if you need to respond to keys that don't
generate ANSI characters, such as the function keys (F1 through F12), or if you need to respond to key
combinations that include the SHIFT, ALT, and CTRL keys (except the CTRL key combinations that respond to
the KeyPress event).
Working with Data
You can use data events in your application to respond to many types of changes to records and data. For
example, the application can run a macro or an event procedure in response to:
Changes to text in a text box or combo box.
Updates to data in a control or record.
Insertions or deletions of records, either before or after the record is inserted or deleted, or before or after a
deletion is confirmed.
Note Some events do not occur when you use Visual Basic code to manipulate data in your application for
example, the Change event, the BeforeInsert event, and the AfterInsert event.
Changing Text in a Text Box or Combo Box
When you change text in a text box or combo box, a Change event occurs. The event occurs whenever the
contents of a control changes, but before you move to a different control or record. For example, when you delete

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

96

a character in a text box by pressing the DELETE key, Microsoft Access triggers the following sequence of
events:
If you then type one or more characters in the text box, Microsoft Access recognizes the same sequence of events
for each keystroke.
The Change event doesn't occur when a value changes in a calculated control, or when you select an item from a
combo box list.

Using Data
Working with Records and Fields
The Microsoft Jet database engine supports a rich set of Data Access Objects (DAO) features for organizing,
sorting, searching, updating, adding, and deleting data. The Recordset object alone provides 24 methods and 26
properties that give you a great deal of control over records in a database. With the Recordset object's Fields
collection and the properties and methods of the Field object, you can manipulate data at the field level. This
chapter describes how to manipulate records and fields by using the DAO Recordset and Field objects.
Using Recordset Objects
A Recordset object represents the records in a base table or the records that result from running a query. You use
Recordset objects to manipulate the data in a database at the record level.
Note You use Field objects to manipulate the data in a database at the field level. For more information, see
"Using Field Objects" later in this chapter.
The four types of Recordset objects table, dynaset, snapshot, and forward-only differ from each other in
significant ways:
A table-type Recordset object can be created from a table in a Microsoft Access database, but not from an Open
Database Connectivity (ODBC) or a linked table. When you create a table-type Recordset, the Jet database engine
opens the actual table, and your subsequent data manipulations operate directly on base-table data. A table-type
Recordset can be opened on only one table; it cannot be opened on a union query or a select query with a join.
One of the biggest advantages of this type of Recordset object is that you can index it by using an index created
for the underlying table. This allows much faster sorting and searching than is possible with the other types. To
locate specific records, use the Seek method, which is faster than the Find methods.
A dynaset-type Recordset object can be created from either a local or a linked table, or with a row-returning
query that joins tables. It's actually a set of references to records in one or more tables. With a dynaset, you can
extract and update data from more than one table, including linked tables from other databases. Heterogeneous
updatable joins are a unique feature of dynasets they enable you to use updatable queries against tables in
different types of databases.
One of the main benefits of this type is that a dynaset and its underlying tables update each other. Changes made
to records in the dynaset are also made in the underlying table, and changes made by other users to data in the
underlying tables while the dynaset is open are reflected in the dynaset. The dynaset is the most flexible and
powerful type of Recordset object, although searches and other manipulations may not run as fast as with a tabletype Recordset.
A snapshot-type Recordset object is a static copy of a set of records as it exists at the time the snapshot is
created. A snapshot-type Recordset object can contain fields from one or more tables in a database. You can't
update a snapshot.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

97

The main advantage of a snapshot is that it creates less processing overhead than the other types, so it can run
queries and return data faster, especially when working with ODBC data sources.
Note For .mdb files, OLE Object and Memo fields are represented in a snapshot by pointers, rather than the
actual data. For more information on OLE Object and Memo fields, see "The OLE Object and Memo Data Types"
later in this chapter.
A forward-only-type Recordset object, sometimes referred to as a forward-scrolling snapshot or a forward-only
snapshot, provides a subset of the capabilities of a snapshot. With forward-only snapshots, you can move only in a
forward direction through the records. Recordset objects of this type cannot be cloned and only support the Move
and MoveNext methods. Like snapshots, you can't update a forward-only-type Recordset object.
The advantage of a forward-only-type Recordset object is that it usually provides the greatest speed. It does,
however, offer the least functionality of any Recordset.
Note A snapshot stores a copy of the entire record (except for OLE Object and Memo fields). A dynaset stores
just the primary key for each record, copying the full record only when it's needed for editing or display purposes.
Since a snapshot stores a complete copy of all the records in a table, a snapshot may perform more slowly than a
dynaset if the number of records is large. To determine whether a snapshot or dynaset is faster, you can open the
Recordset as a dynaset and then open it as a snapshot to see which provides faster performance.
The type of Recordset object you use depends on what you want to do and whether you want to change or simply
view the data. For example, if you must sort the data or work with indexes, use a table. Because table-type
Recordset objects are indexed, they also provide the fastest way to locate data. If you want to be able to update a
set of records selected by a query, use a dynaset. If the table-type is unavailable and you only need to scan
through a set of records, using a forward-only snapshot may improve performance.
All other things being equal, if a table-type Recordset object is available, using it almost always results in the best
performance.
Note In this chapter, the terms table, snapshot, and dynaset are often used for the sake of simplicity. However,
keep in mind that these are all types of Recordset objects. For example, the term dynaset refers to a dynaset-type
Recordset object, not the obsolete DAO Dynaset object.

Creating a Recordset Object Variable


To create a Recordset object variable, use the OpenRecordset method. First, declare a variable of type Recordset,
and then set the variable to the object returned by the OpenRecordset method.
You can use the OpenRecordset method with Database, TableDef, QueryDef, and existing Recordset objects. The
syntax of the OpenRecordset method for Database object is:
Set variable = database.OpenRecordset (source [, type [, options [, lockedits ]]])
The syntax of the OpenRecordset method for all other types of objects is:
Set variable = object.OpenRecordset ([type [, options [, lockedits ]]])
The variable argument is the name of the new Recordset object. The database argument is the name of the open
Database object from which you're creating the new Recordset object. The object argument is the TableDef,
QueryDef, or existing Recordset object from which you're creating the new Recordset object.
The source argument specifies the source of the records for the new Recordset object. The value of source is the
value of the resulting Recordset object's DAO Name property. When you create a new Recordset object from a
Database object, the source argument is a TableDef or QueryDef object in the database or a valid row-returning

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

98

SQL query or statement. When you create a new Recordset object from a TableDef, QueryDef, or existing
Recordset object, the object itself provides the data source for the new Recordset.
The type argument is an intrinsic constant that specifies the kind of Recordset object that you want to create. You
can use the following constants:
dbOpenTable
dbOpenDynaset
dbOpenSnapshot
dbOpenForwardOnly
Note The dbOpenForwardOnly type constant replaces the dbForwardOnly type constant that was available in
previous versions of DAO. You can still use the dbForwardOnly constant, but it's provided only for backward
compatibility.
The following sections discuss the type, options, and lockedits arguments in detail.
Default Recordset Types
Because DAO automatically chooses the default Recordset type depending on the data source and how the
Recordset is opened, you don't need to specify a Recordset type. However, you can specify a type different from
the default by using a type argument in the OpenRecordset method.
The following list describes the available types and the default type, depending on how you open the Recordset
object:
Using the OpenRecordset method with a Database object:
Set rstNew = dbs.OpenRecordset("Data Source")
If Data Source is a table local to the database, all four types are available, and the table-type Recordset object is
the default. If Data Source is anything else, only dynaset- and snapshot-type Recordset objects are available, and
the dynaset type is the default.
Using the OpenRecordset method with a TableDef object:
Set rstNew = tdfTableData.OpenRecordset
If tdfTableData refers to a table in a Microsoft Access database (.mdb) or to an installable ISAM database opened
directly, then all four types are available and the table-type Recordset object is the default type. If tdfTableData is
in an ODBC database or is a linked table in an external database, only dynaset- and snapshot-type Recordset
objects are available, and the dynaset type is the default.
Using the OpenRecordset method with a QueryDef object:
Set rstNew = qdfQueryData.OpenRecordset
Only dynaset- and snapshot-type Recordset objects are available, and the dynaset type is the default.
Using the OpenRecordset method with an existing Recordset object:
Set rstNew = rstExisting.OpenRecordset

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

99

Only dynaset- and snapshot-type Recordset objects are available. The default is the type of the existing Recordset,
in this case, the type of rstExisting.
OpenRecordset Options
With the options argument of the OpenRecordset method, you can specify a number of other features for a
Recordset object. You can use the following constants:
dbAppendOnly Users can append new records to the Recordset, but they cannot edit or delete existing records.
This is useful in applications that collect and archive data (dynaset only).
dbReadOnly No changes can be made to the Recordset. This argument is provided only for backward
compatibility. Use the dbReadOnly constant in the lockedits argument instead.
dbSeeChanges If another user changes data in a record on which this Recordset has invoked the Edit method,
but before it has invoked the Update method, a run-time error occurs. This is useful in applications where multiple
users have simultaneous read/write permission on the same data (dynaset and table only).
dbDenyWrite When used on a dynaset or snapshot, this option prevents other users from adding or modifying
records, although they can still read data. When used on a table, no other user can open any type of Recordset
from an underlying table.
dbDenyRead Other users cannot read data in the table (table only).
dbForwardOnly This option creates a forward-only snapshot. It is provided only for backward compatibility.
Use the dbOpenForwardOnly constant in the type argument instead.
dbSQLPassThrough When the source argument is an SQL statement, use this constant to pass the SQL
statement to an ODBC database for processing. If used with a dynaset, data isn't updatable (dynaset and snapshot
only).
dbConsistent (Default) Only consistent updates are allowed (dynaset only). You can't use this constant with the
dbInconsistent constant.
dbInconsistent Inconsistent updates are allowed. This is the opposite of dbConsistent (dynaset only). You can't
use this constant with the dbConsistent constant.
With the lockedits argument of the OpenRecordset method, you can control how locking is handled for a
Recordset object. You can use the following constants:
dbReadOnly No changes can be made to the Recordset. This constant replaces the dbReadOnly constant that
was used in the options argument in previous versions of DAO.
dbPessimistic (Default) Microsoft Jet uses pessimistic locking to determine how changes are made to the
Recordset in a multiuser environment.
dbOptimistic Microsoft Jet uses optimistic locking to determine how changes are made to the Recordset in a
multiuser environment.
The default value is dbPessimistic. The only effect of using dbPessimistic or dbOptimistic is to preset the value of
the Recordset object's LockEdits property.
Important Setting both the lockedits argument and the options argument to dbReadOnly generates a run-time
error.
Creating a Recordset Object from a Form

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

100

You can create a Recordset object based on a Microsoft Access form. To do so, use the RecordsetClone property
of the form. This creates a dynaset-type Recordset that refers to the same underlying query or data as the form. If
a form is based on a query, referring to the RecordsetClone property is the equivalent of creating a dynaset with
the same query. You can use the RecordsetClone property when you want to apply a method that can't be used
with forms, such as the FindFirst method. The RecordsetClone property provides access to all the methods and
properties that you can use with a dynaset. The syntax for the RecordsetClone property is:
Set variable = form.RecordsetClone
The variable argument is the name of an existing Recordset object. The form argument is the name of a Microsoft
Access form. The following example shows how to assign a Recordset object to the records in the Orders form:
Dim rstOrders As Recordset
Set rstOrders = Forms!Orders.RecordsetClone
This code always creates the type of Recordset being cloned (the type of Recordset on which the form is based);
no other types are available.
Creating a Recordset Object from a Table
The method you use to create a Recordset object from a table depends on whether the table is local to the current
database or is a linked table in another database. The following discussion explains the differences and provides
examples for each type of table.
Creating a Recordset from a Table in a Local Microsoft Access Database
The following example uses the OpenRecordset method to create a table-type Recordset object for a table in the
current database:
Dim dbs As Database, rstCustomers As Recordset
Set dbs = CurrentDb
Set rstCustomers = dbs.OpenRecordset("Customers")
Notice that you don't need to use the dbOpenTable constant to create a table-type Recordset. If you omit the type
constant, as discussed in "Default Recordset Types" earlier in this chapter, DAO chooses the highest-functionality
Recordset type available, depending on the object in which the Recordset is created, and the data source. Because
the table type is available when you open a Recordset from a local table, DAO uses it.
Creating a Recordset from a Linked Table in a Different Database Format
The following example creates a dynaset-type Recordset object for a linked Paradox version 3.x table. Because
the table type isn't available when you open a Recordset from a linked table in a database other than a Microsoft
Access database, DAO selects the next most efficient type, opening a dynaset-type Recordset.
Dim dbs As Database
Dim tdf As TableDef
Dim rstTableData As Recordset
' Get current database.
Set dbs = CurrentDb
Set tdf = dbs.CreateTableDef("PDXAuthor")
' Connect to the Paradox table Author in the database
' C:\PDX\Publish.
tdf.Connect = "Paradox 3.X;DATABASE=C:\PDX\Publish"
tdf.SourceTableName = "Author"
' Link the table.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

101

dbs.TableDefs.Append tdf
' Create a dynaset-type Recordset for the table.
Set rstTableData = tdf.OpenRecordset()
You can also open a Paradox table directly by first opening the Paradox database.
Using an Index on a Table-Type Recordset Object
You can order records in a table-type Recordset object by setting its Index property. Any Index object in the
Indexes collection of the Recordset object's underlying table definition can be specified with the Index property.
The following example creates a table-type Recordset object based on the Customers table, by using an existing
index called City:
Dim dbs As Database, rstTableData As Recordset
Set dbs = CurrentDb
Set rstTableData = dbs.OpenRecordset("Customers", dbOpenTable)
' Move to the first record.
rstTableData.MoveFirst
' First record with no index set.
MsgBox rstTableData!CompanyName
rstTableData.Index = "City"
' Select the City index.
rstTableData.MoveFirst
' Move to the first record.
MsgBox rstTableData!CompanyName
rstTableData.Close
If you set the Index property to an index that doesn't exist, a trappable run-time error occurs. If you want to sort
records according to an index that doesn't exist, either create the index first, or create a dynaset- or snapshot-type
Recordset by using a query that returns records in a specified order.
Important You must set the Index property before using the Seek method. For information on using the Seek
method to locate records that satisfy criteria that you specify, see "Finding a Record in a Table-Type Recordset
Object" later in this chapter.
Creating a Recordset Object from a Query
You can also create a Recordset object based on a stored select query. In the following example, Current Product
List is an existing select query stored in the current database:
Dim dbs As Database, rstProducts As Recordset
Set dbs = CurrentDb
Set rstProducts = dbs.OpenRecordset("Current Product List")
If a stored select query doesn't already exist, the OpenRecordset method also accepts an SQL string instead of the
name of a query. The previous example can be rewritten as follows:
Dim dbs As Database, rstProducts As Recordset
Dim strQuerySQL As String
Set dbs = CurrentDb
strQuerySQL = "SELECT * FROM Products WHERE Discontinued = No " _
& "ORDER BY ProductName;"
Set rstProducts = dbs.OpenRecordset(strQuerySQL)
The disadvantage of this approach is that the query string must be compiled each time it's run, whereas the stored
query is compiled the first time it's saved, which usually results in slightly better performance.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

102

Note When you create a Recordset object by using an SQL string or a stored query, your code doesn't continue
running until the query returns the first row in the Recordset.

Sorting and Filtering Records


Unless you open a table-type Recordset object and set its Index property, you can't be sure records will appear in
any specific order. However, you usually want to retrieve records in a specific order. For example, you may want
to view invoices arranged by increasing invoice number, or retrieve employee records in alphabetic order by their
last names. To see records in a specific order, sort them.
To sort data in a Recordset object that isn't a table, use an SQL ORDER BY clause in the query that constructs the
Recordset. You can specify an SQL string when you create a QueryDef object, when you create a stored query in
a database, or when you use the OpenRecordset method.
You can also filter data, which means you restrict the result set returned by a query to records that meet some
criteria. With any type of Recordset object, use an SQL WHERE clause in the original query to filter data.
The following example opens a dynaset-type Recordset object, and uses an SQL statement to retrieve, filter, and
sort records:
Dim dbs As Database, rstManagers As Recordset
Set dbs = CurrentDb
Set rstManagers = dbs.OpenRecordset("SELECT FirstName, LastName FROM " _
& "Employees WHERE Title = 'Sales Manager' ORDER BY LastName")
One drawback of running an SQL query in an OpenRecordset method is that it has to be recompiled every time
you run it. If this query is used frequently, you can improve performance by first creating a stored query using the
same SQL statement, and then opening a Recordset object against the query, as shown in the following example:
Dim dbs As Database
Dim rstSalesReps As Recordset
Dim qdf As QueryDef
Set dbs = CurrentDb
Set qdf = dbs.CreateQueryDef("SalesRepQuery")
qdf.SQL = "SELECT * FROM Employees WHERE Title = 'Sales Representative';"
Set rstSalesReps = dbs.OpenRecordset("SalesRepQuery")
Note For even greater flexibility and control at run time, you can use query parameters to determine the sort
order and filter criteria. For more information, see "Using Parameter Queries" later in this chapter.
Recreating a Query from a Recordset Object
You can also use a Recordset object opened from a QueryDef object as a template to re-create the QueryDef
object. To do this, use the CopyQueryDef method. This is useful in situations where a Recordset object variable
created from a QueryDef object is passed to a function, and the function must re-create the SQL equivalent of the
query and possibly modify it.
Modifying a Query from a Recordset Object
You can use the Requery method on a dynaset- or snapshot-type Recordset object when you want to run the
underlying query again after changing a parameter. This is more convenient than opening a new Recordset, and it
runs faster.
The following example creates a Recordset object and passes it to a function that uses the CopyQueryDef method
to extract the equivalent SQL string. It then prompts the user to add an additional constraint clause to the query.
The code uses the Requery method to run the modified query.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

103

Sub AddQuery()
Dim dbs As Database
Dim qdf As QueryDef
Dim rstSalesReps As Recordset
Set dbs = CurrentDb
Set qdf = dbs.CreateQueryDef("SalesRepQuery")
qdf.SQL = "SELECT * FROM Employees WHERE Title = 'Sales Representative'"
Set rstSalesReps = qdf.OpenRecordset()
' Call the function to add a constraint.
AddQueryFilter rstSalesReps
' Return database to original.
dbs.QueryDefs.Delete "SalesRepQuery"
rstSalesReps.Close
End Sub
Function AddQueryFilter(rst As Recordset)
Dim qdf As QueryDef
Dim strNewFilter As String, strRightSQL As String
Set qdf = rst.CopyQueryDef
' Try "LastName LIKE 'D*'".
strNewFilter = InputBox("Enter new criteria")
strRightSQL = Right(qdf.SQL, 1)
' Strip characters from the end of the query,
' as needed.
Do While strRightSQL = " " Or strRightSQL = ";" Or strRightSQL = vbCR Or _
strRightSQL = vbLF
qdf.SQL = Left(qdf.SQL, Len(qdf.SQL) - 1)
strRightSQL = Right(qdf.SQL, 1)
Loop
qdf.SQL = qdf.SQL & " AND " & strNewFilter & ";"
rst.Requery qdf
' Requery the Recordset.
rst.MoveLast
' Populate the Recordset.
' "Lastname LIKE 'D*'" should return 2 records.
MsgBox "Number returned = " & rst.RecordCount
End Function
Note To use the Requery method, the Restartable property of the Recordset object must be set to True. The
Restartable property is always set to True when the Recordset is created from a query other than a crosstab query
against tables in a Microsoft Access database. You can't restart SQL pass-through queries. You may or may not be
able to restart queries against linked tables in another database format. To determine whether a Recordset object
can rerun its query, check the Restartable property. For more information on the Restartable property, search the
Help index for "Restartable property."
The DAO Sort and Filter Properties
Another approach to sorting and filtering Recordset objects is to set the DAO Sort and Filter properties on an
existing Recordset, and then open a new Recordset from the existing one. However, this is usually much slower
than just including the sort and filter criteria in the original query or changing the query parameters and running it
again with the Requery method. The DAO Sort and Filter properties are useful when you want to allow a user to
sort or restrict a result set, but the original data source is unavailable for a new query for example, when a
Recordset object variable is passed to a function, and the function must reorder records or restrict the records in
the set. With this approach, performance is likely to be slow if the Recordset has more than 100 records. Using the
CopyQueryDef method described in the previous section is preferable.
Moving Through a Recordset Object

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

104

A Recordset object usually has a current position, most often at a record. When you refer to the fields in a
Recordset, you obtain values from the record at the current position, which is known as the current record.
However, the current position can also be immediately before the first record in a Recordset or immediately after
the last record. In certain circumstances, the current position is undefined.
You can use the following Move methods to loop through the records in a Recordset:
The MoveFirst method moves to the first record.
The MoveLast method moves to the last record.
The MoveNext method moves to the next record.
The MovePrevious method moves to the previous record.
The Move [n] method moves forward or backward the number of records you specify in its syntax.
You can use each of these methods on table-, dynaset-, and snapshot-type Recordset objects. On a forward-onlytype Recordset object, you can use only the MoveNext and Move methods. If you use the Move method on a
forward-only-type Recordset, the argument specifying the number of rows to move must be a positive integer.
The following example opens a Recordset object on the Employees table containing all of the records that have a
Null value in the ReportsTo field. The function then updates the records to indicate that these employees are
temporary employees. For each record in the Recordset, the example changes the Title and Notes fields, and saves
the changes with the Update method. It uses the MoveNext method to move to the next record.
Function UpdateEmployees()
Dim dbs As Database, rstEmployees As Recordset, strQuery As String
Dim intI As Integer
On Error GoTo ErrorHandler
Set dbs = CurrentDb
' Open a recordset on all records from the Employees table that have
' a Null value in the ReportsTo field.
strQuery = "SELECT * FROM Employees WHERE ReportsTo IS NULL;"
Set rstEmployees = dbs.OpenRecordset(strQuery, dbOpenDynaset)
' If the recordset is empty, exit.
If rstEmployees.EOF Then Exit Function
intI = 1
With rstEmployees
Do Until .EOF
.Edit
![ReportsTo] = 5
![Title] = "Temporary"
![Notes] = rstEmployees![Notes] & "Temp #" & intI
.Update
.MoveNext
intI = intI + 1
Loop
.Close
End With
ErrorHandler:
Select Case Err
Case 0
Exit Function
Case Else

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

105

MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR"
Exit Function
End Select
End Function
Note The previous example is provided only for the purposes of illustrating the Update and MoveNext methods.
It would be much faster to perform this bulk operation with an SQL UPDATE query.
Detecting the Limits of a Recordset Object
In a Recordset object, if you try to move too far in one direction, a run-time error occurs. For example, if you try
to use the MoveNext method when you're already beyond the end of the Recordset, a trappable error occurs. For
this reason, it's helpful to know the limits of the Recordset object.
The BOF property indicates whether the current position is at the beginning of the Recordset. If BOF is True, the
current position is before the first record in the Recordset. The BOF property is also True if there are no records in
the Recordset when it's opened. Similarly, the EOF property is True if the current position is after the last record
in the Recordset, or if there are no records.
The following example shows you how to use the BOF and EOF properties to detect the beginning and end of a
Recordset object. This code fragment creates a table-type Recordset based on the Orders table from the current
database. It moves through the records, first from the beginning of the Recordset to the end, and then from the end
of the Recordset to the beginning.
Dim dbs As Database, rstOrders As Recordset
Set dbs = CurrentDb
Set rstOrders = dbs.OpenRecordset("Orders", dbOpenTable)
Do Until rstOrders.EOF
.
. ' Manipulate data.
.
rstOrders.MoveNext
' Move to the next record.
Loop
rstOrders.MoveLast
' Move to the last record.
' Do Until beginning of file.
Do Until rstOrders.BOF
.
. ' Manipulate data.
.
' Move to the previous record.
rstOrders.MovePrevious
Loop
rstOrders.Close
' Close the Recordset.
Notice that there's no current record immediately following the first loop. The BOF and EOF properties both have
the following characteristics:
If the Recordset contains no records when you open it, both BOF and EOF are True.
When BOF or EOF is True, the property remains True until you move to an existing record, at which time the
value of BOF or EOF becomes False.
When BOF or EOF is False, and the only record in a Recordset is deleted, the property remains False until you
try to move to another record, at which time both BOF and EOF become True.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

106

At the moment you create or open a Recordset that contains at least one record, the first record is the current
record, and both BOF and EOF are False.
If the first record is the current record when you use the MovePrevious method, BOF is set to True. If you use
MovePrevious while BOF is True, a run-time error occurs. When this happens, BOF remains True and there is no
current record.
Similarly, moving past the last record in the Recordset changes the value of the EOF property to True. If you use
the MoveNext method while EOF is True, a run-time error occurs. When this happens, EOF remains True and
there is no current record.
The following illustration shows the settings of the BOF and EOF properties for all possible current positions in a
Recordset.
Counting the Number of Records in a Recordset Object
You may want to know the number of records in a Recordset object. For example, you may want to create a form
that shows how many records are in each of the tables in a database. Or you may want to change the appearance
of a form or report based on the number of records it includes.
The RecordCount property contains the number of records in a table-type Recordset or the total number of
records accessed in a dynaset- or snapshot-type Recordset. A Recordset object with no records has a RecordCount
property value of 0.
Note The value of the RecordCount property equals the number of records that have actually been accessed. For
example, when you first create a dynaset or snapshot, you have accessed (or visited) only one record. If you check
the RecordCount property immediately after creating the dynaset or snapshot (assuming it has at least one record),
the value is 1. To visit all the records, use the MoveLast method immediately after opening the Recordset, then
use MoveFirst to return to the first record. This isn't done automatically because it may be slow, especially for
large result sets.
When you open a table-type Recordset object, you effectively visit all of the records in the underlying table, and
the value of the RecordCount property totals the number of records in the table as soon as the Recordset is
opened. Canceled transactions may make the value of the RecordCount property out-of-date in some multiuser
situations. Compacting the database restores the table's record count to the correct value.
The following example creates a snapshot-type Recordset object, and then determines the number of records in
the Recordset:
Function RecCount(strSQL As String) As Long
Dim rstCount As Recordset
Dim dbs As Database
On Error GoTo ErrorHandler
Set dbs = CurrentDb
Set rstCount = dbs.OpenRecordset(strSQL)
If rstCount.EOF Then
rstCount.Close
RecCount = 0
Exit Function
Else
rstCount.MoveLast
RecCount = rstCount.RecordCount
rstCount.Close
Exit Function
End If

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

107

ErrorHandler:
Select Case Err
Case 0
Exit Function
Case Else
MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR"
Exit Function
End Select
End Function
As your application deletes records in a dynaset-type Recordset, the value of the RecordCount property decreases.
However, in a multiuser environment, records deleted by other users aren't reflected in the value of the
RecordCount property until the current record is positioned on a deleted record. At that time, the setting of the
RecordCount property decreases by one. Using the Requery method on a Recordset, followed by the MoveLast
method, sets the RecordCount property to the current total number of records in the Recordset.
A snapshot-type Recordset object is static and the value of its RecordCount property doesn't change when you add
or delete records in the snapshot's underlying table.
Finding the Current Position in a Recordset Object
In some situations, you need to determine how far through a Recordset object you have moved the current record
position, and perhaps indicate the current record position to a user. For example, you may want to indicate the
current position on a dial, meter, or similar type of control. Two properties are available to indicate the current
position: the AbsolutePosition property and the PercentPosition property.
The AbsolutePosition property value is the position of the current record relative to 0. However, don't think of this
property as a record number; if the current record is undefined, the AbsolutePosition property returns - 1. In
addition, there is no assurance that a record will have the same absolute position if the Recordset object is recreated because the order of individual records within a Recordset object isn't guaranteed unless it's created with
an SQL statement that includes an ORDER BY clause.
The PercentPosition property shows the current position expressed as a percentage of the total number of records
indicated by the RecordCount property. Because the RecordCount property doesn't reflect the total number of
records in the Recordset object until the Recordset has been fully populated, the PercentPosition property only
reflects the current record position as a percentage of the number of records that have been accessed since the
Recordset was opened. To make sure that the PercentPosition property reflects the current record position relative
to the entire Recordset, use the MoveLast and MoveFirst methods immediately after opening the Recordset. This
fully populates the Recordset object before you use the PercentPosition property. If you have a large result set,
using the MoveLast method may take a long time for Recordsets that aren't of type table.
Important The PercentPosition property is only an approximation and shouldn't be used as a critical parameter.
This property is best suited for driving an indicator that marks a user's progress while moving though a set of
records. For example, you may want a control that indicates the percent of records completed. For more
information on the PercentPosition property, search the Help index for "PercentPosition property."
The following example opens a Recordset object on a table called Employees. The procedure then moves through
the Employees table and uses the SysCmd function to display a progress bar showing the percentage of the table
that's been processed. If the hire date of the employee is before Jan. 1, 1993, the text "Senior Staff" is appended to
the Notes field.
Function PercentPos()
Dim dbs As Database, strMsg As String, rstEmployees As Recordset, intRet As Integer
Dim intCount As Integer, strQuery As String, sngPercent As Single
Dim varReturn As Variant
Dim lngEmpID() As Long

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

108

On Error GoTo ErrorHandler


strQuery = "SELECT * FROM Employees;"
Set dbs = CurrentDb
Set rstEmployees = dbs.OpenRecordset(strQuery, dbOpenDynaset)
With rstEmployees
If .EOF Then
' If no records, exit.
Exit Function
Else
strMsg = "Processing Employees table..."
intRet = SysCmd(acSysCmdInitMeter, strMsg, 100)
End If
Do Until .EOF
If !HireDate < #1/1/93# Then
.Edit
!Notes = !Notes & ";" & "Senior Staff"
.Update
End If
If .PercentPosition <> 0 Then
intRet = SysCmd(acSysCmdUpdateMeter, .PercentPosition)
End If
.MoveNext
Loop
.Close
End With
intRet = SysCmd(acSysCmdRemoveMeter)
ErrorHandler:
Select Case Err
Case 0
Exit Function
Case Else
MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR"
' Clear progress meter.
varReturn = SysCmd(acSysCmdSetStatus, " ")
Exit Function
End Select
End Function

Updating Data in a Control or Record


When you update data in a control by moving to a different control on the form, Microsoft Access triggers the
BeforeUpdate and AfterUpdate events for the control. The BeforeUpdate event occurs just before the data is
updated; the AfterUpdate event occurs after the update.
For example, if you update data in a text box (TB1) by deleting a character, then click a different text box (TB2),
Microsoft Access triggers the following sequence of events:
If you update a control or record by moving to a different record or by clicking Save Record on the Records
menu, the BeforeUpdate and AfterUpdate events for both the control and the form occur. For example, if you
delete a character in a text box and then click Save Record on the Records menu, the following sequence of
events occurs:
When you update a control or record by moving to a different record, Microsoft Access triggers several events
after the BeforeUpdate and AfterUpdate events for the control and form: It triggers Exit and LostFocus events for
the control losing the focus, the Current event for the new record, and Enter and GotFocus events for the control
receiving the focus.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

109

Inserting Records
When you enter data in a new record by way of the user interface, Microsoft Access triggers a BeforeInsert event
when you first enter data in the record, and an AfterInsert event when the record is saved.
Entering Data in a New Record
This example shows the sequence of events that Microsoft Access triggers in a typical scenario when you enter
data in a new record.
Step One: Enter text in the first field of a new record After clicking Data Entry on the Records menu of a form to
display a blank record, type a character in a text box (TB1).
Step Two: Move to another field of the same record and enter text Click another text box (TB2) on the form and
type a character.
Step Three: Save the new record Click Save Record on the Records menu.

Deleting Records
When you select a record and delete it (either by pressing the DELETE key or by clicking Delete on the Edit
menu), Microsoft Access triggers the Delete event, and then the Current event. If you select multiple records and
delete them, the Delete event occurs once for each record that you have selected, and then Microsoft Access
triggers the Current event. Unless you cancel the Delete event, Microsoft Access also triggers BeforeDelConfirm
and AfterDelConfirm events. You use these events to control how record deletions are confirmed.
For example, when you select a record on a form and delete it, Microsoft Access by default:
Finding a Specific Record
The previous section, "Moving Through a Recordset Object," explores ways you can use the Move methods
MoveFirst, MoveLast, MoveNext, MovePrevious, and Move to loop through records in a Recordset object.
However, in most cases it's more efficient to search for a specific record.
For example, you may want to find a particular employee based on an employee number, or you may want to find
all of the detail records that belong to a specific order. In these cases, looping through all of the employee or order
detail records could be time consuming. Instead, you can use the Seek method with table-type Recordset objects,
and the Find methods with dynaset- and snapshot-type Recordset objects to locate records. Since the forwardonly-type Recordset object doesn't support the Seek method or any of the Find methods, you cannot search for
records in a forward-only-type Recordset.
Finding a Record in a Table-Type Recordset Object
You use the Seek method to locate a record in a table-type Recordset object.
When you use the Seek method to locate a record, the Microsoft Jet database engine uses the table's current index,
as defined by the Index property.
Important If you use the Seek method on a table-type Recordset object without first setting the current index, a
run-time error occurs.
The syntax for the Seek method is:
table.Seek comparison, key1, key2 ...

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

110

The table argument is the table-type Recordset object you're searching through. The comparison argument is a
string that determines the kind of comparison that is being performed. The following table lists the comparison
strings you can use with the Seek method.
Greater than the specified key values
Less than the specified key values
The keyn arguments are a series of one or more values that correspond to the field or fields that make up the
current index of the Recordset. Microsoft Jet compares these values to values in the corresponding fields of the
Recordset object's records.
The following example opens a table-type Recordset object called Employees, and uses the Seek method to locate
the record containing a value of lngEmpID in the EmployeeID field. It returns the hire date for the specified
employee.
Function intGetHireDate(lngEmpID As Long, varHireDate As Variant) As Integer
Dim rstEmployees As Recordset, dbs As Database
Const conFilePath As String = "C:\Program Files\Microsoft Office\Office\Samples\"
On Error GoTo ErrorHandler
Set dbs = OpenDatabase(conFilePath & "Northwind")
Set rstEmployees = dbs.OpenRecordset("Employees", dbOpenTable)
rstEmployees.Index = "PrimaryKey"
' The index name for Employee ID.
rstEmployees.Seek "=", lngEmpID
If rstEmployees.NoMatch Then
varHireDate = Null
' The constants conErrNoMatch, conSuccess, and conFailed are defined at
' the module level as public constants with Integer values of
' -32,761, 0, and -32,737 respectively.
intGetHireDate = conErrNoMatch
Exit Function
Else
varHireDate = rstEmployees!HireDate
intGetHireDate = conSuccess
Exit Function
End If
ErrorHandler:
Select Case Err
Case 0
Exit Function
Case Else
varHireDate = Null
intGetHireDate = conFailed
MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR"
Exit Function
End Select
End Function
The Seek method always starts searching for records at the beginning of the Recordset object. If you use the Seek
method with the same arguments more than once on the same Recordset, it finds the same record.
You can use the NoMatch property on the Recordset object to test whether a record matching the search criteria
was found. If the record matching the criteria was found, the NoMatch property will be False; otherwise it will be
True.
Triggers the following sequence of events:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

111

Displays the following dialog box after the BeforeDelConfirm event.


If you want, you can prevent this dialog box from appearing in two ways. You can cancel the BeforeDelConfirm
event, in which case the deletion is canceled. Or you can set the Response argument of the BeforeDelConfirm
event procedure to acDataErrContinue, in which case the deletion is confirmed.
After the deletion is confirmed, your BeforeDelConfirm event procedure can display a custom dialog box and
handle the user's responses.
Canceling Events
Under some circumstances, you may want to include code in an event procedure that cancels the associated event.
For example, you may want to include code that cancels the Open event in an Open event procedure for a form,
preventing the form from opening if certain conditions are not met.
You cancel an event by specifying a macro containing the CancelEvent action as the corresponding event property
setting or, with the exception of the MouseDown and KeyPress events, by setting an event procedure's Cancel
argument to True. For example, to prevent a form from opening, you can:
Create a macro that carries out the CancelEvent action, and then specify that macro as the form's OnOpen event
property setting.
Add code to the form's Open event procedure that sets the procedure's Cancel argument to True.
You can see an example of canceling an Unload event in the EventHistory form of the Orders sample application.
Because the EventHistory form is required by the ShowEvents form, it's important to close the ShowEvents form
before closing the EventHistory form. To ensure that the ShowEvents form isn't left open without the
EventHistory form, the EventHistory form's Unload event procedure cancels the Unload event if you try to close
it when the ShowEvents form is open.
Private Sub Form_Unload (Cancel As Integer)
' Reminds you to close the ShowEvents form, ensuring that
' it isn't left open without an open EventHistory form.
Dim intX As Integer
' Loop through the open forms. If the ShowEvents form is found,
' display a message and cancel the Unload event.
For intX = 0 To Forms.Count - 1
If Forms(intX).Name = "ShowEvents" Then
MsgBox "Please close the ShowEvents form."
Cancel = True
Exit For
End If
Next intX
End Sub
Default Events
Many types of objects in Microsoft Access are most often associated with a particular event. For example,
command buttons are most commonly associated with the Click event.
To make it easier for you to program responses to these default events, the shortcut menu includes the Build Event
command. When you right-click on an object, and then click Build Event, Microsoft Access displays the Choose
Builder dialog box. If you click Macro Builder, Microsoft Access sets the object's default event property to the
name of the macro you create. If you click Code Builder, Microsoft Access opens the Module window and
displays the object's default event procedure.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

112

Note No event occurs for an object by default. The default event is only the event procedure that Microsoft
Access displays when you click Build Event on the shortcut menu. Microsoft Access always runs the procedure
associated with an object and the event that actually occurs, regardless of the object's default event.
The following table shows the default events for Microsoft Access objects that have them.

Displaying Values of Controls and Properties


You can evaluate any valid expression in the Immediate pane, including expressions involving the values and
properties for Microsoft Access objects or Data Access Objects (DAO). For example:
? Forms!SalesReps.RecordSource
Employees
In the preceding example, the first line is the expression to be evaluated. The second line is what is displayed in
the Immediate pane the value of the RecordSource property for the open SalesReps form.
Displaying Values in the Immediate Pane from Code
While you're testing your code, you may want to display the results of expressions in your code as it's running.
You can use the Print method of the Debug object to display the results in the Immediate pane. The syntax for the
Print method of the Debug object is:
Debug.Print [outputlist]
In this syntax, you can use the optional outputlist argument to specify an expression or list of expressions to print.
For example, you can add the Print method of the Debug object to the DueDate function as follows:
Public Function DueDate(ByVal AnyDate As Date) As Variant
' This function calculates and returns the date of first
' day of month that follows the supplied date.
Debug.Print "Year "; Year(AnyDate); "Month "; Month(AnyDate)
DueDate = DateSerial(Year(AnyDate), Month(AnyDate) + 1, 1)
End Function
Now whenever this function is called, it displays the the current year and month in the Immediate pane, as well as
the value returned by the DueDate function. For example, you can call this function from the Immediate pane and
provide the date 4-12-96 for the AnyDate argument. If you're stepping through the code, as shown in the
following illustration, the function prints the value for the year (1996) and the value for the month (4) to the
Debug window once the line that contains the Debug.Print statement runs.
When you run the next line of code, the function evaluates the expression that determines the date of the first day
of the following month. This is the value returned by the DueDate function. When the DueDate function has
finished running, this value is also printed to the Debug window. In this case, the return value of the function is 51-96.
Displaying values in the Immediate pane from code has a couple of advantages. First, you don't have to suspend
execution to get feedback on how your code is performing. You can view data or other messages as you run your
code. Second, the feedback is displayed in a separate area (the Immediate pane), so it doesn't interfere with output
you want users to see.
Once you're sure your code is working correctly, you can remove Debug.Print statements. Displaying values in
the Immediate pane slows your code slightly, so you don't want to leave Debug.Print statements in your code

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

113

when you don't need to use them. You can also use conditional compilation to specify when to include these
statements and when to ignore them during compilation and at run time.
Note The Debug window doesn't open automatically when Visual Basic encounters a Debug.Print statement. If
you don't have the Debug window open, you won't see the values displayed by the Debug.Print statement. You
can open the Debug window quickly by pressing CTRL+G.
Tips on Using the Immediate Pane
You can use the following shortcuts in the Immediate pane:
Once you've run a statement in the Immediate pane, you can run it again by putting the insertion point anywhere
in the statement and pressing ENTER.
Before pressing ENTER, you can edit the statement in which the insertion point appears.
You can use the mouse or the arrow keys to move the insertion point in the Immediate pane. Press ENTER only
if the insertion point is on a statement you want to run.
You can use the PAGE UP and PAGE DOWN keys within the Immediate pane to move through your code one
page at a time. Pressing CTRL+ END moves the insertion point to the end of the Immediate pane.
You can use the HOME key to move the insertion point to the beginning of the current line and the END key to
move the insertion point to the end of the current line.

Assigning Values to Controls, Properties, and Variables


In addition to examining the values of variables and expressions, you can also use the Locals pane or the Watch
pane to assign new values to controls, properties, and variables. This is useful when you are debugging; as you
develop hypotheses about the cause of an error, you may want to test the effects of particular values.
To change the value of a variable or property, locate the value in the Locals pane or the Watch pane, click the
value in the Value column for the item you want to change, type the new value, and then press ENTER.
You can also use the Immediate pane to set values. For example, you can type assignment statements such as:
Forms!SalesReps!Title = "Sales Executive"
Forms!SalesReps!Title.Visible = False
intRows = 50
After you change the values of one or more controls, properties, or variables, you can continue execution to see
the results of your changes.
Tracing Nested Procedures
When your application is running, Microsoft Access keeps track of each time one procedure calls another. If
you're tracing the progress of your application and want to find out which procedures have been called, click Calls
on the View menu. Or, in the Debug window, click the Calls button to the right of the Procedure box.
The Calls dialog box displays a list of all active procedure calls the procedures in an application that are started
but not completed.
The Calls dialog box is especially helpful if you want to trace nested procedures. For example, an event procedure
can call a second procedure, which can call a third procedure all before the event procedure that started this chain
has finished. In the following illustration, the Calls dialog box lists two active procedure calls: the form's Load
event procedure has called the IsLoaded Function procedure.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

114

The Calls dialog box lists all the active procedure calls in a series of nested procedure calls. It places the earliest
active procedure call at the bottom of the list and adds subsequent procedure calls to the top. The information
given for each procedure begins with the name of the database and module that contain the procedure, followed
by the name of the called procedure.
You can use the Show button to display the statement that calls the next procedure listed in the Calls dialog box. If
you choose the current (top) procedure in the Calls dialog box and then click Show, Visual Basic displays the
current statement (the statement at which execution is suspended).
Debugging Event Procedures
Certain events that are a normal part of using Microsoft Windows can pose special challenges when you're
debugging an application that uses event procedures. It's important to be aware of these issues so they don't
confuse you or complicate the debugging process.
If you keep in mind that suspending execution can put events at odds with what your application expects, you can
usually find solutions. You may need to use the Print method of the Debug object instead of breakpoints to
monitor values of properties or variables. You may also need to change the values of variables that depend on the
sequence of events.
If you suspend execution during a MouseDown event procedure, you can release the mouse button or use the
mouse to do any number of tasks. However, when you continue execution, the application assumes that the mouse
button is still pressed down. A MouseUp event doesn't occur until you press the mouse button down again and
release it.
When you press the mouse button down after you continue execution, you suspend execution in the MouseDown
event procedure again if it contains a breakpoint. In this scenario, the MouseUp event never occurs. The typical
solution is to remove the breakpoint in the MouseDown event procedure.
If you suspend execution during a KeyDown event procedure, considerations similar to those during a
MouseDown event procedure apply. If you retain a breakpoint in a KeyDown event procedure, a KeyUp event
may never occur.
If you suspend execution during a GotFocus or LostFocus event procedure, the timing of system messages may
cause inconsistent results. You can avoid this problem by using the Print method of the Debug object instead of
suspending execution in GotFocus or LostFocus event procedures.
Using Conditional Compilation
You use conditional compilation to specify the parts of your program you want Microsoft Access to include or
ignore when compiling and running. Using conditional compilation, you can maintain a single version of your
application that behaves differently under certain conditions. For example, you may use conditional compilation
to:
Include specific features of your program in different versions of your application. For example, you may want
to design your application to run on different platforms.
Change the date and currency display filters for an application distributed in several different languages.
Include or exclude code used for debugging your application.
Structuring Code for Conditional Compilation

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

115

Visual Basic provides special statements called directives for creating conditional compilation constructs. You use
the #Const directive to declare a Boolean conditional compilation constant. You then evaluate this constant within
the #If...Then...#Else directive.
To conditionally compile a portion of your code, enclose it between #If...Then and #End If statements, using the
conditional compilation constant as the branching test. When you want this segment of code to be compiled and
run, set the value of the constant to True (-1). Otherwise, set the constant to False (0).
For example, suppose you want to include a portion of code only in an administrator's copy of your application.
Start by wrapping this segment in an If...Then statement preceded by a number sign (#).
#If Admn Then
.
. ' Insert code to be compiled and run only for an administrator's
. ' version.
#End If
If the value of the constant Admn is set to True at compile time, Visual Basic compiles and runs the conditional
code. Otherwise, Visual Basic ignores it.
Note Unlike regular Visual Basic code, you can't include other statements on the same line as a conditional
compilation statement by separating those statements with colons.
Declaring Conditional Compilation Constants
You can declare conditional compilation constants by setting the Conditional Compilation Arguments option on
the Advanced tab of the Options dialog box (Tools menu). The list should contain simple assignment statements
separated by colons. For example:
Admn = True : Ansi = 0
Alternatively, you can explicitly declare conditional compilation constants in the Declarations section of the
module containing the #If...Then and #Else statements, as follows:
#Const Admn = True
Conditional compilation constants have a special scope and cannot be accessed from standard code. While
constants declared with the #Const statement are private to the module in which they are declared, constants
declared in the Options dialog box are public to all modules in your application.
Only conditional compilation constants and literals can be used in expressions that you specify by way of the user
interface or with the #Const statement. Any undeclared identifier used in a conditional compilation expression
generates a compile error.
Using Conditional Compilation for Debugging
You can use conditional compilation to remove debugging statements from the application you distribute to users.
To do this, use conditional compilation to include these statements during development, and then ignore these
statements in the version of your application that you distribute to users.
The following example procedure uses these statements to display an assertion message when a function is passed
a value it isn't designed to handle. You may use a function like this one while writing and debugging your code.
Once you've finished debugging your code and you're ready to distribute your application to users, you no longer
need the function.
Sub Assertion(blnExpr As Boolean, strMsg As String)

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

116

If Not blnExpr Then


MsgBox strMsg, , "Assertion"
End If
End Sub
Sub AProcedure(intX As Integer)
#If fDebug Then
Assertion intX < 10000 and intX > 0, "Argument out of range"
#End If
' The code can now assume the correct value.
End Sub
Because the call to the Assertion procedure is conditionally compiled, it is only run if fDebug is set to True. When
you compile your application to distribute it to users, set fDebug to False. As a result, the application will compile
and run as efficiently as possible.

Handling Run-Time Errors


Part of application development is to anticipate and plan for the errors users may encounter. While your
application is running, external events that you can't prevent or may not anticipate are likely to occur. For
example, files may be mistakenly deleted, disk drives can run out of space, and network drives sometimes
disconnect unexpectedly. Such eventualities can cause run-time errors in your code, that is, errors that Microsoft
Access can detect only when your application is running. To handle these errors, you need to add error-handling
code to your procedures.
Errors and Error Handling
In the world of programming, an error often isn't the same as a mistake. An error may be the result of an event or
an operation that doesn't work out as expected, or the result of an attempt to carry out an impossible or invalid
maneuver.
Often, you can anticipate the errors that users will encounter when they work with your application. You can
shield users from these errors by including error-handling code, also known as error trapping, in your application.
You don't have to use error handling in every procedure that you write, but it's a good idea to include it whenever
your application interacts with something external, like the user's machine, a network, or a remote database. You
should also use error handling when your application opens, updates, deletes, or adds a database record.
Error-handling code typically traps a run-time error by interrupting the default Microsoft Access response to the
error and instead running code that you specify. Your application can correct the error, or give the user an
opportunity to correct the error.
Run-time errors occur when the application is actually running; other types of errors can occur under different
circumstances. For example, a syntax error can occur when you enter code in the Module window, and a compile
error can occur when you compile code. In Microsoft Access, however, you can trap only run-time errors.
If your application uses Visual Basic code, you usually have to use both types of error handling.
Both types of error handling require that you identify each error you want to handle by its error code. You usually
declare a constant to represent the numeric error code, and then use the constant to refer to the error. You can
create an Errors table in Microsoft Access that contains the error codes and strings used or reserved by Visual
Basic. Search the Help index for "trapping errors," and then click Determine the Error Codes Reserved by Visual
Basic. You can copy and paste the CreateErrorsTable procedure from this Help topic into a module.
After you create the Errors table, you can find the error code for an error by looking up its corresponding error
message. To do so, open the Errors table, select the Error String column, click Find on the Edit menu, type the
error message in the Find What box, and then click Find First. You can also base a query or report on the table, or
sort the Error String column.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

117

The Err Object, the Errors Collection, and the Error Function
Microsoft Access provides the Err object and the Errors collection to help you retrieve information about run-time
errors in your applications, and regenerate errors when necessary.
Microsoft Access uses the Err object to store information about the most recent run-time error that has occurred.
When a run-time error occurs, the properties of the Err object are filled with information that uniquely identifies
the error's number, description, and source. Depending on the type of error that is generated, you may also be able
to obtain other information about the error.
You can also use the Err object to regenerate errors that have occurred in your application.
The Errors collection is contained by the DBEngine object. It contains one Error object for each error that is
generated by a single operation involving Data Access Objects (DAO). If multiple errors occur during a single
operation, then the collection contains more than one Error object. When another operation generates an error or
errors, the existing collection is cleared and refreshed with a new set of Error objects. Unlike other collections, the
Errors collection doesn't append objects to existing objects as subsequent operations occur.
In addition to the Error object and the Errors collection, there is also an Error function. The Error function returns
the error description that corresponds to a given error number. The Error function and the Description property of
the Err object return the same value.

Errors and Error Handling


In the world of programming, an error often isn't the same as a mistake. An error may be the result of an event or
an operation that doesn't work out as expected, or the result of an attempt to carry out an impossible or invalid
maneuver.
Often, you can anticipate the errors that users will encounter when they work with your application. You can
shield users from these errors by including error-handling code, also known as error trapping, in your application.
You don't have to use error handling in every procedure that you write, but it's a good idea to include it whenever
your application interacts with something external, like the user's machine, a network, or a remote database. You
should also use error handling when your application opens, updates, deletes, or adds a database record.
Error-handling code typically traps a run-time error by interrupting the default Microsoft Access response to the
error and instead running code that you specify. Your application can correct the error, or give the user an
opportunity to correct the error.
Run-time errors occur when the application is actually running; other types of errors can occur under different
circumstances. For example, a syntax error can occur when you enter code in the Module window, and a compile
error can occur when you compile code. In Microsoft Access, however, you can trap only run-time errors.
If your application uses Visual Basic code, you usually have to use both types of error handling.
Both types of error handling require that you identify each error you want to handle by its error code. You usually
declare a constant to represent the numeric error code, and then use the constant to refer to the error. You can
create an Errors table in Microsoft Access that contains the error codes and strings used or reserved by Visual
Basic. Search the Help index for "trapping errors," and then click Determine the Error Codes Reserved by Visual
Basic. You can copy and paste the CreateErrorsTable procedure from this Help topic into a module.
After you create the Errors table, you can find the error code for an error by looking up its corresponding error
message. To do so, open the Errors table, select the Error String column, click Find on the Edit menu, type the

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

118

error message in the Find What box, and then click Find First. You can also base a query or report on the table, or
sort the Error String column.
The Err Object, the Errors Collection, and the Error Function
Microsoft Access provides the Err object and the Errors collection to help you retrieve information about run-time
errors in your applications, and regenerate errors when necessary.
Microsoft Access uses the Err object to store information about the most recent run-time error that has occurred.
When a run-time error occurs, the properties of the Err object are filled with information that uniquely identifies
the error's number, description, and source. Depending on the type of error that is generated, you may also be able
to obtain other information about the error.
You can also use the Err object to regenerate errors that have occurred in your application.
The Errors collection is contained by the DBEngine object. It contains one Error object for each error that is
generated by a single operation involving Data Access Objects (DAO). If multiple errors occur during a single
operation, then the collection contains more than one Error object. When another operation generates an error or
errors, the existing collection is cleared and refreshed with a new set of Error objects. Unlike other collections, the
Errors collection doesn't append objects to existing objects as subsequent operations occur.
In addition to the Error object and the Errors collection, there is also an Error function. The Error function returns
the error description that corresponds to a given error number. The Error function and the Description property of
the Err object return the same value.

Using Error Events


You can handle run-time errors that trigger an Error event by adding code to a form or report's Error event
procedure. The Error event is triggered by any run-time error that is generated either in the Microsoft Access
interface or by the Microsoft Jet database engine. The Error event won't trap errors in your Visual Basic code. To
handle run-time errors generated by Visual Basic, use On Error statements.
For example, the Orders form in the Orders sample application includes code in its Error event procedure to
handle the error that is generated if Microsoft Access tries to save a record that doesn't have an EmployeeID or
CustomerID value. When Microsoft Access generates this error, the Form_Error procedure traps it and displays a
custom dialog box.
Private Sub Form_Error(DataErr As Integer, Response As Integer)
' If error is due to a broken join (missing CustomerID), display a
' message telling the user what's missing and how to cancel the order.
Const conErrBrokenJoin = 3101
If DataErr = conErrBrokenJoin Then
MsgBox "Every order must have a customer for billing. " & _
"Select a company name in the Bill To list, or to " & _
"cancel the order, click Undo Current Field/Record " & _
"on the Edit menu.", vbExclamation
Forms!Orders.SetFocus
BillTo.SetFocus
Response = acDataErrContinue
End If
End Sub
When an error triggers an Error event in the Orders form, Microsoft Access runs the Form_Error procedure and
passes the error code for the error to the procedure's DataErr argument. For example, if you enter a new record,
but neglect to enter a value in the Salesperson box (EmployeeID field), then try to move to the next record,
Microsoft Access:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

119

1. Generates error code 3101, indicating a broken table join.


2. Runs the Form_Error procedure, assigning error code 3101 to the DataErr argument.
The procedure declares the constant conErrBrokenJoin, which represents error code 3101, for the broken join
error.
The If statement then uses this constant to test whether the DataErr argument matches this error code. If it does,
the procedure traps the error and displays a custom dialog box so the user can correct the problem by entering a
value. It then sets the procedure's Response argument to acDataErrContinue, which causes Microsoft Access to
continue execution without displaying the standard Microsoft Access error message.
Important Because this error-handling code relies on the DataErr argument of the Error event procedure, you can
do this kind of error trapping only in an event procedure. You can't trap an error by specifying a macro as an
OnError property setting for a form or report.
You can often use an Error event procedure in this way to display a custom message that is more meaningful to
your application's users than a standard Microsoft Access error message.
You can also add code to the Error event procedure to either take some action to correct the error, or to give the
user an opportunity to correct it. For example, in a multiuser environment, an Error event procedure can handle
common locking conflicts, such as those that occur when one user attempts to update data or save a record that is
locked by another user. You can add error-handling code to the procedure to automatically retry an operation that
has failed because of a locking conflict, or to display a custom dialog box that gives users the choice of retrying or
canceling the operation.

Using On Error Statements


You can handle Visual Basic run-time errors by adding On Error statements and error-handling code to your
procedures. For example, the event procedures created by Microsoft Access wizards include On Error statements
and error-handling code. The following Click event procedure created by a control wizard shows how many of the
procedures created by wizards handle errors.
Private Sub Button1_Click()
Dim strDocName As String
On Error GoTo Err_Button1_Click
strDocName = "ProductsPopUp"
DoCmd.OpenForm strDocName
Exit_Button1_Click:
Exit Sub
Err_Button1_Click:
MsgBox Err.Description
Resume Exit_Button1_Click
End Sub
In this code, the On Error statement turns on error handling. If an error occurs, Visual Basic branches (passes
program control) to the Err_Button1_Click line label, which marks the beginning of the error-handling code. It
then runs the MsgBox statement, displaying the error's message string. Visual Basic then runs the Resume
statement to exit the error-handling code, branching to the Exit_Button1_Click line label. Finally, Visual Basic
runs the Exit Sub statement to exit the Button1_Click procedure.
If no error occurs, Visual Basic runs each line of code until it reaches the Exit Sub statement and then exits the
procedure. It doesn't run the Err_Button1_Click error-handling code that follows.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

120

If your application doesn't handle Visual Basic run-time errors, users may be surprised if a run-time error
suddenly halts the application. It's especially important to handle Visual Basic errors if you're creating a run-time
version of your application. A run-time application shuts down if an untrapped error occurs.
For example, the following procedure doesn't contain error-handling code. It returns True (- 1) if the specified file
exists and False (0) if it doesn't exist.
Function FileExists (ByVal strFileName As String) As Boolean
FileExists = (Dir(strFileName) <> "")
End Function
The Dir function returns the first file matching the specified file name, and returns a zero-length string ("") if no
matching file is found. The code appears to cover either of the possible outcomes of the Dir call. However, if the
drive letter specified in the argument isn't a valid drive, the run-time error message "Device unavailable" is
displayed. If the specified drive is a floppy disk drive, this function works correctly only if a disk is in the drive
and the drive door is closed. If not, the run-time error "Disk not ready" occurs. In both cases, Microsoft Access
displays the error message and halts execution of the code.
To avoid this situation, you can use an On Error statement to respond to Visual Basic errors and take corrective
action. For example, device problems such as an invalid drive or an empty floppy disk drive can be handled by
the following code:
Function FileExists (ByVal strFileName As String) As Boolean
Dim strMsg As String
On Error GoTo CheckError
' Turn on error handling.
FileExists = (Dir(strFileName) <> "") ' Use Dir function to see
' if file exists.
Exit Function
' Avoid running error-handling
' code if no error occurs.
CheckError:
' Run following code
' if error occurs.
' Declare constants to represent Visual Basic error codes.
Const conErrDiskNotReady = 71, conErrDeviceUnavailable = 68
' vbExclamation, vbOK, vbCancel, vbCritical, and vbOKCancel are
' intrinsic constants that don't need to be declared.
If (Err.Number = conErrDiskNotReady) Then
' Display message box with an exclamation point icon and with
' OK and Cancel buttons.
strMsg = "Put a floppy disk in the drive and close the drive door."
If MsgBox(strMsg, vbExclamation + vbOKCancel) = vbOK Then
Resume
Else
Resume Next
End If
ElseIf Err.Number = conErrDeviceUnavailable Then
strMsg = "This drive or path does not exist: " & strfilename
MsgBox strMsg, vbExclamation
Resume Next
Else
strMsg = "Error number " & Str(Err.Number) & " occurred: " & _
Err.Description
' Display message box with stop sign icon and OK button.
MsgBox strMsg, vbCritical
Stop
End If
Resume

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

121

End Function
This code uses properties of the Err object to return the error code number and the message string associated with
the run-time error that occurred.
When Visual Basic generates the error "Disk not ready," the FileExists function displays a message telling the
user to click one of two buttons, OK or Cancel. If the user clicks OK, the Resume statement returns program
control to the statement at which the error occurred and attempts to run that statement again. This statement
succeeds if the user has corrected the problem; otherwise, the program returns to the error-handling code.
If the user clicks Cancel, the Resume Next statement returns program control to the statement following the one at
which the error occurred, in this case, the Exit Function statement.
If the "Device unavailable" error occurs, Visual Basic displays a message describing the problem. The Resume
Next statement then returns program control to the statement following the one at which the error occurred.
If an unanticipated error occurs, Visual Basic displays an alternative message and halts the code at the Stop
statement.
The error-handling code in the preceding example involves three steps:
1. Turn on error handling, and set (enable) an error trap by telling the application where to branch (which errorhandling routine to run) when an error occurs.
The On Error statement in the FileExists function turns on error handling and directs the application to the
CheckError line label.
2. Write error-handling code that responds to all errors you can anticipate. If program control actually branches to
the error-handling code at some point, the trap is then said to be active.
The CheckError code handles the error by using an If...Then...Else statement that checks the value returned by the
Number property of the Err object. The Number property of the Err object returns an error code number
corresponding to the error message that Visual Basic generates. In the example, if the "Disk not ready" error is
generated, a message prompts the user to close the drive door. A different message is displayed if the "Device
unavailable" error occurs.
Your code should also determine what action to take if an unanticipated error occurs. In the previous FileExists
function, if any error other than "Disk not ready" or "Device unavailable" occurs, a general message is displayed
and the program stops.
3. Exit the error-handling code.
In the case of the "Disk not ready" error, the Resume statement passes program control back to the statement at
which the error occurred. Visual Basic then tries to run that statement again. If the situation hasn't changed, then
the same error occurs again, and execution branches back to the error-handling code.
In the case of the "Device unavailable" error, the Resume Next statement passes program control to the statement
following the one at which the error occurred.
Writing Error-Handling Code
Begin the error-handling code with the line label specified in the On Error statement. In the FileExists function,
the line label is CheckError. The colon is part of the label, although it isn't used in the On Error GoTo line
statement.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

122

You'll usually include error-handling code at the end of a procedure, before the End Function or End Sub
statement. Enter an Exit Function, Exit Sub, or Exit Property statement at the end of the main procedure code, but
immediately preceding the error handler's line label. This prevents Visual Basic from running the error-handling
code at the conclusion of the main-procedure code if no error occurs.
The Number property of the Err object returns an error code number representing the most recent run-time error.
By using the Number property of the Err object in combination with the Select Case or If...Then...Else statement,
you can take specific action for any error that occurs.
Note The string returned by the Description property of the Err object always explains the error associated with
the current error code number. However, the exact wording of the message may vary among different versions of
Microsoft Access. Therefore, use the Number property rather than the Description property to identify the specific
error that occurred.
When designing your error-handling routine, include code that tells the user what the problem is and how to
proceed. Also, if the application can't continue after an error is encountered, it's a good idea to close open objects,
remote connections, and database files when you exit the application.
Exiting Error-Handling Code
The preceding FileExists function uses the Resume statement within the error-handling code to rerun the
statement that caused the error, and uses the Resume Next statement to resume execution at the statement
following the one at which the error occurred. Depending on the circumstances, there are other ways to exit errorhandling code; regardless of which way you exit, you should always tell the error handler what to do when its
execution is complete. To exit error-handling code, use any of the statements shown in the following table.
Resumes program execution at the label specified by line, where line is a line label that must be in the same
procedure as the error handler.
Err.Raise Number:= number
Triggers the most recent run-time error again. When this statement is run within error-handling code, Visual Basic
searches backward through the calls list for other error-handling code. The calls list is the chain of procedures that
lead to the current point of execution. For more information, see "Unanticipated Errors" later in this chapter.
The Resume and Resume Next Statements
You can use the Resume and Resume Next statements to perform similar functions. The Resume statement returns
program control to the statement that caused the error. You use it to rerun the statement after correcting the error.
The Resume Next statement returns program control to the statement immediately following the one that caused
the error. The difference between Resume and Resume Next is shown in the following illustration.
Generally, you use the Resume statement whenever the user must make a correction. Use the Resume Next
statement whenever a correction by the user isn't required, and you want to continue program execution without
attempting to rerun the statement that caused the error. You can also use the Resume Next statement if you
anticipate an error in a loop, and you want to start the loop operation again if an error occurs. With the Resume
Next statement, you can write error-handling code so that the existence of a run-time error isn't revealed to the
user.
For example, the following Divide function uses error handling to perform division on its numeric arguments
without revealing errors that have occurred. There are several errors that can occur in division. If the numerator is
not zero, but the denominator is zero, Visual Basic generates the "Division by zero" error; if both numerator and
denominator are zero in floating-point division, it generates the "Overflow" error; or if either the numerator or the
denominator is a nonnumeric value (or can't be considered a numeric value), Visual Basic displays an appropriate
error message. In all three cases, the Divide function traps these errors and returns the Null value.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

123

Function Divide (Numerator, Denominator) As Variant


Const conErrDivo = 11, conErrOverflow = 6, conErrIllFunc = 5
On Error GoTo MathHandler
Divide = Numerator / Denominator
Exit Function
MathHandler:
' If error was Division by zero, Overflow, or
' Illegal function call, return Null.
If Err.Number = conErrDivo Or Err.Number = conErrOverflow Or _
Err.Number = conErrIllFunc Then
Divide = Null
Else
MsgBox "Unanticipated error " & Err.Number & ": " & _
Error.Description, vbExclamation
End If
' In all cases, Resume Next continues execution at the
' Exit Function statement.
Resume Next
End Function
The Resume line Statement
Alternatively, you can use the syntax Resume line, which returns control to a specified line label. The following
example illustrates the use of the Resume line statement. A variation on the preceding FileExists function, the
following VerifyFile function enables the user to enter a file specification that the function returns if the file
exists.
Function VerifyFile () As Variant
Const conErrBadFileName = 52, conErrDriveDoorOpen = 71
Const conErrDeviceUnavailable = 68, conErrInvalidFileName = 64
Dim strPrompt As String, strMsg As String, strFileSpec As String
On Error GoTo Handler
strPrompt = "Enter file specification to check:"
StartHere:
strFileSpec = "*.*"
' Start with a default spec.
strMsg = strMsg & vbCrLf & strPrompt
' Let the user modify the default.
strFileSpec = InputBox(strMsg, "File Search", strFileSpec, 100, 100)
' Exit if the user enters nothing.
If strFileSpec = "" Then Exit Function
VerifyFile = Dir(strFileSpec)
Exit Function
Handler:
Select Case Err.Number
' Analyze error code, then load message.
Case conErrInvalidFileName, conErrBadFileName
strMsg = "Your file specification is invalid. Try another."
Case conErrDriveDoorOpen
strMsg = "Close the disk drive door and try again."
Case conErrDeviceUnavailable
strMsg = "The drive you specified was not found. Try again."
Case Else
Dim intErrNum As Integer
intErrNum = Err.Number
Err.Clear
' Clear the Err object.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

124

Err.Raise Number:=intErrNum
' Regenerate the error.
End Select
' This jumps back to the StartHere label so the user can
' try another file name.
Resume StartHere
End Function
If a file matching the specification is found, the function returns the file name. If no matching file is found, the
function returns a zero-length string (""). If one of the anticipated errors occurs, a message appropriate to the error
is assigned to Msg and execution branches to the StartHere line label. This gives the user another chance to enter
a valid path and file specification.
Use the Resume line statement when you want to resume execution at a place other than the statement that caused
the error, or the line immediately after the statement that caused the error. The Resume line statement can be
especially useful if you want to exit the error-handling code and branch to a point just before an Exit statement in
a Function or Sub procedure.
Note Although branching to a line label can be useful in some circumstances, jumps to labels are often
considered throwbacks to a less structured style of programming. Too many Resume line statements can make
code difficult to understand and debug.
The following example illustrates how you can create a function that uses the Seek method to locate a record by
using a multiple-field index:
Function GetFirstPrice(lngOrderID As Long, lngProductID As Long) As Variant
Dim dbs As Database, rstOrderDetail As Recordset
On Error GoTo ErrorHandler
Set dbs = CurrentDb
Set rstOrderDetail = dbs.OpenRecordset("Order Details", dbOpenTable)
rstOrderDetail.Index = "PrimaryKey"
rstOrderDetail.Seek "=", lngOrderID, lngProductID
If rstOrderDetail.NoMatch Then
GetFirstPrice = Null
MsgBox "Couldn't find order detail record."
Else
GetFirstPrice = rstOrderDetail!UnitPrice
End If
rstOrderDetail.Close
ErrorHandler:
Select Case Err
Case 0
Exit Function
Case Else
MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR"
Exit Function
End Select
End Function
In this example, the table's primary key consists of two fields: OrderID and ProductID. When you call the
GetFirstPrice function with a valid (existing) combination of OrderID and ProductID field values, the function
returns the unit price from the found record. If it can't find the combination of field values you want in the table,
the function returns the Null value.
If the current index is a multiple-field index, trailing key values can be omitted and are treated as Null values.
That is, you can leave off any number of key values from the end of a Seek method's key argument, but not from

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

125

the beginning or the middle. However, if you don't specify all values in the index, you can use only the ">" or "<"
comparison string with the Seek method.
Finding a Record in a Dynaset- or Snapshot-Type Recordset Object
You can use the Find methods to locate a record in a dynaset- or snapshot-type Recordset object. DAO provides
four Find methods:
The FindFirst method finds the first record satisfying the specified criteria.
The FindLast method finds the last record satisfying the specified criteria.
The FindNext method finds the next record satisfying the specified criteria.
The FindPrevious method finds the previous record satisfying the specified criteria.
Note To locate a record in a table-type Recordset object, use the Seek method, which is described in the previous
section.
When you use the Find methods, you specify the search criteria; typically an expression equating a field name
with a specific value.
You can locate the matching records in reverse order by finding the last occurrence with the FindLast method and
then using the FindPrevious method instead of the FindNext method.
DAO sets the NoMatch property to True whenever a Find method fails and the current record position is
undefined. There may be a current record, but you have no way to tell which one. If you want to be able to return
to the previous current record following a failed Find method, use a bookmark.
The NoMatch property is False whenever the operation succeeds. In this case, the current record position is the
record found by one of the Find methods.
The following example illustrates how you can use the FindNext method to find all orders in the Orders table that
have no corresponding records in the Order Details table and adds the value in the OrderID field to the array
lngOrderID().
Function FindEx(lngOrderID() As Long)
Dim dbs As Database, rstOrders As Recordset
Dim strQuery As String, rstOrderDetails As Recordset
Dim intIndex As Integer
On Error GoTo ErrorHandler
Set dbs = CurrentDb
' Open recordsets on the Orders and Order Details tables. If there are no
' records in either table, exit the function.
strQuery = "SELECT * FROM Orders ORDER BY OrderID;"
Set rstOrders = dbs.OpenRecordset(strQuery, dbOpenSnapshot)
If rstOrders.EOF Then Exit Function
strQuery = "SELECT * FROM [Order Details] ORDER BY OrderID;"
Set rstOrderDetails = dbs.OpenRecordset(strQuery, dbOpenSnapshot)
' For the first record in Orders, find the first matching record
' in OrderDetails. If no match, redimension the array of order IDs and
' add the order ID to the array.
rstOrderDetails.FindFirst "OrderID = " & rstOrders![OrderID]
If rstOrderDetails.NoMatch Then
ReDim Preserve lngOrderID(1 To intIndex)
lngOrderID(intIndex) = rstOrders![OrderID]

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

126

End If
' The first match has already been found, so use the FindNext method to find the
' next record satisfying the criteria.
intIndex = 0
Do Until rstOrders.EOF
rstOrderDetails.FindNext "OrderID = " & rstOrders![OrderID]
If rstOrderDetails.NoMatch Then
intIndex = intIndex + 1
ReDim Preserve lngOrderID(1 To intIndex)
lngOrderID(intIndex) = rstOrders![OrderID]
End If
rstOrders.MoveNext
Loop
ErrorHandler:
Select Case Err
Case 0
Exit Function
Case Else
MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR"
Exit Function
End Select
End Function
Tip If you need to frequently search records in a dynaset, you may find it easier to create a temporary indexed
table and use the Seek method instead.
Marking Record Position with Bookmarks
A bookmark is a system-generated Byte array that uniquely identifies each record. The DAO Bookmark property
of a Recordset object changes each time you move to a new record. To identify a record, move to that record and
then assign the value of the DAO Bookmark property to a variable of type Variant. To return to the record, set the
DAO Bookmark property to the value of the variable.
The following example illustrates how you can use a bookmark to save the current record position. You can then
perform other operations on the Recordset object, and then return to the saved record position.
Function BookMarkEx() As Integer
Dim dbs As Database, rstProducts As Recordset
Dim vBookMark As Variant, sngRevenue As Single
Dim strQuery As String, rstCategories As Recordset, strCriteria As String
On Error GoTo ErrHandler
BookMarkEx = 0
strQuery = "SELECT * FROM Products WHERE UnitsOnOrder >= 40 ORDER BY " _
& "CategoryID, UnitsOnOrder DESC;"
Set dbs = CurrentDb
Set rstProducts = dbs.OpenRecordset(strQuery, dbOpenSnapshot)
Set rstCategories = dbs.OpenRecordset("SELECT CategoryID FROM " _
& "Categories ORDER BY CategoryID;", dbOpenSnapshot)
If rstProducts.NoMatch Then Exit Function
' For each category find the product generating the least revenue
' and the product generating the most revenue.
Do Until rstCategories.EOF
strCriteria = "CategoryID = " & rstCategories![CategoryID]
rstProducts.FindFirst strCriteria
sngRevenue = rstProducts![UnitPrice] * rstProducts![UnitsOnOrder]
If Not rstProducts.NoMatch Then

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

127

' Set a bookmark at the first record containing the CategoryID.


vBookMark = rstProducts.Bookmark
' Find the product generating the most revenue.
Do While rstProducts![CategoryID] = rstCategories![CategoryID]
If rstProducts![UnitPrice] * rstProducts![UnitsOnOrder] > sngRevenue Then
sngRevenue = rstProducts![UnitPrice] * rstProducts![UnitsOnOrder]
End If
rstProducts.MoveNext
Loop
' Move to the first record containing the CategoryID.
rstProducts.Bookmark = vBookMark
sngRevenue = rstProducts![UnitPrice] * rstProducts![UnitsOnOrder]
' Find the product generating the least revenue.
Do While rstProducts![CategoryID] = rstCategories![CategoryID]
If rstProducts![UnitPrice] * rstProducts![UnitsOnOrder] < sngRevenue Then
sngRevenue = rstProducts![UnitPrice] * rstProducts![UnitsOnOrder]
End If
rstProducts.MoveNext
Loop
End If
rstCategories.MoveNext
Loop
' Error Handler.
ErrHandler:
Select Case Err
Case 0
Exit Function
Case Else
MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR"
Exit Function
End Select
End Function
A bookmark is particularly useful if a method fails because the current record position is undefined.
The LastModified property of the Recordset object provides a good illustration of how to use a bookmark. The
LastModified property returns the bookmark of the last record in the Recordset to be added or modified. To use it,
set the DAO Bookmark property equal to the LastModified property, as follows:
rstCustomers.Bookmark = rstCustomers.LastModified
This moves the current record position to the last record that was added or modified. This is particularly useful
when adding new records, because after you add a new record, the current record is the one you were on before
you added the record. With the LastModified property, you can move to the newly added record if that's what
your application expects.
Bookmark Scope
When you close a Recordset object, any bookmarks you saved become invalid. You can't use a bookmark from
one Recordset in another Recordset, even if both Recordset objects are based on the same underlying table or
query. However, you can use a bookmark on the clone of a Recordset, as shown in the following example:
Dim dbs As Database
Dim rstOriginal As Recordset, rstDuplicate As Recordset
Dim strPlaceholder As String

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

128

Set dbs = CurrentDb


' Create the first Recordset.
Set rstOriginal = dbs.OpenRecordset("Orders", dbOpenDynaset)
' Save the current record position.
strPlaceholder = rstOriginal.Bookmark
' Create a duplicate Recordset.
Set rstDuplicate = rstOriginal.Clone()
' Go to same record.
rstDuplicate.Bookmark = strPlaceholder
rstOriginal.Close
You can also use the DAO Bookmark property on the Recordset object underlying a form. With this property,
your code can mark which record is currently displayed on the form, and then change the record that is being
displayed. For example, on a form containing employee information, you may want a button that a user can click
to show the record for an employee's supervisor. The following example illustrates the event procedure you would
use for the button's Click event:
Private Sub cmdShowSuper_Click()
Dim frmEmployees As Form
Dim rstEmployees As Recordset
Dim strOrigin As String
Dim strEmployee As String
Dim strSuper As String
Set frmEmployees = Screen.ActiveForm
' Open the Recordset.
Set rstEmployees = frmEmployees.RecordsetClone
strOrigin = frmEmployees.Bookmark
strEmployee = frmEmployees!FirstName & " " & frmEmployees!LastName
rstEmployees.FindFirst "EmployeeID = " & frmEmployees!ReportsTo
If rstEmployees.NoMatch Then
MsgBox "Couldn't find " & strEmployee & "'s supervisor."
Else
frmEmployees.Bookmark = rstEmployees.Bookmark
strSuper = frmEmployees!FirstName & " " & frmEmployees!LastName
MsgBox strEmployee & "'s supervisor is " & strSuper
frmEmployees.Bookmark = strOrigin
End If
rstEmployees.Close
End Sub
Why Use Bookmarks Instead of Record Numbers?
If you have used another database or programming environment, you may be accustomed to referring to record
numbers. For example, you may have written code that opens a text file and thereafter refers to specific records
by their relative position in the file. The first record in the file would be record 1, the second would be record 2,
and so on.
In Microsoft Access databases, your view of records (a Recordset) is usually a subset of the records in one or
more tables. Because the actual number of records in a Recordset can change at any time, especially in a
multiuser environment, there's no absolute record number you can always use to refer to a particular record. The
AbsolutePosition property isn't the same as a record number, because this property changes if a lower-numbered
record is deleted.
Furthermore, records returned in a Recordset object appear in no particular order, unless the Recordset was
created with a query that includes an ORDER BY clause, or is a table-type Recordset with an index. Record
numbers are usually meaningless in a Recordset object.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

129

Instead of record numbers, DAO provides bookmarks to uniquely identify a particular record. A given record
retains its unique bookmark for the life of the Recordset.
Which Recordset Objects Don't Support Bookmarks?
Dynasets based on certain linked tables, such as Paradox tables that have no primary key, don't support
bookmarks, nor do forward-only-type Recordset objects.
You can determine whether a given Recordset object supports bookmarks by checking the value of the
Bookmarkable property, as in the following example:
If rstLinkedTable.Bookmarkable Then
MsgBox "The underlying table supports bookmarks."
Else
MsgBox "The underlying table doesn't support bookmarks."
End If
Important If you try to use bookmarks on a Recordset object that doesn't support bookmarks, a run-time error
occurs.
Changing Data
After you've created a table- or dynaset-type Recordset object, you can change, delete, or add new records. You
can't change, delete, or add records to a snapshot-type or forward-only-type Recordset object.
This section presents the methods and procedures for changing data in table- and dynaset-type Recordset objects.
Using Parameter Queries
A parameter query is a query that when run displays a dialog box that prompts the user for information, such as
criteria for retrieving records or a value to insert in a field. You can use stored parameter queries to accomplish
most of the database maintenance tasks described in the rest of this chapter.
In many situations, you'll want a user or another procedure to provide parameters you can use with your stored
queries and Recordset objects. Microsoft Jet provides the means to do this. First, create a stored query, specifying
which parameters the user needs to provide. When you open a Recordset against one of these queries, the
application opens a dialog box that prompts the user to enter a value, such as the criteria for a WHERE clause or
the field on which to sort the selected records.
The following example takes two strings that represent dates and creates a parameter query that returns all records
in the Orders table whose order date is between the two dates. It adds all values in the OrderID field in the query's
recordset to an array.
Function OrdersFromTo(strDateFrom As Variant, strDateTo As Variant, _
lngOrderIDs() As Long)
Dim dbs As Database, rstOrders As Recordset
Dim qdf As QueryDef, strSQL As String, intI As Integer
On Error GoTo ErrorHandler
Set dbs = CurrentDb
strSQL = "PARAMETERS [DateFrom] DateTime, [DateTo] DateTime; "
strSQL = strSQL & "SELECT * FROM Orders WHERE OrderDate BETWEEN "
strSQL = strSQL & "[DateFrom] AND [DateTo];"
' Create an unstored parameter query.
Set qdf = dbs.CreateQueryDef("", strSQL)
' Set the query parameters.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

130

qdf.Parameters("DateFrom") = strDateFrom
qdf.Parameters("DateTo") = strDateTo
' Open a forward-only snapshot on the query.
Set rstOrders = qdf.OpenRecordset(dbOpenSnapshot, dbForwardOnly)
' Load all the OrderIDs in the query into an array that the caller
' of the function can use.
intI = 1
While rstOrders.EOF = False
ReDim lngOrderIDs(1 To intI)
lngOrderIDs(intI) = rstOrders!OrderID
intI = intI + 1
rstOrders.MoveNext
Wend
ErrorHandler:
Select Case Err
Case 0
Exit Function
Case Else
MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR"
Exit Function
End Select
End Function
Making Bulk Changes
Many of the changes you may otherwise perform in a loop can be done more efficiently with an update or delete
query. The following example creates a QueryDef object to update the Employees table and then runs the query:
Dim dbs As Database, qdfChangeTitles As QueryDef
Set dbs = CurrentDb
Set qdfChangeTitles = dbs.CreateQueryDef("")
qdfChangeTitles.SQL = "UPDATE Employees SET Title = 'Account Executive' " _
& "WHERE Title = 'Sales Representative';"
qdfChangeTitles.Execute dbFailOnError
' Invoke query.
You can replace the entire SQL string in this example with a stored parameter query, in which case the procedure
would prompt the user for parameter values. The following example shows how the previous example may be
rewritten as a stored parameter query:
Dim dbs As Database, qdfChangeTitles As QueryDef
Dim strSQLUpdate As String, strOld As String
Dim strNew As String
Set dbs = CurrentDb
strSQLUpdate = "PARAMETERS [Old Title] Text, [New Title] Text; " _
& "UPDATE Employees SET Title = [New Title] WHERE Title = [Old Title];"
' Create the QueryDef object.
Set qdfChangeTitles = dbs.CreateQueryDef("", strSQLUpdate)
' Prompt for old title.
strOld = InputBox("Enter old job title")
' Prompt for new title.
strNew = InputBox("Enter new job title")
' Set parameters.
qdfChangeTitles.Parameters("Old Title") = strOld
qdfChangeTitles.Parameters("New Title") = strNew
' Invoke query.
qdfChangeTitles.Execute

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

131

Note A delete query is more efficient than code that loops through records looking for records to delete,
especially with databases created in Microsoft Access for Windows 95 or later.
Modifying an Existing Record
You can modify existing records in a table- or dynaset-type Recordset object by using the Edit and Update
methods.
To modify an existing record in a table- or dynaset-type Recordset object
1 Go to the record that you want to change.
2 Use the Edit method to prepare the current record for editing.
3 Make the necessary changes to the record.
4 Use the Update method to save the changes to the current record.
The following example illustrates how to change the job titles for all sales representatives in a table called
Employees:
Dim dbs As Database, rstEmployees As Recordset
Set dbs = CurrentDb
Set rstEmployees = dbs.OpenRecordset("Employees")
rstEmployees.MoveFirst
Do Until rstEmployees.EOF
If rstEmployees!Title = "Sales Representative" Then
rstEmployees.Edit
rstEmployees!Title = "Account Executive"
rstEmployees.Update
End If
rstEmployees.MoveNext
Loop
rstEmployees.Close
Important If you don't use the Edit method before you try to change a value in the current record, a run-time
error occurs. If you edit the current record and then move to another record or close the Recordset object without
first using the Update method, your changes are lost without warning. For example, omitting the Update method
from the preceding example results in no changes being made to the Employees table.
You can also terminate the Edit method and any pending transactions without saving changes by using the
CancelUpdate method. While you can terminate the Edit method just by moving off the current record, this isn't
practical when the current record is the first or last record in the Recordset, or is a new record. It's generally
simpler to use the CancelUpdate method.
Inconsistent Updates
Dynaset-type Recordset objects can be based on a multiple-table query containing tables with a one-to-many
relationship. For example, suppose you want to create a multiple-table query that combines fields from the Orders
and Order Details tables. Generally speaking, you can't change values in the Orders table because it's on the "one"
side of the relationship. Depending on your application, however, you may want to be able to make changes to the
Orders table. To make it possible to freely change the values on the "one" side of a one-to-many relationship, use
the dbInconsistent constant of the OpenRecordset method to create an inconsistent dynaset. For example:
Set rstTotalSales = dbs.OpenRecordset("Sales Totals" ,, _

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

132

dbInconsistent)
When you update an inconsistent dynaset, you can easily destroy the referential integrity of the data in the
dynaset. You must take care to understand how the data is related across the one-to-many relationship and to
update the values on both sides in a way that preserves data integrity.
The dbInconsistent constant is available only for dynaset-type Recordset objects. It's ignored for table-, snapshot-,
and forward-only-type Recordset objects, but no compile or run-time error is returned if the dbInconsistent
constant is used with those types of Recordset objects.
Even with an inconsistent Recordset, some fields may not be updatable. For example, you can't change the value
of an AutoNumber field, and a Recordset based on certain linked tables may not be updatable.
Deleting an Existing Record
You can delete an existing record in a table- or dynaset-type Recordset object by using the Delete method. You
can't delete records from a snapshot-type Recordset object. The following example deletes all the duplicate
records in the Shippers table:
Function DeleteDuplicateShippers() As Integer
Dim rstShippers As Recordset, strQuery As String, dbs As Database, strName As String
On Error GoTo ErrorHandler
strQuery = "SELECT * FROM Shippers ORDER BY CompanyName;"
Set dbs = CurrentDb
Set rstShippers = dbs.OpenRecordset(strQuery, dbOpenDynaset)
' If no records in Shippers table, exit.
If rstShippers.EOF Then Exit Function
strName = rstShippers![CompanyName]
rstShippers.MoveNext
Do Until rstShippers.EOF
If rstShippers![CompanyName] = strName Then
rstShippers.Delete
Else
strName = rstShippers![CompanyName]
End If
rstShippers.MoveNext
Loop
ErrorHandler:
Select Case Err
Case 0
' The constants conSuccess and conFailed are defined at
' the module level as public constants with Integer values of
' 0 and -32,737 respectively.
DeleteDuplicateShippers = conSuccess
Exit Function
Case Else
MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR"
DeleteDuplicateShippers = conFailed
Exit Function
End Select
End Function
When you use the Delete method, Microsoft Jet immediately deletes the current record without any warning or
prompting. Deleting a record doesn't automatically cause the next record to become the current record; to move to

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

133

the next record you must use the MoveNext method. However, keep in mind that after you've moved off the
deleted record, you cannot move back to it.
If you try to access a record after deleting it on a table-type Recordset, you'll get error 3167, "Record is deleted."
On a dynaset, you'll get error 3021, "No current record."
If you have a Recordset clone positioned at the deleted record and you try to read its value, you'll get error 3167
regardless of the type of Recordset object. Trying to use a bookmark to move to a deleted record will also result in
error 3167.
Adding a New Record
You can add a new record to a table- or dynaset-type Recordset object by using the AddNew method.
To add a new record to a table- or dynaset-type Recordset object
1 Use the AddNew method to create a new record you can edit.
2 Assign values to each of the record's fields.
3 Use the Update method to save the new record.
The following example adds a new record to a table-type Recordset called Shippers:
Dim dbs As Database, rstShippers As Recordset
Set dbs = CurrentDb
Set rstShippers = dbs.OpenRecordset("Shippers")
rstShippers.AddNew
rstShippers!CompanyName = "Global Parcel Service"
.
. ' Set remaining fields.
.
rstShippers.Update
rstShippers.Close
When you use the AddNew method, Microsoft Jet prepares a new, blank record and makes it the current record.
When you use the Update method to save the new record, the record that was current before you used the
AddNew method becomes the current record again.
The new record's position in the Recordset depends on whether you added the record to a dynaset- or a table-type
Recordset object. If you add a record to a dynaset-type Recordset, the new record appears at the end of the
Recordset, no matter how the Recordset is sorted. To force the new record to appear in its properly sorted
position, you can either use the Requery method or re-create the Recordset object.
If you add a record to a table-type Recordset, the record appears positioned according to the current index, or at
the end of the table if there is no current index. Because Microsoft Jet version 3.0 or later allows multiple users to
create new records in a table simultaneously, your record may not appear right at the end of the Recordset as it did
in previous versions of Microsoft Jet. Be sure to use the LastModified property rather than the MoveLast method
to move to the record you just added.
Important If you use the AddNew method to add a new record, and then move to another record or close the
Recordset object without first using the Update method, your changes are lost without warning. For example,
omitting the Update method from the preceding example results in no changes being made to the Shippers table.
Caching ODBC Data with a Recordset

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

134

You can use the dynaset-type Recordset to create a local cache for ODBC data. This lets you retrieve records in
batches instead of one at a time as each record is requested, and makes much better use of your server connection,
thus improving performance.
The CacheSize and CacheStart properties establish the size and starting offset (expressed as a bookmark) for the
cache. For example, you may set the CacheSize property to 100 records. Then, using the FillCache method, you
can retrieve sufficient records to fill the cache.
Tracking Recordset Changes
You may need to determine when the underlying TableDef object of a table-type Recordset was created, or the last
time it was modified. The DateCreated and LastUpdated properties, respectively, give you this information. Both
properties return the date stamp applied to the table by the machine on which the table resided at the time it was
stamped. These properties are only updated when the table's design changes; they aren't affected by changes to
records in the table.
Microsoft Jet Transactions
A transaction is a set of operations bundled together and treated as a single unit of work. The work in a transaction
must be completed as a whole; if any part of the transaction fails, the entire transaction fails. Transactions offer
the developer the ability to enforce data integrity. With multiple database operations bundled into a single unit that
must succeed or fail as a whole, the database can't reach an inconsistent state. Transactions are common to most
database management systems.
The most common example of transaction processing involves a bank's automated teller machine (ATM). The
processes of dispensing cash and then debiting the user's account are considered a logical unit of work and are
wrapped in a transaction: The cash isn't dispensed unless the system is also able to debit the account. By using a
transaction, the entire operation either succeeds or fails. This maintains the consistent state of the ATM database.
You should consider using transactions if you want to make sure that each operation in a group of operations is
successful before all operations are committed. Keep in mind that all transactions are invisible to other
transactions. That is, no transaction can see another transaction's updates to the database until the transaction is
committed.
Note The behavior of transactions with Microsoft Access databases differs from the behavior of ODBC data
sources, such as Microsoft SQL Server. For example, if a database is connected to a file server, and the file
server stops before a transaction has had time to commit its changes, then your database could be left in an
inconsistent state. If you require true transaction support with respect to durability, you should investigate the use
of a client/server architecture. For more information on client/server architecture, see Chapter 19, "Developing
Client/Server Applications."
Using Transactions in Your Applications
Microsoft Jet supports transactions through the DAO BeginTrans, CommitTrans, and Rollback methods of the
Workspace object.
The following example changes the job title of all sales representatives in the Employees table of the Northwind
sample database. After the BeginTrans method starts a transaction that isolates all of the changes made to the
Employees table, the CommitTrans method saves the changes. Notice that you can use the Rollback method to
undo changes that you saved with the Update method.
Sub ChangeTitle()
Dim dbsSales As Database
Dim rstEmp As Recordset
Dim wrkCurrent As Workspace

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

135

Set wrkCurrent = DBEngine.Workspaces(0)


Set dbsSales = OpenDatabase("Northwind.mdb")
Set rstEmp = dbsSales.OpenRecordset("Employees", dbOpenTable)
wrkCurrent.BeginTrans
Do Until rstEmp.EOF
If rstEmp!Title = "Sales Representative" Then
rstEmp.Edit
rstEmp!Title = "Sales Associate"
rstEmp.Update
End If
rstEmp.MoveNext
Loop
If MsgBox("Save all changes?", vbQuestion + vbYesNo) = vbYes Then
wrkCurrent.CommitTrans
Else
wrkCurrent.Rollback
End If
rstEmp.Close
dbsSales.Close
End Sub
When you use transactions, all databases and Recordset objects in the specified Workspace object are affected
transactions are global to the workspace, not to a specific database or Recordset. If you perform operations on
more than one database or within a workspace transaction, the Commit and Rollback methods affect all the
objects changed within that workspace during the transaction.
Note You can also use the BeginTrans, CommitTrans, and Rollback methods with the DBEngine object. In this
case, the transaction is applied to the default workspace, which is DBEngine.Workspaces(0).
Managing Transactions
Microsoft Jet uses sophisticated algorithms to enhance transaction performance, reliability, and usability. This
section discusses topics related to how the Jet database engine manages transactions.
Transaction Size
Transaction size is limited only by the amount of physical space on your disk drive. That is, Microsoft Jet can
store a quantity of transaction data as large as the amount of free space on your disk drive. If the available disk
space is exhausted during a transaction, a trappable run-time error occurs. Your code should check for this error
(number 2004) and react accordingly. If you try to commit the transaction after this error occurs, Microsoft Jet
will commit an indeterminate number of changes, possibly leaving the database in an inconsistent state. You
should usually roll back the transaction when this error occurs to ensure a consistent database state.
Nesting Transactions
You can have up to five levels of transactions active at any one time by nesting combinations of BeginTrans and
either CommitTrans or Rollback. If you nest transactions, you must make sure that you commit or roll back the
current transaction before trying to commit or roll back a transaction at a higher level of nesting.
If you want to have transactions with overlapping, nonnested scopes, you can open additional Workspace objects
and manage other transactions within those new workspaces.
When a Transaction is Rolled Back by the Jet Database Engine
If you close a Workspace object, any transactions within the scope of the workspace are automatically rolled back.
Microsoft Jet never automatically commits any transactions you have started. This behavior is also true of

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

136

database object variables. If you close a database object variable, any uncommitted transactions within the scope
of that database object variable are rolled back. You should be aware of this behavior when you write your code.
Never assume that the Jet database engine is going to commit your transaction for you.
Transactions on External Data Sources
Transactions aren't supported on external non-Microsoft Jet data sources, with the exception of ODBC data. For
example, if your database has linked FoxPro or dBASE tables, any transactions on those objects are ignored.
This means that the transaction doesn't fail or generate a run-time error, but it doesn't actually do anything either.
Note Microsoft Access version 2.0 databases are opened by Microsoft Access for Windows 95 and Microsoft
Access 97 as external installable ISAM databases. However, unlike other external data sources, the Jet database
engine does support transactions on Microsoft Access version 2.x databases.
To determine whether or not a Database or Recordset object supports transactions, you can check the value of its
Transactions property. A value of True indicates that the object does support transactions, and a value of False
indicates that the object doesn't support transactions.
Transactions and Performance
In previous versions of Microsoft Access, it was generally recommended that you use transactions as a
performance enhancement. Now all transactions for DAO add, update, and delete operations are performed
internally and automatically. In most situations, this automatic support provides your application with the best
possible performance. However, there may be situations where you want to fine-tune transaction behavior. You
can do this by creating and modifying various settings in the Windows Registry.
Extracting Data from a Record
After you've located a particular record or records, you may want to extract data to use in your application instead
of modifying the underlying source table.
Copying a Single Field
You can copy a single field of a record to a variable of the appropriate data type. The following example extracts
three fields from the first record in a Recordset object:
Dim dbs As Database, rstEmployees As Recordset
Dim strFirstName As String, strLastName As String
Dim strTitle As String
Set dbs = CurrentDb
Set rstEmployees = dbs.OpenRecordset("Employees")
rstEmployees.MoveFirst
strFirstName = rstEmployees!FirstName
strLastName = rstEmployees!LastName
strTitle = rstEmployees!Title
rstEmployees.Close
Copying Entire Records to an Array
To copy one or more entire records, you can create a two-dimensional array and copy records one at a time. You
increment the first subscript for each field and the second subscript for each record.
A fast way to do this is with the GetRows method. The GetRows method returns a two-dimensional array. The
first subscript identifies the field and the second identifies the row number, as follows:

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

137

varRecords(intField, intRecord)
The following example uses an SQL statement to retrieve three fields from a table called Employees into a
Recordset object. It then uses the GetRows method to retrieve the first three records of the Recordset, and it stores
the selected records in a two-dimensional array. It then prints each record, one field at a time, by using the two
array indexes to select specific fields and records.
To clearly illustrate how the array indexes are used, the following example uses a separate statement to identify
and print each field of each record. In practice, it would be more reliable to use two loops, one nested in the other,
and to provide integer variables for the indexes that step through both dimensions of the array.
Sub GetRowsTest()
Dim dbs As Database
Dim rstEmployees As Recordset
Dim varRecords As Variant
Dim intNumReturned As Integer
Dim intNumColumns As Integer
Dim intColumn As Integer, intRow As Integer
Set dbs = CurrentDb
Set rstEmployees = dbs.OpenRecordset("SELECT FirstName, LastName, Title " _
& "FROM Employees", dbOpenSnapshot)
varRecords = rstEmployees.GetRows(3)
intNumReturned = UBound(varRecords, 2) + 1
intNumColumns = UBound(varRecords, 1) + 1
For intRow = 0 To intNumReturned - 1
For intColumn = 0 To intNumColumns - 1
Debug.Print varRecords(intColumn, intRow)
Next intColumn
Next intRow
rstEmployees.Close
End Sub
You can use subsequent calls to the GetRows method if more records are available. Because the array is filled as
soon as you call the GetRows method, you can see why this approach is much faster than copying one field at a
time.
Notice also that you don't have to declare the Variant as an array, because this is done automatically when the
GetRows method returns records. This enables you to use fixed-length array dimensions without knowing how
many records or fields will be returned, instead of using variable-length dimensions that take up more memory.
If you're trying to retrieve all the rows by using multiple GetRows calls, use the EOF property to be sure that
you're at the end of the Recordset. The GetRows method may return fewer rows than you request. If you request
more that the remaining number of rows in a Recordset, for example, the GetRows method only returns the rows
that remain. Similarly, if it can't retrieve a row in the range requested, it doesn't return that row. For example, if
the fifth record cannot be retrieved in a group of ten records that you're trying to retrieve, the GetRows method
returns four records and leaves the current record position on the record that caused a problem and doesn't
generate a run-time error. This situation may occur if a record in a dynaset was deleted by another user. If it
returns fewer records than the number requested and you're not at the end of the file, you need to read each field
in the current record to determine what error the GetRows method encountered.
Because the GetRows method always returns all the fields in the Recordset object, you may want to create a query
that returns just the fields that you need. This is especially important for OLE Object and Memo fields.
Using Field Objects

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

138

The default collection of a Recordset object is its Fields collection. This collection includes a single Field object
that corresponds to each field (or column) in the Recordset. Each Field object has a set of properties that uniquely
identifies the field name, data type, and so on, as well as the value of the field in the current record. You use the
Field objects in a Recordset object to read and set values for the fields in the current record of the Recordset
object.
You manipulate a field by using a Field object and its methods and properties. For example, you can:
Use the OrdinalPosition property to get or set the position of a Field object relative to other fields in a Fields
collection.
Use the FieldSize property, the GetChunk method, or the AppendChunk method to get or set a value in an OLE
Object or Memo field of a Recordset object.
Read or set the DAO Value property of a Recordset object.
Read or set the DAO AllowZeroLength, Required, ValidationRule, ValidationText, or ValidateOnSet property
setting to find or specify validation conditions.
Read the SourceField and SourceTable property settings to determine the original source of the data.
Referring to Field Objects
You can identify a Field object by its DAO Name property, which corresponds to the column name in the table
from which the data in the field was retrieved. The Fields collection is the default collection of a Recordset object.
Therefore, you can refer to the LastName field in the rstEmployees Recordset in any of the following ways:
rstEmployees.Fields("LastName")
rstEmployees!LastName
rstEmployees![LastName]
When using the ! operator, you must include brackets around a field name when it contains spaces. For example,
the statement:
strEmp = rstEmployees!Last Name
will not compile, but the statement:
strEmp = rstEmployees![Last Name]
will compile with no problems.
Within the Fields collection, each Field object can also be identified by its index:
rstEmployees.Fields(0)
The index enables you to walk through the collection in a loop, replacing the index with a variable that is
incremented with each pass through the loop. Objects in a collection are numbered starting with zero, so the first
Field object in the Fields collection is number 0, the second is 1, and so on. The field order is determined by the
underlying table. Fields are usually numbered in the order retrieved when the Recordset object is opened. One
drawback to this approach is that you can't be certain which field will be referred to, because the underlying table
structure may change, fields may be added or deleted, and so on.
To help you determine the order of fields in a Fields collection, the Field object supports the OrdinalPosition
property, which you can use to get or set a field's position relative to other fields in the collection. You can set the

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

139

OrdinalPosition property to any positive integer to change the field order when data is displayed in a form, copied
to an array or a Microsoft Excel worksheet, and so on.
When writing code that refers to fields within a loop, it's more efficient to refer to Field objects rather than to refer
to fields by their names. The following example shows a more efficient way of writing the ChangeTitle procedure
discussed earlier in this chapter. Instead of referring to the Title field as rstEmp!Title, it refers to the field by its
object variable, which doesn't require that the field be looked up in the Fields collection every time it's referred to.
Sub ChangeTitle()
Dim dbsSales As Database
Dim rstEmp As Recordset, fldTitle As Field
Dim wrkCurrent As Workspace
Set wrkCurrent = DBEngine.Workspaces(0)
Set dbsSales = OpenDatabase("Northwind.mdb")
Set rstEmp = dbsSales.OpenRecordset("Employees", dbOpenTable)
Set fldTitle = rstEmp.Fields("Title")
wrkCurrent.BeginTrans
Do Until rstEmp.EOF
If fldTitle = "Sales Representative" Then
rstEmp.Edit
fldTitle = "Sales Associate"
rstEmp.Update
End If
rstEmp.MoveNext
Loop
If MsgBox("Save all changes?", vbQuestion + vbYesNo) = vbYes Then
wrkCurrent.CommitTrans
Else
wrkCurrent.Rollback
End If
rstEmp.Close
dbsSales.Close
End Sub
Field Data Types
For a Field object on a Recordset, the Type property is read-only. However, you must be aware of the field type
when copying data to or from a field in code or a "Type mismatch" error may occur. For example, you cannot
copy Text data to an Integer field.
The Type property of a Field object on a Recordset is determined by the underlying table from which the record
was retrieved. If you create the table and its fields by using DAO data-definition language (DDL) statements, you
can easily determine the data type of the table's fields.
If you're accessing external data through an installable ISAM driver, the data types within external tables may be
different from those defined within Microsoft Jet. The installable ISAM driver for the external data source
converts external data types into their closest equivalent DAO data type.
The GUID Data Type
The GUID data type is used to store a globally unique identifier, a unique string of digits that identifies OLE
objects, Microsoft SQL Server remote procedure calls, and other entities that need a unique reference
identification.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

140

Note The GUID data type is also used in the Database object's ReplicaID property to identify a replica. For
information on replicas, see Chapter 20, "Using Replication in Your Application."
The Text Data Type
For Field objects declared as type Text, you must set the Size property, which indicates the length of the longest
string that can be stored in the field. All other types of Field objects have their Size property set automatically.
The Currency Data Type
If you need to store monetary values, use fields of type Currency. Don't use any of the number data types (such as
Single) for currency values, because numbers to the right of the decimal may be rounded during calculations. The
Currency data type always maintains a fixed number of digits to the right of the decimal.
The Long Data Type
In some tables, you'll want to store a series of sequential numbers to uniquely identify records. For example, you
may want to start customer order records at order number 1 and begin counting upward.
Microsoft Access can automatically insert unique numbers in a field, saving your application the effort of
generating unique identifiers to be used within a primary key field. To take advantage of this capability, define a
field with the Long data type and set the dbAutoIncrField constant in the Field object's Attributes property. Autoincrementing fields start at 1 and increment sequentially. Fields of this type are also referred to as AutoNumber
fields.
If you want to establish a primary key/foreign key relationship between two tables by using an AutoNumber field,
make sure that the foreign key field is also defined as Long.
You can also set the DAO DefaultValue property of a Field object on a TableDef object to a special value called
GenUniqueId( ). This causes a random number to be assigned to this field whenever a new record is added or
created. The field's Type property must be Long.
Note A Field object's data type is read/write before the field is appended to a table's Fields collection, and readonly after it's appended.
The OLE Object and Memo Data Types
OLE Object and Memo fields are collectively referred to as large value fields because they are typically much
larger than fields of other data types. OLE Object fields consist of binary data up to 1.2 gigabytes in size. This
type is used to store pictures, files, or other raw binary data. Memo fields are used to store lengthy text and
numbers, such as comments or explanations. The size of a Memo field is limited by the maximum size of the
database.
Records within a Recordset object must fit on the 2K data pages supported by the Microsoft Jet database engine.
Each Field object you include in the table definition counts toward this 2K total, including OLE Object and
Memo fields. However, the amount stored for OLE Object and Memo fields is only 14 bytes per non-null field,
and only 1 byte for null fields. The 14 bytes is used to store a pointer to the actual data for these fields, which is
located on additional 2K pages. The amount of data committed to each text field isn't set until you actually store
data in the field. You can overcommit a data page by defining more text fields than there would be room for, but
no more than about 2K of actual data can be stored in a record. For example, you can define fifteen 250-byte text
fields in a record, but the total number of characters stored must be less than 2K.
When you query tables containing large value fields, don't include those fields in the field list unless you need
them, because returning large value fields takes time. Also, be aware that you can't index large value fields.

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

141

A snapshot- or forward-only-type Recordset object opened against large value fields in an .mdb file doesn't
actually contain that data. Instead, the snapshot maintains references to the data in the original tables, the same
way a dynaset refers to all data.
Handling Large Value Data
Sometimes you'll need to read or copy data from a large value field where you don't have sufficient memory to
copy the entire field in a single statement. Instead, you have to break up the data into smaller units, or chunks, that
will fit available memory. The FieldSize property tells you how large the field is, measured in bytes. Then you can
use the GetChunk method to copy a specific number of bytes to a buffer, and use the AppendChunk method to
copy the buffer to the final location. You then continue using GetChunk and AppendChunk until the entire field is
copied.
Reading and Writing Data
When you read or write data to a field, you're actually reading or setting the DAO Value property of a Field
object. The DAO Value property is the default property of a Field object. Therefore, you can set the DAO Value
property of the LastName field in the rstEmployees Recordset in any of the following ways:
rstEmployees!LastName.Value = strName
rstEmployees!LastName = strName
rstEmployees![LastName] = strName
Write Permission
The tables underlying a Recordset object may not permit you to modify data, even though the Recordset is of type
dynaset or table, which are usually updatable. Check the Updatable property of the Recordset to determine
whether its data can be changed. If the property is True, the Recordset object can be updated.
Individual fields within an updatable Recordset object may not be updatable, and trying to write to these fields
generates a run-time error. To determine whether a given field is updatable, check the DataUpdatable property of
the corresponding Field object in the Fields collection of the Recordset. The following example returns True if all
fields in the dynaset created by strQuery are updatable and returns False otherwise.
Function blnUpdatable(strQuery As String) As Boolean
Dim dbs As Database, rstDynaset As Recordset, intI As Integer
On Error GoTo ErrorHandler
' Initialize the function's return value to True.
blnUpdatable = True
Set dbs = CurrentDb
Set rstDynaset = dbs.OpenRecordset(strQuery, dbOpenDynaset)
' If the entire dynaset isn't updatable, return False.
If rstDynaset.Updatable = False Then
blnUpdatable = False
Else
' If the dynaset is updatable, check if all fields in the dynaset are updatable.
' If one of the fields isn't updatable, return False.
For intI = 0 To rstDynaset.Fields.Count - 1
If rstDynaset.Fields(intI).DataUpdatable = False Then
blnUpdatable = False
Exit For
End If
Next intI
End If
ErrorHandler:
Select Case Err

COPYRIGHT INFOTECH 1999

INFOTECH TRAINING CENTRE 229066 , 214824 , 246006

142

Case 0
Exit Function
Case Else
MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR"
Exit Function
End Select
End Function
Criteria
Any single field can impose a number of criteria on data in that field when records are added or updated. These
criteria are defined by a handful of properties. The DAO AllowZeroLength property on a Text or Memo field
indicates whether or not the field will accept a zero-length string (""). The DAO Required property indicates
whether or not some value must be entered in the field, or if it instead can accept a Null value. For a Field object
on a Recordset, these properties are read-only; their state is determined by the underlying table.
Field-Level Data Validation
Validation is the process of determining whether data entered into a field's DAO Value property is within an
acceptable range. A Field object on a Recordset may have the DAO ValidationRule and ValidationText properties
set. The DAO ValidationRule property is simply a criteria expression, similar to the criteria of an SQL WHERE
clause, without the WHERE keyword. The DAO ValidationText property is a string that Microsoft Access
displays in an error message if you try to enter data in the field that's outside the limits of the DAO ValidationRule
property. If you're using DAO in your code, then you can use the DAO ValidationText for a message that you
want to display to the user.
Note The DAO ValidationRule and ValidationText properties also exist at the Recordset level. These are readonly properties, reflecting the table-level validation scheme established on the table from which the current record
is retrieved.
A Field object on a Recordset also features the ValidateOnSet property. When the ValidateOnSet property is set to
True, Microsoft Access checks validation as soon as the field's DAO Value property is set. When it's set to False
(the default), Microsoft Access checks validation only when the completed record is updated. For example, if
you're adding data to a record that contains a large Memo or OLE Object field and that has the DAO
ValidationRule property set, you should determine whether the new data violates the validation rule before trying
to write the data you should write the data when the field value is set. To do so, set the ValidateOnSet property to
True. If you wait to check validation until the entire record is written to disk, you may waste time trying to write
an invalid record to disk.
Tracing the Origin of Dynaset Fields
A dynaset-type Recordset object can include records from more than one source table. Also, within a single
record, fields from different tables can be joined into new records. Sometimes it's useful to know the table from
which a field originated. The SourceTable property of a Field object returns the name of the table from which the
field's current data was retrieved.
Within a query, a field can be renamed for display purposes. For example, in an SQL SELECT query, the AS
operator in the select field list can create an alias for one of the returned fields. In a Recordset based on an SQL
query, a field that has been aliased is represented by a Field object whose DAO Name property reflects the alias,
not the original field name. To find out the original field name, check the Field object's SourceField property.

COPYRIGHT INFOTECH 1999

Das könnte Ihnen auch gefallen