Werbung
Werbung
Microsoft Access Techniques
Form Stuff
Types of Forms
There are several types of forms based on their content, operation, and use:
Type of Form How Defined/Opened Use Interaction with Code
window dialog box message box
Access form, opened manually or in code; several may be open at the same time, user can make manually each active in turn defined as an Access form, opened in code with
DoCmd.OpenForm with the acDialog window mode parameter; form stays on top
opened in code with the
MsgBox statement, parameters control contents; form stays on top input box opened in code with the
InputBox statement, parameters control contents; form stays on top file dialog box opened in code with the
FileDialog object (in the
Microsoft Office 11.0
Object Library); form stays on top to present data and processing controls to present settings to control a future process so the user can change the settings to present brief messages and/or to have user choose between two or three alternatives to capture text for future use to select a file for opening or saving, or to select a path for future processing form and window events can run code; code in a separate module can interact with this form by opening it, changing its properties and data values, and/or closing it all processing stops until user closes the box all processing stops until user closes the box all processing stops until user closes the box all processing stops until user closes the box
Application Forms
A database application typically opens with a main form that I often call “Application
Central.” It has form controls that operate the various transactions. I prefer to use command buttons for these controls and label them as clearly as possible; that way the form can inform the user just what can be done.
1 No other form can be made active while a dialog box is open.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 42 of 119
Microsoft Access Techniques
This main form should be opened automatically when the database opens. Do this with menu Tools, Startup which opens the “Startup” dialog box. Provide an application title and name the startup form.
The startup form is the place to locate code that initializes global variables.
Form-Process Interaction
Typical Access applications employ forms to present data and provide mechanisms with which the user can run standalone processes. There are a number of situations where a process might interact with a form. Some of these are discussed in later sections:
change values of form properties (see page 47)
change values of form text controls
use a popup form to control data on a main form (see page 77)
use a form to accept selection criteria and pass it to another form or report (see page 78)
Other uses include:
present progress information on a separate form (open a form, keep on processing, write to
that form intermittently, then close it) (see page Error! Bookmark not defined.)
present short messages with a message box (see page Error! Bookmark not defined.)
capture limited text with an input box (see page Error! Bookmark not defined.)
use a File Dialog box to capture path and filename (see page Error! Bookmark not defined.)
There are some form properties which affect how a process can interact with the form:
PopUp:
A form that stays on top of other windows and the formʹs toolbar isnʹt available (so you cannot change the view). A pop‐up form can be modal or modeless. The PopUp property can be set only in form Design view. The PopUp property by itself only affects how a process can close the form: the close code should name the form (and not assume it is the active one).
Modal:
A form that requires the user to take some action before the focus can switch to another form. Dialog boxes are usually modal. When a form opens as modal, you must close it before you can move the focus to another Access object. When the form is modal, you canʹt switch to
Datasheet view from Form view, although you can switch to Design view and then to Datasheet view. To disable menus and toolbars in addition to other forms, set both the Modal and PopUp
properties to Yes.
When a form is opened in code, you use the DoCmd.OpenForm statement described on page
Error! Bookmark not defined.. It has a window mode parameter which affects how the program can interact with the form. When a form is opened with window mode = acDialog, the code stops execution until the user closes the form. When the form is opened with window mode = acWindowNormal (the default setting), then the code continues execution while the form is open.
This is true for modal and modeless forms. If you use a modeless form that is not popup for progress information, be sure that the code closes that form by name, not just the active form (as
the user can make another form active while the process is running).
[I wrote a note in the margin: execution resumes when second form closes. – pause – But now I don’t know where to put the note.]
While a form is open and code is running, the code can change the contents of the form (such as text controls but also control properties), and the changes are effective immediately.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 43 of 119
Microsoft Access Techniques
Useful Form Properties
The following images reflect all the form properties.
Useful properties:
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 44 of 119
Microsoft Access Techniques
Category
Format
Data
Other
Property
Caption
Record Selectors
Navigation Buttons
Min Max Buttons
Close Button
Control Box
Record Source
Pop Up
Modal
Cycle
Shortcut Menu
Shortcut Menu Bar
Menu Bar
Effect
Appears in title bar.
Column at left of form with icons to indicate editing status.
Built‐in record navigation buttons at bottom of form.
If either or both buttons in title bar are enabled (if disabled, they are greyed out).
If X button in title bar is enabled (if disabled, it is greyed out). If not you must have a custom control to close the form. A form can also be closed with [Ctrl+F4].
If there is a Control menu, which is accessed via an icon at the left side of the title bar. If there is no Control menu, the Control icon, Min Max buttons, and Close buttons do not appear in the title bar. The Control menu has items:
Move, Size, Minimize, and Maximize.
Identifies the table or query whose data is presented in the form.
If form remains on top until closed. A pop‐up form stays on top of other open forms, even when another form is active?
If form must be closed before you can move the focus to another object.
What happens when you press [Tab] and the focus is in the last control on a bound form. Can be All Records
(moves the focus to the first control in the tab order in the next record), Current Record (moves the focus to the first control in the tab order in the same record), or
Current Page (moves the focus back to the first control in the tab order on the page).
If form or any of its controls has a context menu
(accessed with a right‐click).
Name of the context (shortcut) menu for the form and all of its controls. A shortcut menu can be any command bar whose Type property is set to Popup. Leave blank for the built‐in menu. (Create a context menu with menu Tools,
Customize then tab Toolbars.) Assign a custom menu to a control with its Shortcut Menu Bar property.
Name of the menu bar to appear when the form has the focus. Can be a custom menu bar or the built‐in bar
(leave blank).
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 45 of 119
Microsoft Access Techniques
Change Form Properties
After the form is created, you can manually change its properties. Use menu Edit, Select Form, then View, Properties to open the Properties window. Some properties can be changed in code while the form is open.
Form Control Scrollbar
A text box control can be defined with a vertical scrollbar using the ScrollBars = Vertical property.
Be aware that the scrollbar will only appear if (1) the text box has the focus and (2) all margins = zero. If you want the scrollable text box to appear with the scrollbar visible when the form opens,
then set the text box properties as follows:
TabStop = Yes
TabIndex = 0.
Refresh Contents of Form
Me.Refresh works only if the form has a RecordSource.
This seems to require both a Refresh and a Requery.
In the form’s class module, add this code after the code that changes the contents of the table(s) that populate the form:
Me.Refresh
A control button can be used with an event procedure (applies only to current form):
DoCmd.RunCommand acCmdRefresh
DoCmd.Requery
When form B is used to edit records on form A so that they no longer qualify for form A, its
AfterUpdate event procedure can refresh the contents of form A:
Forms![UnassignedExceptions].Requery
When a record is deleted from a form, the contents of the form are automatically refreshed.
The Recalc method of a form immediately updates all calculated controls on the form. (A calculated control is one whose data source is an expression.) The method could be used in an AfterUpdate event procedure.
Me.Recalc
When form B adds a record displayed in a subform on Form A, and formB is opened as a popup modal window from formA:
RunCommand acCmdSave
DoCmd.Close
RunCommand acCmdRefresh
' saves new record
' closes formB, leaving formA with focus
' refreshes display on formA
Requery‐refresh form with many list boxes and/or combo boxes.
Public Function fRequeryRowSources(frm As Form)
' Requery all combo and list boxes on the argument form
' and on any subforms it may contain, and any of their
' subforms, and so on.
Dim ctl As Control
With frm
For Each ctl In .Controls
Select Case ctl.ControlType
Case acListBox, acComboBox ctl.Requery
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 46 of 119
Microsoft Access Techniques
Case acSubform
If Len(ctl.SourceObject) > 0 Then fRequeryRowSources ctl.Form
End If
End Select
Next ctl
End With
End Function
Refer to Form Controls
See also Tab Control on page 63.
Refer to current open form:
Me
Refer to a named open form:
Forms!formname
Forms![form name]
Forms(strFormName)
Refer to control on current open form:
Me.fieldA
Refer to a control on a open form:
Forms!formname!controlname
Refer to a field in a subform (a form embedded by a subform control)
Forms!formname!ctlSubForm!fieldA -- faster
Forms!formname!ctlSubForm.Form!Controls.fieldA -- slower
Forms!formname!ctlSubForm.Form.controlA
Me.subformA!listboxcontrolB.Column(2) -- value of the third column
Refer to the original value of a field on current open form:
Me.fieldA.OldValue
Refer to the current value of a field on current open form:
Me.fieldA.Value
Is Form Open
Access 2000: The IsLoaded property of the AccessObject object. Example:
Dim objCP as Object
Set objCP = Application.CurrentProject
If objCP.AllForms!formname.IsLoaded Then . . .
or simply
If CurrentProject.AllForms(“Main”).IsLoaded = True Then . . .
Multiple Instances of Forms
When you open a form, you are creating an instance of that form. It is possible to open a form
more than once, thereby creating multiple instances.
A form opened by a user from the Database window or DoCmd.OpenForm in code is called the
default form, because the object instance is unique to the application environment. Only one instance of a specific form can be open at a given time if any of these invocation methods are used:
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 47 of 119
Microsoft Access Techniques
Opening the form from the Database window
Opening the form with DoCmd.OpenForm
Using CreateForm to build a new form
Using a Form type variable to refer to the instance
In order to have multiple instances a form must have a class module. And to keep the form alive until the user closes it, at least one variable that references it must be defined in the
Declarations area of a form that will outlive it, say the Main form. Form instances perish
when the variables referencing them go out of scope. See Object References page 4. See also
http://support.microsoft.com/kb/198465 .
Create the form instance using a module‐level variable. Adding the ʺForm_ʺ prefix to the form class name is a VBA “feature” that tells the compiler you are trying to instance a form object
.
Dim frmA As Form_frmIceCreamPopup
Set frmA = New Form_frmIceCreamPopup frmA.Visible = True
' best if in the Declarations area
You can use the ʺForm_ʺ prefix to open a form and refer to a method or property of that form or one of its controls in one step. You refer to the formʹs class module as shown in the following example:
Form_Employees.Visible = True
Form_Employees.Caption = "New Employees"
When you run this code, Microsoft Access opens the Employees form in Form view if itʹs not already open and sets the formʹs caption to ʺNew Employees.ʺ The form isnʹt visible until you explicitly set its Visible property to True (–1). When the procedure that calls this code has finished executing, this instance of the form is destroyed, i.e., the form is closed. But another source says that because there is no attached object variable, it does not go out of scope when the procedure ends so you must close the form itself. Furthermore should there be more than one such instance, it is harder to address each one directly.
If you will have several instances of the same form, you’ll need a way to address each easily.
You could create an array of form classes. Be aware that an array of objects, which holds the actual data, is resource intensive and should be used wisely.
Private mfrmMultiInst(3) As Form_frmMultiInst ʹ in form’s Declaration section
. . .
Private Sub cmdOpen_Click()
' Purpose: Open multiple form instances
Dim iintLoop As Integer
For iintLoop = 0 To 3
Set mfrmMultiInst(iintLoop) = New Form_frmMultiInst mfrmMultiInst(iintLoop).Caption = "Instance " & CStr(iintLoop) mfrmMultiInst(iintLoop).Visible = True
Next iintLoop
End Sub
You can use a Collection object to group several related objects together into a container where they can be subjected to bulk operations. The only information the Collection object holds is references (pointers). Working with a Collection object is easy and involves the following three methods:
1 The syntax for addressing form class objects with Form_formname was introduced in Access 95.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 48 of 119
Microsoft Access Techniques
•
Add method inserts an object reference (called a member) into the collection. It has parameters: Item, Key, and Before or After. Item provides the object reference. Key provides a unique string value to use when referring to the item, a “primary key”; without a Key value, an element must be retrieved by its position in the collection.
•
Item method returns an object reference from a specified location in the collection. The location is referenced by either a positional index number or a unique identifier called a key.
•
Remove method removes a reference from the collection, either by positional index or key.
Public colForms As New Collection ' in a Declaration section colForms.Add After:=3, Item:=rstWidget, Key:="Widget1" colForms.Add Forms!frmWidget ' a specific form is added colForms.Add Item:=Forms!frmWidget!txtWidgetName ' a form control is added colForms.Add Form_frmWidget!subComponent.Form.txtSupplier ' a subform control colForms.Add Me.RecordSetClone ' a recordset is added
Widget = colForms.Item("WidgetName") ' retrieve a named item ctlWidget = colForms("WidgetName") ' retrieve a named item colForms.Remove(7) ' remove item by position colForms.Remove("WidgetName") ' remove named item
Another way to open an instance of the form is as follows:
Dim frm as Form
Set frm = Form_Employees
Approach to Editing a Table With a Form
The form can present a single record or multiple records at a time. Data is presented in text controls, most of which allow direct editing.
Style of Form
columnar tabular
Records
one record at a time several records at a time
Description
you can layout the field controls any way you prefer each record’s field controls
(text boxes) are arranged to suit on one or more lines, but the field labels are presented as column headings
Record Navigation Comments
needed; the built‐in navigation controls may be used text boxes for long fields can have vertical scroll bar
(only visible when control has the focus) can use built‐in navigation controls and/or vertical scroll bar column widths cannot be changed by user; all fields in fixed‐height text boxes, no vertical scroll bar for text longer than box, however you can move cursor through all text; you can style text
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 49 of 119
Microsoft Access Techniques
Style of Form
datasheet embedded datasheet list box multi‐page tabbed
Records
several records at a time several records at a time several records at a time one record at a time one record at a time
Description
grid, the same as if you had opened the table/query like tabular grid like columnar
Record Navigation
uses built‐in navigation controls can use built‐in navigation controls and/or vertical scroll bar like columnar like columnar; vertical scroll bar provides access to pages like columnar
Creating a multi‐page form requires a number of particular steps:
Comments
why bother with a form? for the form events? embedded in subform; otherwise like tabular vertical scroll bar record id must be included; column widths cannot be changed by user; text that extends beyond column width is invisible; memo fields cannot be included; you cannot edit data in the list box, however you can provide a double‐ click event which opens a second form for editing that single record
(requires the source have a single field as unique key—only one column can be bound) additional pages are created by adding a page break; [Page
Down] and [Page
Up] move between pages; not appealing pages created for tabs on tab control
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 50 of 119
Microsoft Access Techniques
also set Cycle property is set to Current Page.
The limitation of the list box to present memo fields can be overcome if you use a query that selects only a fixed number of characters from the memo field, such as with Left(memofield, 40).
You use navigation controls to access the other records in the table.
The simplest approach is to present the records in a datasheet format and use the default navigation controls. This works for a simple table with no foreign keys and no child table.
If you take this approach, be sure to provide directions for using standard Access navigation buttons, how to save a record, and how to undo changes.
One or more requirements may necessitate using control buttons to add, delete, and change individual records. Possible requirements: different security for different users, fool‐ proofing. The actual data can be displayed in a pseudo datasheet format, all protected.
Actions can be initiated by control buttons and double‐clicking: An existing record can be selected for editing by double‐clicking its key field; this opens a specialized form in Edit mode (form property Allow Edits = Yes, all others = No). A new record can be added by clicking an Add button; this opens a specialized form in Add mode (form property Allow
Additions = Yes, all others = No). An existing record can be deleted by clicking a Delete button; this runs an event procedure class module that uses the DoCmd.DoMenuItem statement (built with a wizard) or the DoCmd.RunCommand acCmdDeleteRecord (more readable). See “Setting Field Value” and “Refresh Contents of Form” for details. Alternative to using a specialized form is to use a basic form tailored with VBA code, e.g,
DoCmd.OpenForm “Portal”, , , , acFormAdd
Typically you use the DoCmd.RunCommand method which has one parameter, the acCommand constant. Frequently used values:
Constant Meaning
acCmdDeleteRecord acCmdFind acCmdSaveRecord acCmdUndo delete current record presents Find dialog box save current record
undo changes to current record;
BETTER: Me.Undo ** see page 77
acCmdRecordsGoToFirst acCmdRecordsGoToLast acCmdRecordsGoToNext acCmdRecordsGoToPrevious
An input mask can be used to guide the user. But the error message refers to the input mask itself, which is arcane. Best avoided.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 51 of 119
Microsoft Access Techniques
When the table being edited has a foreign key and you want both the id code and the text value from the lookup table to appear on the form, use a list box with a row source = query/table showing all values in the lookup table, and indicate which list box column is to be bound to the main table field (the id field).
You may want to use two forms A and B. On formA use a list box to show all the table records; use a query to include foreign key text values. User selects an Add button to add a record, a Delete button to delete a record. To edit a record, user double‐clicks its row. The
Add button opens formB so that only one record is added, after which it requeries formA’s list box and closes itself. The edit action opens formA so that only the selected record is presented; after the record is updated, the form requeries formA’s list box and closes itself.
The delete action confirms the delete, then builds a delete query and runs it. The sample code applies to the edit action; be sure to block additions and deletions.
DoCmd.OpenForm “formname”, acNormal, , strWHERE
Forms!formname.AllowAdditions = False
Forms!formname.AllowDeletions = False
Be sure the variable strWHERE is created properly: Dim in the current Sub or Private/Public in the Declarations. If not, the argument is no‐op.
Forms for Parent-Child Tables
A simple way to design a form to present data on parent and child tables is to use a form for the parent table fields with a subform for the child table fields.The parent form presents a single record with built‐in record navigation. The subform contains a datasheet, which automatically
has the built‐in record navigation controls.
If you want the parent form to present a total of the amounts in the subform datasheet, this is done as follows:
1. In the subform put a textbox in the footer with source = Sum(AmountCharged). Call this textbox txtSumCharged.
2. In the parent form put a textbox with source = [subService].[Form]![txtSumCharged].
“subService” is the name of the control that contains the subform. Voila!
This approach is easily extended to a family of three tables, each in a 1:M parent‐child
relationship with each other:
A has a 1:M relationship with B
B has a 1:M relationship with C
Use a parent form for A, a single‐record subform for B, and a datasheet subform for C. Embed C’s subform in B’s subform, then embed B’s subform in A’s form. Let each have built‐in record navigation.
Navigation on Single Record Form
Access has built‐in navigation controls that are consistent to use BUT require training. They are located at the bottom of the datasheet or form. The controls can be organized into three groups:
record navigation
start new record
record x of y
where y = total record count, x = current record number
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 52 of 119
Microsoft Access Techniques
You can replace these with custom controls.
To disable the built‐in navigation controls, set form properties:
NavigationButtons = False
Sample code for [New]:
DoCmd.GoToRecord , , acFirst
DoCmd.GoToRecord , , acNewRec
Sample code for [Next]:
DoCmd.GoToRecord , , acNext
It may be that you have to check to be sure you are not at the end when this code is run.
Sample code for [Previous]:
DoCmd.GoToRecord , , acPrevious
It may be that you have to check to be sure you are not at the end when this code is run.
Sample code for [First]:
DoCmd.GoToRecord , , acFirst
It may be that you have to check to be sure you are not at the end when this code is run.
Sample code for [Last]:
DoCmd.GoToRecord , , acLast
It may be that you have to check to be sure you are not at the end when this code is run.
Sample code for total record count:
SELECT Count(*) FROM . . .
Return current record number:
Me.CurrentRecord but only works on the recordset (table or query) that the form is based on. It may not reflect a
later sort. [needs work]
You likely do not want the navigation controls enabled all the time. For instance, if there is only one record then only the New control should be enabled. You especially want to avoid running the Next code when you are at the end, because doing so will cause a run‐time error.
Recognizing when you are at the beginning or end of the form’s record source is possible with the Recordset property of the Form object. It returns an ADO or DAO Recordset object which has the necessary properties—BOF (beginning of file) and EOF (end of file)—which can be tested.
If Me.Recordset.EOF Then . . . Else . . . End If
If Me.Recordset.BOF Then . . . Else . . . End If
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 53 of 119
Microsoft Access Techniques
The following table summarizes the various conditions you can expect to encounter and when to enable or disable each control. In each case the New control can be enabled.
Current Position First Previous Next Last
empty recordset disabled
1st record of 1 disabled disabled disabled disabled disabled disabled disabled
1st record of 2 disabled disabled enabled enabled
2nd record of 5 enabled enabled enabled enabled
5th record of 5 enabled enabled disabled disabled
In the following discussion be careful how you refer to the form object. If code is in the form module, “Me” works. Otherwise pass the form name to a standard module procedure which will
use “Forms(strForm)”.
The controls can be enabled and disabled by various events:
Event Action Sample Code
form.OFalsepen
form.cmdFirst if record count < 2, then disable all controls go to first record disable First and Previous,
Enabled Next and Last
form.cmdPrevious go to previous record
Enabled Next and Last if at beginning of recordset, then disable First and
Previous, else Enabled them
NOTE: Use of BOF does not work in standard module.
There use form.CurrentRecord
= 1.
If DCount("*", "table or query
name
") < 2 Then
form
.cmdNext.Enabledd = False
form
.cmdLast.Enabledd = False
form
.cmdFirst.Enabledd = False
form
.cmdPrevious.Enabledd =
False
ElseIf on first record Then
form
.cmdFirst.Enabledd = False
form
.cmdPrev.Enabledd = False
End If
DoCmd.GoToRecord , , acFirst
form
.cmdFirst.Enabled = False
form
.cmdPrevious.Enabled = False
form
.cmdLast.Enabled = True
form
.cmdNext.Enabled = True
DoCmd.GoToRecord , , acPrevious
form
.cmdNext.Enabled = True
form
.cmdLast.Enabled = True
If form.Recordset.BOF Then
form
.cmdFirst.Enabled = False
form
.cmdPrevious.Enabled =
False
Else
form
.cmdFirst.Enabled = True
form
.cmdPrevious.Enabled =
True
End If
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 54 of 119
Microsoft Access Techniques
Event
form.cmdNext
form.cmdLast
Action
go to next record
Enabled First and Previous if at end of recordset, then disable Next and Last, else
Enabled them
NOTE: Use of EOF does not work in a standard module.
There use form.CurrentRecord
= DCount(*,
form.RecordSource) go to last record disable Next and Last,
Enabled First and Previous
Sample Code
DoCmd.GoToRecord , , acNext
form
.cmdFirst.Enabled = True
form
.cmdPrevious.Enabled = True
If form.Recordset.EOF Then
form
.cmdNext.Enabled = False
form
.cmdLast.Enabled = False
Else
form
.cmdNext.Enabled = True
form
.cmdLast.Enabled = True
End If
DoCmd.GoToRecord , , acLast
form
.cmdNext.Enabled = False
form
.cmdLast.Enabled = False
form
.cmdFirst.Enabled = True
form
.cmdPrevious.Enabled = True
Place Cursor When Form Opens
This is done by using the method SetFocus for the desired field. It’s Enabled property must
= True/Yes.
Forms!Employees!EmployeeID.SetFocus
This can be done in a form’s Open or Current
event procedure:
=Forms!Employees!EmployeeID.SetFocus
It can also be done in a macro? Not directly. A macro can run a function (via the RunCode action) that executes the method.
Save and Undo
Save:
by default Access saves changes to the current record when you navigate to a different record. When you use custom navigation you may want to have an explicit save that retains the current record on the form or subform:
DoCmd.RunCommand acCmdSaveRecord
Undo:
Changes to the current record may be discarded instead of saved. Access calls this “undo.”
The code is:
Me.Undo ' clear all changes to the current record
Me!txtDetails.Undo ' clear changes to named control
There are two kinds of changes in a form: (1) When current data is changed, the form’s
Dirty property is set to True. (2) When a new record is initiated, the form’s NewRecord property is set to True.
Changes in a subform are not known to the main form. They must be handled separately.
When the subform holds a datasheet, the Dirty property does not correctly reflect changes in current data. The NewRecord property is correct. The basic problem is that when an [Undo]
1 A form’s OnCurrent event happens when the form opens, is refreshed, requeried, or the record changes.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 55 of 119
Microsoft Access Techniques
button is on the main form, clicking it causes the focus to leave the subform and any changes there are saved.
For a form:
Me.Undo
Forms!("formname").Undo
DoCmd.RunCommand acCmdUndo
For a control:
Me!controlname.Undo
For a subform (only works when done within the subform). Actually I am not sure about this code because if it is in the subform module, Me refers to the subform, so why the need to address the subform control? Why not just Me.Undo?
Me!subformcontrolname.Form.Undo
Programmatically undoing data entered by a user will cause a run‐time error if there is nothing be be undone. Each conditon (Dirty and NewRecord) should be tested. If there is a subform, changes to existing data are saved when a control on the parent form is selected, thus the only “change” that can be “undone” by the parent form is the new record.
If Me.Dirty = True Then
Me.Undo
ElseIf Me.NewRecord = True Then
End If
Protect Form Field From Update
Set field’s properties:
Enabled = Yes (can have focus, appears normally)
Locked = Yes (no editing)
TabStop = No (tab skips over field)
Miscellaneous Form Control Properties
To designate a control button to be activated on [Enter]: set that control’s Default property = Yes.
To designate a control button to be activated on [Esc]: set that control’s Cancel property = Yes.
To put cursor on a given control when form event occurs:
Forms![form name].SetFocus
You can hide and/or unhide a form control in code:
Me![control name].Visible = No
Me![control name].Visible = Yes
' hides the control
' unhides the control
You can layer controls (place one beneath another) with menu Format, Send to Back. This can only be done in Design View. You can in code hide the controls on top while unhiding the controls on the bottom using the Visible property.
Command Buttons
Typical buttons are [OK] and [Cancel]. If you want a button to be the default and activated by the [Enter] key (in addition to clicking it), then set property Default = Yes. If you want a button to cancel the current operation and close the window and you want the [Esc] key to activate it (in
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 56 of 119
Microsoft Access Techniques
addition to clicking it), then set property Cancel = Yes. Remember these buttons still need an
OnClick event handler.
Option Buttons
Option buttons may be part of an option group. Each is its own control. Apparently the relationship is established by physical location: the buttons are located within the rectangular frame of the group, there is no property that refers to the relationship.
For the option buttons in an option group to be selectable, the form must have property
AllowEdits = Yes.
How to test if a particular option button is selected: compare the option groupʹs Value property to the option buttonʹs OptionValue property.
If [Date Option Group].Value = [Date Range].OptionValue Then . . .
Refer to the option group:
Me!optRecordOrder
Refer to an individual option button:
Me!opt1ID
You can set the focus on the option group but not an option button:
Me!optRecordOrder.SetFocus
You can act on the selected option button with a SELECT CASE structure:
Select Case optRecordOrder.Value
Case 1 ' Latest History
lstJobs.RowSource = "AllJobs"
Case 2 ' Employer-Title
lstJobs.RowSource = "AllJobsEmployerTitle"
Case 3 ' Title-Employer
lst Jobs.RowSource = "AllJobsTitleEmployer"
Case 4 ' ID
lstJobs.RowSource = "AllJobsID"
Case Else ' this would be an error
lstJobs.RowSource = "AllJobs"
End Select
Memo Field in Text Box
The standard way to present a memo field (up to 65,000+ characters of text) is with a multi‐line text box with a vertical scroll bar. The scroll bar only appears when the control has the focus. The size of the text box is fixed when viewed online. If the property Can Grow = Yes, then when the form is printed, the size of the text box increases to present all the text. You can zoom in on such a text box: when it has the focus press [Shift+F2] to open the “Zoom” window, which can be closed easily with [Esc].
Combo Box
Drop‐down box with text box at the top of a pre‐defined value list. Can have vertical scroll bars. Value may be typed or selected from list. No multi‐select allowed. When not open
(dropped‐down) the height of the box is one line. Down arrow is full‐size in one line.
The form must have property AllowEdits = True in order to select a row in the list.
Relevant objects, methods, and properties:
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 57 of 119
Microsoft Access Techniques
AutoExpand property controls whether the typed value is automatically completed by a matching list entry. Best when list entries are text, not numbers.
Column property references a numbered column in a multi‐column list. Has (column, row) format where (0, 0) is the first column in first row. Value is Null when there is no selection. Can be expressed as Column(x) to refer to the entire column.
ColumnCount property specifies number of columns in the box.
ColumnWidths property specifies width of each column separated by semicolon. You can hide a column by making its width = 0.” Example: .3”;1.5” and .3”; 0”; 1.5”
DefaultValue property specifies list entry to be used as default format. Value is like: “=Null”; the
Null value has the advantage that it will not be accepted as a valid value when LimitToList is used. If no value is specified, “0” appears and, even when LimitToList is True, is accepted as a valid value.
DropDown method forces the named combo box to drop down. Typically used when something happens in another control.
Format property can customize way in which list data is displayed.
IsNull function determines if any row is selected: If IsNull(Forms!formname!controlname) Then . . .
ItemData property returns the value in the bound column in the list; uses zero‐based index (first entry is 0). I was unable to use this property as a default value. Example: ItemData(5) refers to the value in the numbered row.
LimitToList property controls whether entered value may be different from value list. “True” by default when first column is not bound. “True” does not keep a combo box from accepting a
Null value; the way to disallow Null is to set the Required property of the field in the table to which the combo box is bound to True.
ListCount property returns the number of rows in the list.
ListIndex property returns relative position (index) of the selected entry in the list. First entry is
0. Read‐only.
ListRows property specifies maximum number of rows to display in drop‐down list.
ListWidth property specifies width of drop‐down box. It is advisable to set this to the combined column widths as the default value is likely to be narrower.
OnNotInList property specifies name of procedure that will run automatically if entered value is not in the list.
RowSourceType property specifies values in the list as Table/Query, Value List, or Field List. For
Value List, values are entered in property RowSource as “Open”, “Closed” . . .
Text property sets or returns the text in the text box portion of the control You can set the Text property to the text you want to display in the control. You can also use the Text property to read the text currently in the control. Note: to set or return a controlʹs Text property, the control must have the focus, or an error occurs.
Value property returns the text in the text box portion of the control after the bound record has been updated. Hopefully, it also returns the text in an unbound column after the focus leaves it.
To access selected list entry: Use Value property.
To determine if entry selected (this is most useful when the combo box’s DefaultValue property is set to Null):
If IsNull(Forms!formname!controlname) Then . .
To set value of a second control (controlB) based on the value selected in the combo box
(controlA); requires the second control be physically located after the combo box:
Me!controlB = Me!controlA.Column(2, n)
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 58 of 119
Microsoft Access Techniques
List Box
List box is a fixed‐height box containing a list of data in rows, no drop‐down. Can have vertical and horizontal scroll bars. Multi‐select allowed. If height is only one text line, down arrow is tiny.
The form must have property AllowEdits = True in order to select a row in the list.
Relevant objects, methods, and properties:
BoundColumn Links the records in the list box to the record source of the form.
ColumnHeads When True the first line in the list box appears as column headings.
ColumnWidths property specifies the width of each column in the list box separated by semicolons. In America, the unit of measure is inches. A column can be hidden by setting its width to zero; while it is not visible in the list box, it is still available to Access (and code).
Example with three columns: 1”; 2”; 0.4”. Example with a hidden column: 0”; 1”; 1”.
Column property references a numbered column in a multi‐column list. Has (column, row) format where (0, 0) is the first column in first row. Value is Null when there is no selection. Can be expressed as Column(x) to refer to the entire column.
ItemData property returns the value in the bound column in the list; uses index. Example:
ItemData(5) refers to the value in the numbered row.
ItemsSelected property returns a reference to the collection of the same name. The collection contains variants, each an integer index referring to a selected row. (0) is the first member in the collection. Read only.
ItemsSelected.Count property returns the number of indexes in the collection.
ItemsSelected.Item(n) property refers to the numbered row in the collection. It is optional as you can achieve the same end by ItemsSelected(n). Read‐only.
ListCount property returns the number of rows in the list.
ListIndex property returns relative position (index) of the selected entry in the list. First entry is
0. Read‐only.
MultiSelect property controls whether more than one entry can be selected and how (there are two methods).
Row Source property contains either the name of a query or table or the SQL SELECT statement that returns the fields for the list box. When a list box is created by the wizard, the latter value is used. Best to replace it with an actual query/table name.
Selected property indicates if the corresponding list item is selected. Example: Me!ctl.Selected(4)
= True. Read/write.
Value property returns the value in the bound column for the selected row. Is “‐1” if no column is bound.
To pre‐select the first data row:
Me!lbJobs.Selected(0) = True ' when there are no column headings
Me!lbJobs.Selected(1) = True ' when there are column headings
To access selected entry of single selection list:
Dim n As Integer
Dim ctl As Control
Set ctl = Me!lbAssignee
If ctl.ItemsSelected.Count = 1 Then ctl.ItemData(1) . . .
' NOTE: the use of Me! is optional
To access each selected entry of multi‐select list:
Dim varItem As Variant
For Each varItem in ctl.ItemsSelected
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 59 of 119
Microsoft Access Techniques
. . .
Next
To unselect a row in a single‐select list box:
If ctl.ItemsSelected.Count = 1 Then
Dim n As Variant n = ctl.ItemsSelected(0)
Me!ctl.Selected(n) = False
End If
To unselect all rows in a list box based on a query:
listboxname
.Requery
To unselect a row in a multi‐select list box: (have not yet proven this in code)
Dim varItem As Variant
Dim n As Variant
If ctl.ItemsSelected.Count => 1 Then
For Each varItem in ctl.ItemsSelected
n = ctl.ItemsSelected(varItem)
ctl.Selected(n) = False
Next
End If
You can populate a list box with a query and set one row‐value as DefaultValue. The query can append “all” to its values with a UNION.
When you use a list box as the way of displaying table/query records for some action by the user (easier than using a datasheet), some form properties are critical:
DefaultView: Single Form
AllowDatasheetView: No
AllowPivottableView: No
AllowPivotchartView: No
AllowEdits: Yes
AllowDeletions:
AllowAdditions:
Yes
Yes
DataSource: the next three enable a click on list box row to select the row
(none) > unbound
When list box holds a query result with a numeric field, a null value appears blank. When you test the value in code, you can test for a numeric value. Test for the blank value as follows:
If (cnt > 0 And Not cnt = "") Then . . .
When no item is selected in a list box, the value of the bound column is Null. To tell if an item is selected:
If IsNull(Me.ProductID) Then . . .
To reorder the contents of a list box provide a separate form control for the user to select one of a group of order choices and apply the selected order by changing the list box’s Row
Source. This is easiest if you have one query for the default order and additional queries based on it with different orders (e.g., SELECT * FROM AllJobs ORDER BY Employer, Title).
One way to present record order choices is in an option group. Code sets the Row Source to
the query corresponding to the selected order. See page 57 for an example.
You can synchronize the contents of a list box to the form’s primary record (its record source). This is helpful when you use a list box to present related data from a second table, either a child table or one with a foreign key relationship. This takes three components: (1)
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 60 of 119
Microsoft Access Techniques
the list box’s Row Source is a SELECT statement with a WHERE clause that links a field on the form to a field in the list box, (2) list box’s property Bound Column identifies the related form field, and (3) the form’s Current event requeries the list box.
Example: The form has Record Source = Recruiter. The list box has Bound Column = ID (the field in the Recruiter table that has a foreign key relationship with the RecruiterID field in the Job table. The list box has Row Source = SELECT ID, Employer, Title, Location, Status,
Submitted, Interviewed FROM JobsForRecruiter WHERE
(((JobsForRecruiter.RecruiterID)=Forms!RecruiterWithJobs!ID)) ORDER BY 1; The form’s
Current event has code: Me.lstJobs.Requery.
You can present a memo field in a list box only by using a query that selects a fixed number of characters. Example: SELECT Name, Left(Notes, 30) FROM Comment.
Use List Box to Restrict Values to Lookup Table
List box properties:
RowSourceType = Table/Query
RowSource = lookup table name
MultiSelect = None
ColumnCount = 2 (code and description)
ColumnWidths = 0.25”;1”
BoundColumn = 1
List box will appear always, so both code and text will always be displayed.
Populate List Box with Query Result
List box properties:
RowSourceType = Table/Query
RowSource = query name
BoundColumn = some column, preferable one that is unique
Enabled = Yes
Locked = No
ColumnCount = number of columns in query
ColumnWidths = 1”; 1”; . . .
MultiSelect = depends
Text box can present count of rows selected in list box. List box can have OnClick event that can be used to set value of text box:
Private Sub ListBoxUnassigned_Click()
Dim tempCount As Long, lb As Control, lbItem As Variant tempCount = 0
Set lb = Me!ListBoxUnassigned
If lb.ItemsSelected.Count > 0 Then
For Each lbItem In lb.ItemsSelected
tempCount = tempCount + lb.Column(4, lbItem)
Next lbItem
Me!txtCount = tempCount
End If
End Sub
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 61 of 119
Microsoft Access Techniques
Populate List Box Containing Names of Queries
Form has list box with property RowSourceType = function name. Leave property
RowSource blank. Property ColumnCount must be correct.
VB function populates list box.
Function ListBoxQueries(fld As Control, id As Variant, row As Variant, col As
Variant, code As Variant) As Variant
' this function is the data source for a list box
Dim strUserName As String
Dim r As Integer
Dim qdfItem As QueryDef
Dim ReturnVal As Variant
ReturnVal = Null
' subscript for q array
' QueryDef object variable
Select Case code
Case acLBInitialize ' Initialize, build array.
strUserName = GetUserName()
n = DBEngine.Workspaces(0).Databases(0).QueryDefs.Count
Static q(70, 3) As String
r = 0
For Each qdfItem In DBEngine.Workspaces(0).Databases(0).QueryDefs
If Not Left(qdfItem.Name, 1) = "~" Then
q(r, 0) = qdfItem.Name
q(r, 1) = QueryDefType(qdfItem.Type)
q(r, 2) = "Updatable for user " & strUserName & ": " & qdfItem.Updatable
r = r + 1
End If
Next qdfItem
ReturnVal = r
Case acLBOpen ' Open.
ReturnVal = Timer ' Generate unique ID for control
(seconds since midnight).
Case acLBGetRowCount ' Get number of rows.
ReturnVal = n
Case acLBGetColumnCount ' Get number of columns.
ReturnVal = 3
Case acLBGetColumnWidth ' Column width.
ReturnVal = "2 in;1 in;1 in"
Case acLBGetValue ' Get data.
ReturnVal = q(row, col)
Case acLBEnd ' End.
Erase q
End Select
ListBoxQueries = ReturnVal
End Function
Scroll Down List Box to Show Pre-Selected Row
Scroll a listbox to a specific row. Emulates the VB ListBox TopIndex property. You can alter the code to easily have the selected row display as the first or last row as well. The example code is placed behind a Command Button. From Stephen Lebans www.lebans.com
Private Sub cmdListIndex_Click()
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 62 of 119
Microsoft Access Techniques
On Error GoTo Err_cmdListIndex_Click
' Always make NumRows an odd number
' if you want selected Row to be in the
' middle of the ListBox.
' NumRows is the number of completely visible rows in the ListBox Const
NumRows = 7
' Row we want displayed in middle of ListBox.
Dim intDesiredRow As Integer
' Arbitrarily select the 24th row. intDesiredRow = 24
' ListBox must have the Focus
Me.List2.SetFocus
' Force ListBox to start from the top
Me.List2.ListIndex = 1
' Force the Scroll offset we desire
Me.List2.ListIndex = intDesiredRow + (NumRows / 2)
' Now select the row without further scrolling
Me.List2.ListIndex = intDesiredRow
Exit_cmdListIndex_Click:
Exit Sub
Err_cmdListIndex_Click:
MsgBox Err.Description
Resume Exit_cmdListIndex_Click
End Sub
Tab Control
A tab control is a neat way to present a lot of data in a small amount of space—by layering it and using the tabs themselves to access each layer. There is an Access tab control and there are
ActiveX controls.
The following discussion uses the terms: tab control: refers to the overall control tab: refers to the rectangle that is commonly positioned at the top layer: the body of the control that is specific to an individual tab; others call this “page” tab bar: referns to the bar at the top of the tab control in which the tabs are located tab text: refers to the text that appears in the tab; the control property is Caption data control: refers to any control which appears on a layer
Access Tab Control
BackgroundColor can be the default gray or “transparent” which lets the color of the form section be visible. Unfortunately, the latter does not apply to the area of the tab bar where there are no tabs. I was unable to place a rectangle on top of the unused tab bar in hopes of giving that area a different color; even moving the rectangle to the front did not make it visible in the tab bar.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 63 of 119
Microsoft Access Techniques
FixedWidth applies to the width of the tabs. The default width is just enough to hold the tab text; there is some effort to balance the width of the tabs so a tab with short text is similar in width to the tab with long text. You can set a width, like 1.25”, to widen the tabs. There is no way to automatically expand the width to fill the width of the overall control.
You can change the default tab height with the property Tab Fixed Height. It can be visually better to increase it.
Tab text length. In design view you can pad the actual text with spaces before but not after
(you can type a space but it disappears). You could increase the padding in code in the form’s OnCurrent event:
Me!Page1.Caption = " Job "
Number of tabs is initially 2. Use the context menu to add tabs (Insert Page), remove tabs
(Delete Page), and change the text (caption) on a tab (Rename). Alternatively, use menu
Insert, Tab Control Page to add a tab. Use the Properties window to set each tab Name (may be best left to the default) and Caption.
Data tab order: You set the data control tab order within each layer at a time with the context menu Tab Order. There does not seem to be a way to tab between layers, therefor it is advisable to include an access key in the tab text: enter the text like “F&lowers” to produce the result “Flowers” where [Alt+L] makes that layer active.
You can use the keyboard to move between tabs: [Ctrl+Tab] moves to the next tab page on the right. [Ctrl+Shift+Tab] moves to the next tab page on the left.
In design mode you place data controls on a tab layer in different ways. If dragging a field from the Field List window, you can drag it directly to the tab layer. If adding a control with the Toolbox window, you can drag it directly to the tab layer. Once on a tab layer you cannot drag a control off to the form itself; instead you must cut (with [Ctrl+X]) the control from the form and then paste it on a tab layer (with [Ctrl+V]). Likewise you cannot drag a control from the form to a tab layer; instead you must cut (with [Ctrl+X]) the control from the form and then paste it on a tab layer (with [Ctrl+V]).
Useful Tab Control properties: Visible, Style. Useful events: OnClick, OnChange (occurs when you move from page to page, or tab to tab, cannot be canceled).
Relevant VBA object model: the TabControl object contains two objects: Pages collection and Properties collection. The Pages collection contains the Page object which also contains a
Properties collection. The Page object correlates to what I think of as a layer.
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. Because the Value property is the default property for a tab control, you do not have to refer to it explicitly. By reading the
Value property at run time, you can determine which page is currently on top.
You reference the tab control itself as any form control:
formname
!TabControlName
Me!TabControl1 = 2 ' moves focus to third tab of named tab control.
You can reference a Page object in three ways:
Me.Pages!pagename
Me.Pages("pagename")
Me.Pages(index)
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 64 of 119
Microsoft Access Techniques
Because each page is a member of the formʹs Controls collection, you can reference a page solely by its Name property without referring to the tab controlʹs name or its Pages collection.
Me!TabControlName.Pages(0).Caption = "First Page"
Me!Page1.Caption = "First Page" ' Page1 is the tab’s name
The Page object is also a form control. Controls on a Page object belong to that Page objectʹs
Controls collection and to the Controls collection of the form itself. For this reason, each control on a tab control page must have a name that is unique with respect to all other controls on the same form. You can refer to controls on a tab control page by using the same syntax for controls on a form without a tab control.
Me!Page1!txtDescription.Value = "Anon"
Me!txtDescription.Value = "Anon"
Each Page object has a PageIndex property that indicates its position in the Pages collection.
The Value property of the tab control is equal to the PageIndex property of the current
(active) page. You can use these properties to determine which page is currently selected after the user has switched from one page to another, or to change the order in which the pages appear in the control. The PageIndex is automatically set to start at 1 and increment by 1 when you use the form designer to create the tab control and the tabs. I cannot see why you would change the values. 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.
Useful Page properties:
Caption
Enabled
Name
Visible (read/write)
PageIndex
Useful Page methods:
Requery
SetFocus
Useful Page events:
OnClick
OnDblClick
(fires when the user clicks a blank area or record selector on the form)
You may want to put a record count in a tab caption, such as “Comments (3)”. This is easily done. In the Current event of the main form set the tab captions; this will work corrrectly when the form is opened and when navigating between records on the main form. If you need the caption to update automatically as data is added or deleted on a subform on the tab, use the AfterInsert event and OnDelete event of the subform to change the tab caption.
See “Counting Records in a Subform” on page 71 for details.
Private Sub Form_Current() ' in main form
'set the caption of the tabs
Me!Page3.Caption = "History (" &
Me!HistorySubform.Form.RecordsetClone.RecordCount & ")"
Me!Page4.Caption = "Comments (" &
Me!CommentsSubform.Form.RecordsetClone.RecordCount & ")"
End Sub
Private Sub Form_AfterInsert() ' in subform
'set the caption of the tab
Me.Parent!Page3.Caption = "History (" & Me.RecordsetClone.RecordCount & ")"
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 65 of 119
Microsoft Access Techniques
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer) ' in subform
'set the caption of the tab
Me.Parent!Page3.Caption = "History (" & Me.RecordsetClone.RecordCount & ")"
DoCmd.GoToRecord , , acFirst
End Sub
Editing data on tabs may be tricky. If the main form is not bound, a bound subform will save the record as soon as a different tab is on top.
”Subforms with master/child links in Access 2003 are rather buggy. Subforms on tab controls just add to the complexity.”
You cannot keep the user from clicking on a tab and giving that page the focus. You can have a tab control Change event that conditionally changes back to the previous page:
Private Sub MyTabCtl_Change()
If IsNull(txtRequiredEntry) And MyTabCtrl.Value <> 0 Then
MsgBox “Required field is missing.”
MyTabCtrl.Value = 0
End If
End Sub
One alternative to using the built‐in tabs is to set the Style property = None (2) and use in their place labels styled to suit and having a Click event that sets the focus on the corresponding tab page. The current tab label could be chiselled and sunken when not current (SpecialEffect property). Because you cannot disable a label, the Click event will have to test the SpecialEffect property to see if it is current.
One application which I found on Experts‐Exchange: “Many folders went in a box, and many boxes belonged to a project. And every box has a label that has to do with the folders in the box and the project the box is in. In that case, I had four tabs: Projects, Boxes, Folders,
Labels. This super‐switchboard acted as the only form the user actually interacted with
(other than some pop‐ups and dialogs, etc.). Once you chose a project on the first tab, you just kept working to the right, tab by tab, until it was time to print the labels.”
Microsoft Forms 2.0 Tab Strip
I found this unworkable. The biggest problem I encountered was the inability to place data controls on a tab layer and then see them in Form View. I could also not set the text for any but the first tab. It is likely that I need to know more about the control before being able to use it effectively.
Microsoft TabStrip Control, version 6.0
This looks good. The formatting of the tabs and tab bar are more sophisticated than for Access’s tab control. However, using an ActiveX control requires programming.
There is a help file for this control and others in MSComctl.ocx: cmctl198.chm; it is used in conjunction with cmctl198.chi. They are normally installed when MSDN is installed; it may be found in C:\Program Files\Microsoft Visual Studio\MSDN\2001APR\1033. I found the files at http://homepages.uel.ac.uk/u0005080/vb/COMMON/HELP/
However it does not work at this time. The answer seems to be in comctl1.cnt and comctl1.hlp.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 66 of 119
Microsoft Access Techniques
The TabStrip object has a Tabs collection consisting of the individual tabs. Each tab has properties: index: The position of the tab within the collection (and in the tab bar). caption: The text string appearing on the tab. key: A unique string that identifies the Tab. Can be used in code. Optional. image: The index of an image in an associated ImageList control. Optional. tooltiptext: to be determined.
Use the control’s context menu TabStrip Object, Properties to open the (custom) dialog box of the same name. This dialog box presents some of the properties that are visible in the standard controls Properties dialog box, but in a more useful manner. Unfortunately you cannot resize the control while the custom dialog box is open.
The formatting of the tab bar is controlled by several properties.
In the preceding example, MultiRow = no, Style = 0 – tabTabs, TabWidthStyle = 1 – tabNonJustified, Placement = 0 – tabPlacementTop, TabStyle = 0 – tabTabStandard,
TabFixedWidth = 0, TabFixedHieght – 0, TabMinWidth = 567.
If the width of the control is reduced, the tab bar gets scroll buttons:
If the TabWidthStyle is changed to 2 – tabFixed, the bar looks like this:
If MultiRow is set on, the bar looks like this:
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 67 of 119
Microsoft Access Techniques
If TabWidth Style is changed to 0 – tabJustified, the bar looks like:
If TabWidthStyle is changed to 1 – tabNonJustified, the bar looks like:
Tabs can be added, removed, and re‐texted with the Tabs tab of the custom properties dialog box.
There is no way to change the background color of the control.
The TabStrip control is not a container. Any contents must be held in a container object. If you use a control array for the container, you can associate each item in the array with a specific Tab object.
This control should be placed in an unbound form. The contents of the control are held in a container object, the easiest being a subform control. You can place one subform control in the tab strip control; it should hold the form corresponding to the data in the first tab’s layer.
The tab strip control utilizes an OnClick event handler to replace the contents of the subform control with a different subform when a different tab is selected.
For example: Create a bound form for each tab with no record selectors, navigation buttons, and dividing lines. On the control place a pre‐defined bound subform that represents the contents of the first tab: With the Database window in the foreground, drag such a form to a
TabStrip control layer. Select and then delete the subformʹs label. Adjust the size of the tab
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 68 of 119
Microsoft Access Techniques
strip control to hold the subform completely. Write a TabStrip onClick event handler to swap the contents of each layer based on which tab is clicked, like:
' the name of the tabstrip control is TabStripPlant
' the name of the subform control is subContainer
' the names of the forms are General, Foliage
Private Sub TabStripPlant_Click()
On Error GoTo errhandler
ʹ
Dim rs As DAO.Recordset, mybookmark As String, I As Integer
Select Case Me!TabStripPlant.SelectedItem.Index
Case 1 ' Show General form
subContainer.SourceObject = "General"
Case 2 ' Show Foliage form
subContainer.SourceObject = "Foliage"
End Select
Exit Sub errhandler:
MsgBox "There is an error"
Customers.SourceObject = "General"
End Sub
Note that the Dim line seems extraneous and I commented it out successfully; if you use it you must reference the Microsoft DAO 3.6 Object Library.
Microsoft offers an alternate method for changing the content of TabStrip control’s container: If you use a control array for the container, you can associate each item in the array with a specific Tab object, as in the following example:
Option Explicit
Private mintCurFrame As Integer' Current Frame visible
Private Sub Tabstrip1_Click()
If Tabstrip1.SelectedItem.Index = mintCurFrame _
Then Exit Sub ' No need to change frame.
' Otherwise, hide old frame, show new.
Frame1(Tabstrip1.SelectedItem.Index).Visible = True
Frame1(mintCurFrame).Visible = False
' Set mintCurFrame to new value.
mintCurFrame = Tabstrip1.SelectedItem.Index
End Sub
Using access keys in tab text also requires programming. Set the KeyDown property of the form to the following event procedure:
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
If (Shift and acAltMask) > 0 Then ' ALT key was pressed
Select Case KeyCode
Case vbKeyA ' ALT+A was pressed
oleTab.Tabs(1).Selected = True
Case vbKeyB ' ALT+B was pressed
oleTab.Tabs(2).Selected = True
Case vbKeyC ' ALT+C was pressed
oleTab.Tabs(3).Selected = True
End Select
End If
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 69 of 119
Microsoft Access Techniques
End Sub
Setting Field Value
Initial value can use the table definition: the field’s property Default Value can be used, but it is limited to a value or an expression using the built‐in functions (not custom application functions).
Initial value can be done on the form: The value can be set with an event procedure either immediately after the form is opened (using the form’s On Open event) or when a record is added/updated (using the field’s Before Update event). In both cases, the field is included on the form and it may be protected.
When form A opens form B which contains fields that must be initialized, the event procedure used to open the form can be a macro OpenFormB. That procedure has two actions: (1) OpenForm and (2) RunMacro InitializeFormB. The second step has one
SetValue action per field. For example, a date field: [Forms]![formB]![Date Assigned] =
Date(). A field on form B that is set to a value from form A: [Forms]![formB]![Exception ID]
= [Forms]![formA]![Exception ID].
I USED MACROS WHEN I STARTED, BUT NEVER AGAIN. In this example, use VBA code to open the form (DoCmd.OpenForm) and initialize the fields (Forms(“formB”)![Date
Assigned] = Date(); Forms(“formB”)![Exception ID] = Me![Exception ID] ). Note that you can initialize the field in the same Sub only if the form is not opened as a dialog box. In that case use an OnOpen event in formB to initialize the field (Me![Date Assigned] = Date(). You could alternatively define formB so the field in question has property Default Value =Date().
When you want to initialize a field to a value in another form, be sure to test to see if the form is open first.
When form A opens form B in order to edit one record from a list on A, use the where condition to specify which data is to be presented on form B:
DoComd.OpenForm "formB" acFormEdit, , "ID = " & Me![PortalID]
The value of a form field can be set to the value of an expression or function, either built‐in or custom.
Programmatically set a Date‐Time: a) The field is protected on the form: with properties Enabled = Yes, Locked = Yes, TabStop
= No. b) Each form that changes the object with the date‐time (1) displays the field in a text box named “Revision” and (2) has a BeforeUpdate event that runs procedure “Set Revision
DateTime.” c) The procedure has one step: Me![Revision].Value = Now(). If date only is desired, use function Date(). d) In the form event AfterUpdate include statement Me.Refresh.
A text field can be set to the sum of a table/query field. This may be done when the table/query results are displayed in a subform.
1) Its property ControlSource:
=DSum("fieldname", "tablename")
2) The DSum function works to initialize the field, however if the table on which it is based changes, the text field will not reflect the current value until it is requeried:
Dim ctlSum As Control
Set ctlSum = Forms!MassAssignments!txtCountAssgn
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 70 of 119
Microsoft Access Techniques
ctlSum.Requery
3) If in the subform, records can be added, deleted, and changed, then the requery can be done by subform events AfterUpdate, AfterInsert, and AfterDelConfirm. It can also be done as the main form’s control event OnExit. It is not clear if all event procedures are needed.
A text field can be set to the count of rows selected in a list box:
Private Sub ListBoxUnassigned_Click()
Dim tempCount As Long, lb As Control, lbItem As Variant tempCount = 0
Set lb = Me!ListBoxUnassigned
If lb.ItemsSelected.Count > 0 Then
For Each lbItem In lb.ItemsSelected
tempCount = tempCount + lb.Column(4, lbItem)
Next lbItem
Me!txtCount = tempCount
End If
End Sub
A form used to add a record can, in code, set field value:
[Parameter Count] = CountParms(.., ..)
DoCmd.Save
Put Record Count on Form
When you want to present a record count on a form, use a Text Box control with the source
=DCount(“*”, “table or query name”). The control value will be refreshed by form module code
Me.Refresh (along with any list boxes that may be present).
I had a problem in the form below. The list box displays a query of the main table, Total records is meant to be the records in the table, but control source =Count(“*”) only worked when the form was first opened. It was not refreshed when records were deleted or added to the table. I
finally tried a control source = DSum(“Count”, “CountFilesByPath”) where the domain is the query displayed in the list box.
There are other approaches, but they did not count correctly: (1) using TableDef RecordCount property, (2) using value of count query. The second approach caused an error.
Counting Records in a Subform
When you want to count records displayed on a subform, the basic count is obtained with
RecordsetClone.RecordCount, but you have to be careful how you qualify it, i.e., which object
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 71 of 119
Microsoft Access Techniques
you reference and when. Remember that “Me” refers to the current object; for the form event Me is the form, for a subform event, Me is the subform.
Note: when setting the value of a calculated control with Control Source, using
=Screen.ActiveForm.RecordsetClone.RecordCount worked, using Me did not.
(1) With a calculated control on the main form:
=Me!subformname.Form.RecordsetClone.RecordCount
(2) With a calculated control on the subform:
=Me.RecordsetClone.RecordCount
(3) In an event of the main form:
Me!subformname.Form.RecordsetClone.RecordCount
(4) In an event of the subform:
Me.RecordsetClone.RecordCount
(5) In the subform and when it is linked to the main form and contains the subset of all the records that relate to the current record in the main form, use
Me.RecordsetClone.RecordCount
Edit Data
You may want to edit data entered by a user. This can be done with form field edits.
Relevant properties:
Format
ValidationRule (caution: active even during Cancel)
ValidationText
Access will not edit a field unless the value changes (by the user) or ? (not by setting the
Dirty property).
OnExit event procedure performs the edit. When there is a relational edit, the OnExit event procedure for each field should perform the edit.
Find Individual Record on Form
Use command button created with the wizard and specify Category = Record Navigation,
Action = Find Record.
The command button has Caption property = &Find Record. The & character enables the shortcut key [Alt+F] for the control.
The command button opens the standard Microsoft Access “Find in field” window, which operates on the field in which the cursor is located. Some user training may be necessary.
Block Record Additions/Deletions on Form
Include form properties:
NavigationButtons = yes
RecordSelectors = yes
AllowAdditions = no
AllowDeletions = no
Print a Form
You can print a form with menu File, Print. The Print dialog box has a Print Range control that controls which data are printed; the choices are All, a page range, and Selected Record(s). The
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 72 of 119
Microsoft Access Techniques
best use for such a printout is as a record of the form; normally the colors and type are printed as they appear on your monitor. A report is the best way to print the data.
What data is printed?
If the form has a subform with a default view of Datasheet, many records appear on the form. Regardless of your choice of Print Range, all the records are printed.
If the form presents one record at a time, you can print only the current record by selecting
Print Range = Selected Record(s).
If the form presents one record at a time with child record subform(s), you can print only the current record by selecting Print Range = Selected Record(s). All the child records will print.
Enhance a form’s printability:
Use menu File, Print Preview to see how printed form will look. A form that is wider than the printed page will wrap horizontally to a second page.
If the form is wider than a printed page, you can either reduce the page’s side margins (with
Page Setup) and/or set the page orientation to lanscape (with printer Properties).
A page header with the form’s title and the current date‐time make printing a form self‐ documenting. The current date‐time is accomplished with a text box whose data
ControlSource = =Now(); the text box can have properties: Border Style = Transparent, Back
Style = Transparent, Special Effect = Flat. The page footer can be shrunk to nothing.
Add a [Print] button to the form that removes the background colors and/or picture before printing.
Copy the form as a report, then edit the report to make it more “printable.” To copy the form: In the Database window, select the form, select Save As from the context menu, in the
Save As box select type Report, then [OK]. Put a [Print] button on the form with an OnClick event handler that opens the report.
Initiate New Query/Report
Macro step: Action = RunCommand, Command = NewObjectQuery or NewObjectReport.
Update a Table From a Form Based on a Query
This is commonly done and works well in most situation.
If there are M:M relationships, set form’s RecordsetType property = Dynaset (Inconsistent
Updates).
If the query has a JOIN, an Add may try to add records to both tables—and fail.
Edit Table with Two Forms
You may want to use two forms A and B. On formA use a list box to show all the table records; use a query to include foreign key text values. User selects an Add button to add a record, a Delete button to delete a record. To edit a record, user double‐clicks its row. The
Add button opens formB so that only one record is added, after which it requeries formA’s list box and closes itself. The edit action opens formA so that only the selected record is presented; after the record is updated, the form requeries formA’s list box and closes itself.
The delete action confirms the delete, then builds a delete query and runs it.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 73 of 119
Microsoft Access Techniques
FormA had one list box and command buttons. (Actually these samples came from different forms.)
Private Sub cmdAdd_Click()
DoCmd.OpenForm "Subject", acNormal, , , acFormAdd, , "Add"
End Sub
Private Sub cmdDelete_Click() msgTitle = "Edit Subjects"
Dim ctl As Control
Set ctl = Me!SubjectList
If Not ctl.ItemsSelected.Count = 1 Then ' if a row is not selected, error
MsgBox "No subject selected.", , msgTitle
Exit Sub
End If lbItem = ctl.ItemsSelected(0) ' integer index of selected row sid = ctl.ItemData(lbItem) ' value of bound column in selected row,
SubjectID
Dim selRow As Long selRow = ctl.ListIndex ' relative position of selected row (row index) cnt = ctl.Column(4, selRow + 1) ' DocCount in selected row
If cnt > 0 Then ' If DocCount is not zero, cannot delete
MsgBox "You cannot delete subjects assigned to documents.", , msgTitle
Exit Sub
End If
Dim response As Integer response = MsgBox("Do you really want to delete the selected subject?", vbYesNo, msgTitle)
If response = vbNo Then Exit Sub strSQL = "DELETE ID FROM Subject WHERE ID = " & sid
DoCmd.SetWarnings False
DoCmd.RunSQL strSQL
DoCmd.SetWarnings True ctl.Requery
End Sub
Private Sub PortalList_DblClick(Cancel As Integer)
Dim ctl As Control
Set ctl = Me!PortalList
If Not ctl.ItemsSelected.Count = 1 Then Exit Sub
Dim lbItem As Variant lbItem = ctl.ItemsSelected(0) ' integer index of selected row sid = ctl.ItemData(lbItem) ' value of bound column in selected row
Set ctl = Nothing
Dim strWHERE As String strWHERE = "ID = '" & sid & "'"
DoCmd.OpenForm "Portal", acNormal, , strWHERE, acFormEdit, , "Subject"
Forms!Portal!PortalName.SetFocus
Forms!Portal!ID.Enabled = Yes
Forms!Portal!ID.Locked = Yes
End Sub
FormB has data fields and command buttons. It checks for an OpenArgs in order to decide to close after a save.
Private Sub cmdSave_Click()
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 74 of 119
Microsoft Access Techniques
DoCmd.RunCommand acCmdSaveRecord
Dim how As String
If IsNull(Me.OpenArgs) Then Exit Sub how = Me.OpenArgs
If Len(how) > 0 Then
If how = "Add" Then
Dim ctl As Control
Set ctl = Forms!Subjects!SubjectList
ctl.Requery ' refresh list box to include new subject
DoCmd.Close ' close this form
End If
End If
End Sub
Subforms
You can embed one form within another. Generally you use a subform to present data in a different table than is presented in the main form. The subform data may be for a child table, a parent table, or a completely unrelated table. For convenience the data source of the subform is referred to here as the secondary table. More than one subform can be embedded within the main form.
A subform is embedded within a main form with a subform control. The subform can be a form or datasheet. If the subform is to be a form, create it first. Then in the main form use the
Subform tool to create an area to hold the subform and identify the form to be contained. If the subform is to be a datasheet, start by adding a subform control to the main form, then set its SourceObject property to a table name, query name, or form name; the linked field does not appear and the subform has property CanGrow = Yes.
Link the subform to the main form with subform properties LinkMasterFields and
LinkChildFields. These specify the field(s) that must be the same on the main form and on the subform. See below for details.
If the subform is for data entry, set the subform’s DataEntry property to Yes. This will open the subform at a new blank record and prevent users from scrolling back thru the existing records.
When the secondary table has a field that holds a code with a separate lookup table (a foreign key) and you want to show the text value but not the code, create a query to hold the parent table primary key, child table key, and lookup table text. Then create the subform based on the query. Be aware that you will not be able to use the standard navigation controls, so you must include your own custom controls. Form properties should include:
DefaultView = Datasheet
AlowFormView = No
RecordSelectors = No
NavigationButtons = No
To tab out of a subform: {Ctrl+Tab] will tab you out manually, or you could set an event in the LostFocus event of the last field of the subform to manually move the focus to a control on the main form.
When the data is displayed as a datasheet the standard navigation controls and record selector icon are presented.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 75 of 119
Microsoft Access Techniques
When the secondary table is linked to the main table with a foreign key (not a primary key) do not use the standard navigation controls in the subform. Include the primary key as a text control in the main form (which can be populated manually or by special code you write).
You can add a secondary table record at the same time as a main table record. But if you try to type or paste text into a control on the subform before the main table record is saved, you will get a warning message “You can’t assign a value to this object.” But after you click [OK] the value appears in the control.
When you use custom navigation controls: To add a record, you must use a separate form; a popup modal form works well; the key field(s) that are linked to the main form can be initialized with form property DefaultValue:
DefaultValue = Forms![main form name]![linked field name]
The Add form uses code to act on [Add] and [Cancel] buttons. In the following example, code checks to be sure a value was selected from the combo box.
Private Sub cmdAdd_Click()
If IsNull(Me![BP Code]) Then
MsgBox "Please select a list entry", vbOKOnly
Else
RunCommand acCmdSave
DoCmd.Close
RunCommand acCmdRefresh
End If
End Sub
Private Sub cmdCancel_Click()
DoCmd.Close
End Sub
Subforms with master‐child links are buggy in Access 2003. Subforms on tab controls just add to the complexity.
Editing Data in a Child SubForm with Standard Navigation Controls
Fo a new record in the main form, the only standard subform navigation control that is enabled is Last. The record selector is a tiny arrow. Normally you enter the data in the main form first. when you move the focus to a control in the subform (probably by positioning the mouse in the control) the main form record is saved automatically and the enabled navigation keys for the child subform become First and Last. You then enter text in the subform.
After a new main record is saved, when the first child record is saved, the enabled navigation keys for the child subform become First, Next, Last, and New.
Should you enter data in the child subform without entering the main table data and then try to start a new child record, you’ll get an error message “Index or primary key cannot contain a Null value.” This happens because Access is trying to save the first child record but, because it does not have the primary key for the parent record, it has a Null value in part of the child record’s key. Your only action at this point is to undo the edits to first child record and enter text in the main form.
When adding or changing a child record, your edits are saved automatically when you click a control outside of the subform.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 76 of 119
Microsoft Access Techniques
You can delete a record from a datasheet by selecting the row, then pressing the [Delete] key.
Link Subform to Main Form
You can use the LinkChildFields property with the LinkMasterFields property to specify how
Access links records in a form or report to records in a subform, subreport, or embedded object such as a chart. When these properties are set, Access automatically updates the related record in the subform when you position on a different record in the main form (this does not work when you pre‐select a record in the main form). Both properties apply to the subform control in the main form. Corresponding fields in each form must have compatible data types and field sizes.
LinkChildFields is the name of one or more linking fields in the subform, subreport, or embedded object. It is analogous to a foreign key. If you want to use a calculated value as the link for a subform, subreport, or embedded object, define a calculated field in the child object’s underlying query and set the LinkChildFields property to that field. When there are two or more
fields, separate each with a semi‐colon, e.g., “ID;Title”.
LinkMasterFields is the name of one or more linking fields (bound or unbound) in the parent form or report. It is analogous to a primary key. When there are two or more fields, separate each
with a semi‐colon, e.g., “ID;Title”.
It is common to link a subform to a single record presented in the main form. It is also possible to link a subform to the selected row in a list box.
How To Tell Which Form Subform Is In
If the subform has the focus, the property ActiveForm refers to the main form.
Control Data on Main Form with Popup Form
This approach is generalizable: the popup form can be used by more than one main form.
The popup form selects data that is then presented on the main form.
Popup form has two hidden text fields: main form name and control name (to hold the selected data) on main form. The main form sets the values of these fields before opening the popup form.
Popup form (1) prompts user for selection criteria, (2) selects data and stores it in a temporary table, (3) sets RowSource property of the control whose name is in the hidden text field = temporary table, (4) closes itself, and (5) sets focus on the main form (by using the hidden text field value).
Dim strSQL As String strSQL = "SELECT . . . INTO temptable FROM . . . WHERE . . ."
. . . dbs.Execute strSQL2
Dim ctl As Control
Set ctl = Forms(txtFormName).Controls(txtControlName) ctl.RowSourceType = "Table/Query" ctl.RowSource = "temptable" ctl.Requery dbs.Close
Dim strFormName As String
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 77 of 119
Microsoft Access Techniques
strFormName = Me!txtFormName.Value
DoCmd.Close
Forms(strFormName).SetFocus
Resequencing Records in a Single Record Form
The default record sequence is that of the record source. It is possible to resequence the records in an open form. You can do this by selecting a field and using menu Records, Sort, Sort
Ascending/Descending. This can only be done for one field at a time.
Note that memo fields are sorted only on the first 255 characters.
If you want to sort on multiple fields in Form view, or sort on non‐adjacent columns in Datasheet
View, or specify a column order other than the default left‐to‐right order in Datasheet view, use the Advanced Filter/Sort dialog box accessed with menu Records, Filter, Advanced Filter/Sort.
While the dialog box is open, use menu Filter, Apply Filter/Sort to apply the sort, then close the
filter/sort box. The special sort can be removed with menu Records, Remove Filter/Sort.
In VBA there seems to be at least two ways of doing this: a. You can change the underlying record source. Be sure the new query contains all the fields in the form. Any changes to the record source are not saved when you close the form, so that when you re‐open the form it uses its original record source.
Private Sub cmdResequence_Click()
Me.RecordSource = "JobsByEmployer"
Me.Requery
End Sub
b. You can use the OrderBy property of the Form object. The down side to this approach is that the form’s OrderBy property is changed permanently, not just while the form is open.
Consequently, when you re‐open the form, the record sequence will be the same as when it was last open—after the OrderBy property was changed.
OrderBy = "ContactName DESC"
OrderBy = "Job, Contact Name"
The property must be applied separately: frmMaine.OrderByOn = True
It may be necessary to have an index on the table to facilitate the different sequence.
Resequencing Records in a List Box or Combo Box
The default record sequence is that of the list box’s row source. It is possible to resequence the records using the underlying row source. It would be great if the user could just click a column heading to change the order, but column headings cannot have events. There are several other approaches:
(1) Replace the list box with a sub‐form datasheet. The datasheet allows the user to resize the columns and easily the sort or filter the data. The disadvantage is that the only way to highlight an entire row is to click on the Record Selector (not obvious).
(2) Use the VB list box control. This is probably available as an ActiveX control: Microsoft
ListView Control version 6.0.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 78 of 119
Microsoft Access Techniques
(3) Use a separate control: Set the listbox headers to No, create wide thin command buttons across the top of the listbox columns to look like headers, create OnClick code for each button to change the listbox recordsource to correspond with the desired sequence.
(4) Use a separate control: Keep the list box column headings and use a group of radio buttons, one button for each sequence. The default button is the one that corresponds to the default list box sequence. When the user clicks a button, the group’s OnClick event changes the record sequence.
Use the Option Group Wizard to assign a value to be returned when a radio button is clicked:
Sub optGrpSequence_OnClick()
Dim rst as Recordset
Set rst = Me.Recordset
Dim curRec as Variant
Set curRec = rst.Bookmark
Select Case optGrpSequence.Value
Case 1 ' ID
Me.RecordSource = "RecruitersByID"
Case 2 ' Name-Agency
Me.RecordSource = "RecruitersByNameAgency"
Case 3 ' Agency-Name
Me.RecordSource = "RecruitersByAgencyName"
Me.Requery
Me.Bookmark = curRec
End Sub
With Separate Control
You could have one query to reflect each possible record sequence; be sure the new queries contains all the fields in the list box and in the same order. The query for the default sequence is used for the list box’s row source. Use a separate control to allow the user to select a different sequence. That control’s OnClick event can change the list box’s row source to the corresponding
query and then requery the list box.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 79 of 119
Microsoft Access Techniques
Sub cmdResequence_OnClick()
Me.lbGadgets.Rowsource = "GadgetsByDate"
Me.lbGadgets.Requery
End Sub
Selection Criteria Form, version 1
One or more of several fields can be used to provide selection criteria.
Each criterion has a checkbox and a list box of possible values. The sample code is based on a list box that is not multi‐select.
Form has no data source. It allows record editing but no additions or deletions.
Checkbox has properties: DefaultValue = False. Checkbox Click event procedure enables the corresponding list box when the checkbox is enabled and vice versa
(listboxcontrol.Enabled = checkboxcontrol). lbDateAssigned.Enabled = CheckDateAssigned
Set ctl = Me!lbDateAssigned
Call Deselect(ctl)
. . .
Private Sub Deselect(lbctl As ListBox)
Dim n As Variant
With lbctl
If .ItemsSelected.Count = 1 Then
n = .ItemsSelected(0)
.Selected(n) = False
End If
End With
End Sub
List box has properties: Enabled = No; Locked = No; BoundColumn = 0 (unbound);
RowSourceType = Table/Query; RowSource = SQL select statement (like “SELECT
DISTINCT fieldname FROM queryname”).
Select command button: Click event builds SELECT query and (1) saves it as QueryDef or
(2) saves results in new temporary table (done as “SELECT INTO”). strSQL = "SELECT . . . " & _
"INTO [TempSelection] " & _
"FROM AssignedExceptions " strSQLWHERE = " WHERE "
With lbAssignee
If .ItemsSelected.Count = 1 Then
n = .ListIndex
selData = .Column(0, n)
strSQLWHERE = strSQLWHERE & " [Assigned To] = """ & selData & """"
End If
End With
. . . strSQL = strSQL & strSQLWHERE dbs.Execute strSQL dbs.Close
The remaining work is to make the query results visible. My first attempt at this is based on
(1) embedding the selection criteria form as a subform, (2) saving the selected records as a temporary table, (3) displaying the results in a list box (originally with no data source), and
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 80 of 119
Microsoft Access Techniques
(4) using a command button to attach the results to the list box (by setting its RowSource property = table name and using the Requery method). NOTE any temporary table must be deleted before closing the form; if displayed in a list box, it must be first removed from the list box (by clearing the RowSource property).
Selection Criteria Form, version 2
This form is used only to accept and edit selection criteria and to execute the query. The query stores the results in a temporary table TempSelection. The results are displayed in a list box in a separate form, what I call the action form.
The select form is designed to be used with several different actions forms.
Approach:
1. Action form opens. Its list box has no data source.
2. Select form opens. as modal and popup, with no control box or close box opened by action form’s OnOpen event, which hides the action form name and list box control name in invisible text boxes on the select form
3. Select form edits selection criteria. When no errors, it builds the SQL statement as a
“SELECT INTO” and runs it. Then it changes the list box’s data source to the temporary table, and closes itself.
4. The action form reappears from behind the closed select form with the selected data in place.
5. The user interacts with the action form. If the user cancels the action, the Cancel button’s
OnClick event deletes the temporary table. If the user completes the action, the completion process ends with deleting the temporary table and closing the form.
Code in action form to open the select form:
Private Sub Form_Open(Cancel As Integer)
DoCmd.OpenForm "SelectionCriteriaPopup"
Forms!SelectionCriteriaPopup.txtFormName = "RemoveAssignments"
Forms!SelectionCriteriaPopup.txtControlName = "ListTempSelection"
End Sub
Code in select form after select query run:
. . .
Dim ctl As Control
Dim s As String
Set ctl = Forms(txtFormName).Controls(txtControlName) ctl.RowSourceType = "Table/Query" ctl.RowSource = "TempSelection" ctl.Requery dbs.Close
DoCmd.Close
End Sub
Private Sub cmdCancel_Click()
Dim strFormName As String strFormName = Me!txtFormName.Value
DoCmd.Close acForm, strFormName, acSaveNo
DoCmd.Close
End Sub
Code in action form after action completed: response = MsgBox("Assignments removed.", vbOKOnly, "BRT Exceptions")
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 81 of 119
Microsoft Access Techniques
Call cmdCancel_Click
Code in action form when cancelled:
Private Sub cmdCancel_Click()
Dim ctl As ListBox
Set ctl = ListTempSelection
With ctl
.RowSourceType = ""
.RowSource = ""
.ColumnHeads = False
.BoundColumn = 0
.Requery
End With
Dim strSQL As String strSQL = "DROP TABLE TempSelection"
Dim dbs As Database
Set dbs = CurrentDb dbs.Execute strSQL
DoCmd.Close
Exit Sub
Using OpenArgs
Forms can be passed a string argument when they are opened by code. See the FileOpen method on page Error! Bookmark not defined. for its details.
Essentially, formA opens formB:
DoCmd.OpenForm "Subject", acNormal, , , acFormAdd, , "Add"
FormB tests for the argument and acts on it. In this case formB is used to add a single record, then close itself. Normally, it would stay open after the record save.
Private Sub cmdSave_Click()
DoCmd.RunCommand acCmdSaveRecord
Dim how As String
If IsNull(Me.OpenArgs) Then Exit Sub how = Me.OpenArgs
If Len(how) > 0 Then
If how = "Add" Then
Dim ctl As Control
Set ctl = Forms!Subjects!SubjectList
ctl.Requery ' refresh list box to include new subject
DoCmd.Close ' close this form
End If
End If
End Sub
Positioning an Open Form on a Different Record
When a form opens it is normally positioned on the first record in the underlying record source.
What if you want to (1) open the form so that it is positioned on a particular record or (2) reposition an open form on a particular record? Either can be accomplished by (1) positioning a
clone of the underlying recordset on the particular record with Find or Seek and (2) setting the form’s Bookmark to the recordset’s Bookmark.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 82 of 119
Microsoft Access Techniques
The key player in this process is the recordset which corresponds to the form’s record source.
The recordset, either DAO (the native Access library) or ADO, has methods Find and Seek which can be used to locate the particular record. It also has a Bookmark property which uniquely
identifies the current record.
Choosing Find or Seek:
You can only use Seek with recordsets of type table: when the record source is a table or a
single‐table query and the search field is indexed.
You can use Find for recordsets of type dynaset: when the record source is a multi‐table query.
Seek can be easier to code, but is limited to indexed table recordsets.
About form recordsets:
The form.Recordset property returns a recordset object representing the record source for the specified form, report, list box control, or combo box control. Read/write. Caution: changing which record is current in the recordset returned by the formʹs Recordset property also resets the current record of the form.
The form.RecordsetClone property returns a recordset that is a copy of the form’s underlying table/query. Read‐only. When a new recordset object is opened, its first record is the current record. If you apply a filter to the form, the recordset object reflects the filter. The recordset object is available only as long as the form is open. You use the RecordsetClone property to navigate or operate on a formʹs records independent of the form itself.
You can synchronize the formʹs current record with the recordsetʹs current record: By setting the form’s Bookmark to the cloned recordset’s bookmark, the current record in the recordset is made current in the form, i.e., displayed in the form.
Dim rst As Recordset
Set rst = Me.RecordsetClone rst.Index = "ID" rst.Seek "=", curID
If rst.NoMatch = False Then
Me.Bookmark = rst.Bookmark
End If rst.Close
Dim rst As Recordset
Set rst = Me.RecordsetClone rst.FindFirst "ID" = curID
If rst.NoMatch = False Then
Me.Bookmark = rst.Bookmark
End If rst.Close
' best performance if field is indexed
In the two preceding examples, you can use operators other than “=”: >, <, <>, LIKE, etc. You can
also do substring comparisons.
So, to reposition an open form:
1. Establish a cloned recordset.
2. Find or Seek the desired record in the recordset.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 83 of 119
Microsoft Access Techniques
3. Set the form’s bookmark to the recordset’s bookmark.
4. Close the recordset.
To open a form so that it is positioned on a particular record:
1. Pass the key of the particular record to the form in OpenArgs (of DoCmd.OpenForm).
2. The form’s OnOpen event checks for the presence of OpenArgs.
3. If it finds it, it uses the Recordset property to open a recordset, then Find or Seek to position on the desired record, then closes the recordset.
4. If it does not find it, it does nothing.
There is another technique—using DoCmd.OpenForm with the Where parameter—which can work even if the form is already open, but is known to have odd behaviors.
Note that using the Filter property is not suitable as it will not provide access to the entire table/query once the form is open.
Events
Event category
Window events
Events
Close, Load, Open, Resize,
Unload
Occur when
A user or code opens, resizes, or closes a form or report.
Focus events
Data events
Activate, Deactivate, Enter,
Exit, GotFocus, LostFocus
AfterDelConfirm, AfterInsert,
AfterUpdate,
BeforeDelConfirm,
BeforeInsert, BeforeUpdate,
Change, Current, Delete,
NotInList, Updated, Dirty
Click, DblClick, MouseDown,
MouseMove, MouseUp
Objects receive or lose the focus, or become active or inactive.
A user or code enters, deletes, or changes data in a form or control, or moves the focus from one record to another.
Mouse events
Keyboard events KeyDown, KeyPress, KeyUp A user types on the keyboard, or keys are sent using the SendKeys action or the
SendKeys statement.
Print events
Filter events
Format, NoData, Page, Print,
Retreat
ApplyFilter, Filter
A report is being printed, or is being formatted for printing.
A user creates, applies, or removes a filter for a form.
Error and Timing events
Error, Timer
A user performs a mouse action, such as clicking or double‐clicking.
Access or the Jet database engine encounters an error, or a specified time interval passes.
You can cancel the following events:
ApplyFilter Delete
BeforeDelConfirm Dirty
MouseDown (only the right mouse button, and only with
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 84 of 119
Microsoft Access Techniques
BeforeInsert
BeforeUpdate
DblClick
Exit
Filter
Format a macro)
NoData
Open
KeyPress (only with a macro) Print
Unload
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.
Form Events
The following definitions are from the help file.
Error
event occurs when a run‐time error is produced in Microsoft Access when a form or report has the focus. This includes Microsoft Jet database engine errors, but not run‐time errors in Visual
Basic or errors from ADO.
Open:
The Open event occurs when a form is opened, but before the first record is displayed. For reports, the event occurs before a report is previewed or printed.
Load:
The Load event occurs when a form is opened and its records are displayed. (In the case of a form with slow‐to‐complete queries as the source of data controls, this event occurs before the data appears in the controls.)
Activate:
The Activate event occurs when the form or report receives the focus and becomes the active window. (I could not engineer a situation when this event occurred)
Current:
The Current event occurs when the focus moves to a record, making it the current record, or when the form is refreshed or requeried. (In my test, this happened before form appeared.)
Dirty
occurs when the contents of a form or the text portion of a combo box changes. It also occurs when you move from one page to another page in a tab control. Canceling the Dirty event will cause the changes to the current record to be rolled back. It is equivalent to pressing the ESC key.
If you need to test this, use your own variable. The Dirty event does not fire for new record.
Close
occurs when form is closed and removed from the screen.
Unload
occurs when the form is closed but before it is removed from the screen.
BeforeUpdate
event occurs after you have entered the new or changed data in the current record and before the record is saved.
Delete:
Occurs when the user performs some action, such as pressing the DEL key, to delete a record, but before the record is actually deleted.
Timer:
The Timer event occurs at regular time intervals as specified by the formʹs TimerInterval property (stated in milliseconds ).
LostFocus
event occurs when form loses the focus, applicable when focus switches from form to subform and vice versa. Applies to form only when it has no visible or enabled controls.
Deactivate
event occurs when you switch between two open forms. It cannot be cancelled.
Followed by Activate event in second form. Also fires between Unload and Close.
Opening a form
Open . . . Load . . . Resize . . . Activate . . . Current
The form does not become visible until after activate.
“If youʹre trying to decide whether to use the Open or Load event for your macro or event procedure, one significant difference is that the Open event can be canceled, but the Load event
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 85 of 119
Microsoft Access Techniques
canʹt. For example, if youʹre dynamically building a record source for a form in an event procedure for the formʹs Open event, you can cancel opening the form if there are no records to display.”
The OnLoad event procedure can carry out actions for the controls or records on a form after the form opens. For example, you can specify default settings for controls.
When you first open a form with data
Open . . . Load . . . Resize . . . Activate . . . Current . . . Enter (control) . . . GotFocus (control)
Closing a form
Unload . . . Deactivate . . . Close
Closing a form with X button
Exit (control) . . . LostFocus (control) . . . Unload (form) . . . Deactivate (form) . . . Close (form)
If youʹve changed data in a control, the BeforeUpdate and AfterUpdate events for both the control and the form occur before the Exit event for the control.
Creating a new record
Current (form) . . . Enter (control) . . . GotFocus (control) . . . BeforeInsert (form) . . . BeforeUpdate
. . . AfterInsert (form) . . . AfterUpdate . . .
Deleting a record
Delete . . . BeforeDelConfirm . . . AfterDelConfirm
Move to another record
BeforeUpdate . . . AfterUpdate . . . Exit (control2) . . . LostFocus (control2) . . . RecordExit (form) . .
. Current (form)
Enter text in text box in new record.
KeyDown =. . . KeyPress . . . BeforeInsert . . . Dirty . . . KeyUp
Some events can be cancelled. One way is with the DoCmd.CancelEvent method. (The following events can cancel themselves: BeforeInsert, BeforeUpdate, DblClick, Dirty, Exit,
Open, Unload.) Another way to cancel an event procedure is as follows:
Private Sub ProductName_BeforeUpdate(Cancel As Integer)
If(Not IsNull(DLookup("[ProductName]", _
"Products", "[ProductName] ='" _
& Me!ProductName & "'"))) Then
MsgBox "Product has already been entered in the database."
Cancel = True
Me!ProductName.Undo
End If
End Sub
If you cancel an update, the value of the OldValue property replaces the existing value in the control.
Use the form’s Before Update event to perform ‐level validation, including checking for nulls. Use a control’s After Update event to programmatically fill in a separate control.
What I am looking for is which event it is that happens after the data contents of controls appear. I am creating a form with three list boxes, each with a source of a record count of a query. The queries take time to run, so the form opens and the boxes are blank for awhile until numbers appear. I want to know which event occurs when the numbers appear.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 86 of 119
Microsoft Access Techniques
I could not find an event that fit my needs. But the following code in the OnOpen event worked perfectly. The hourglass appeared until the form opened with all the data visible.
DoCmd.SetWarnings True
Dim x
If (Me.DiscrepancyA.Value = 0 And Me.DiscrepancyC.Value = 0 And
Me.DiscrepancyE.Value = 0) Then
x = “no”
Else
x = “yes”
End If
DoCmd.SetWarnings False
Form Control Events
AfterUpdate
BeforeUpdate
Dirty
Change
Click
DoubleClick
Enter
Exit
GotFocus
KeyDown
KeyPress
KeyUp
MouseDown
MouseMove
MouseUp after the changed data has been saved before the changed data has been saved; useful for validating data only for combo box and tab control, when the content changes after contents of a text box or the text portion of a combo box changes; it also occurs when you move from one page to another page in a tab control
can be cancelled
before control receives the focus from another control before control loses the focus to another control
Form Opens Blank
This happens when the form has a RecordSource and the data controls have no values. For instance if the form has text boxes with source of DCount() and the counts are all zero, the form opens blank—with nothing in it, only the border. One solution is to remove the RecordSource.
But . . . doing so means Me.Refresh does not work. However, if all the text controls are calculated
(i.e., with sources equal to functions like DCount), then Me.Recalc works. It may be necessary to add a command button to run the Recalc.
Slow Opening Form
When a form has controls whose source is a query, the queries are run one after another
, and the form is not opened until all the queries are done (I think). When a form takes a long time to open because source queries take a long time to run, the form has no values in its text box controls until the queries complete. This can be misleading. There are three ways to handle this:
1) Speed up the queries as much as possible.
1 This is because Access is single threaded.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 87 of 119
Microsoft Access Techniques
2) Move one or more queries to an OnEnter event of the combo box so they are not run when the form opens.
3) Display an hourglass until the form appears with data on it.
There are four techniques for option 1:
Make sure the sort and parameter fields in the queries are indexed fields in the underlying tables.
Limit the number of fields returned.
Set the ʺAuto Expandʺ property to No.
Use saved queries rather than SQL statements for the control’s record source.
There is one technique for option 2:
Leave the Row Source for the combo box blank in its property sheet. Then create an
“OnEnter” event procedure for the combo box. In that procedure set the combo boxʹs
RowSource to the desired query or table by specifying:
Me!ComboBoxName.RowSource = "queryortablename"
This will make the form load faster by not running the combo box query until the user requests the data.
There is one technique for option 3:
Find the query that takes the longest to open. Add an OnLoad event to the form like the following code. In the example InvalidTitles is the text box sourced with the slowest query.
Private Sub Form_Load()
' this code is to hold the hourglass while the text boxes are filling with slow-running
' queries.
DoCmd.Hourglass True
Dim x
If (Me!InvalidTitles.Value = 0) Then
x = “no”
Else
x = “yes”
End If
DoCmd.Hourglass False
End Sub
Form Suspends Itself
A form can be opened with window mode = Hidden. The Stop statement suspends execution, but unlike End, it doesnʹt close any files or clear variables; it can be placed anywhere in procedures. The UserForm has a Hide method which hides it without unloading it. A form has a
Visible property (boolean) which can be queried or set to affect the form. form.SetFocus moves the focus to a specified form. All in all it looks possible, needs testing to confirm.
Controlling One Form From Another
Open form before initializing contents:
DoCmd.OpenForm "formb"
Forms!formB!controlname = value expression
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 88 of 119
Microsoft Access Techniques
The second line happens after last form open event (onCurrent). So any programmatic use of that data must be done by an event procedure for a control.
Revision: 9/2/2012
Copyright 2001–2012 by Susan J. Dorey
Page 89 of 119
Herunterladen
Werbung