You are on page 1of 17

An Alternative List Box Control Method

or

A Beginners Guide to using a Subform control in place of a Standard List Box control

For Microsoft Access 2000 or later. Version 1.0 January 2014


By Peter Hibbs.

Introduction
The standard List Box control that comes with all versions of Access is very useful for providing a
list of items on a form and is generally easy and quick to set up. It also has the facilities to
select/deselect items on the list, select multiple rows, read which row/s are currently highlighted
and so on. However, it has very limited formatting facilities for text alignment, color coding,
graphics images, etc which tends to make it look a bit plain and boring in modern applications.

The accompanying (Access 2003) demo database shows a method of providing a better looking
alternative to the normal List Box control by using a standard Subform control instead. Using this
method is a little bit more complicated to set up but the resultant Graphical User Interface (GUI)
provides a better experience for the application users and can also provide more facilities than the
normal list box.

The demo database is based on the ‘Northwind’ example database from Microsoft (the table names
and some field names have been changed slightly although the data itself is unchanged) and
consists of several forms that each use a subform control to emulate a list box with various
different facilities and formatting options. The standard list box also has a number of facilities
which can be used with some VBA code to read data from the control, set/clear the highlighting on
one or more rows, count the total number of rows or the number of highlighted rows, etc, etc. To
provide all these facilities with a subform control is a little more complicated so a set of VBA
procedures has been provided in a separate code module which can be called with (usually) one
line of code that can perform the same functions as the list box equivalent (see below for more
details).

To try out the demo database just run it is normal and select each option in turn on the main
Switchboard. The function of the various fields and buttons are described in more detail below.

Create a Basic Subform


To use a subform control as a list box you should first create a new form, set up various properties
on the form and save the form, this form will then be copied onto your main form and be used as
the list box control. You can give it any name you like but it is usually a good idea to give it the
same name as the form on which it will be used but with the suffix ‘sub’ (or you could, perhaps,
use a suffix like ‘lst’ to indicate that it is a subform being used as a list box on the said form) but
try and keep the name fairly short because you will probably need to enter the subform name in the
VBA code many times. Alternatively, if one of the subforms in the accompanying demo database
is very similar to what you need, you could just import that form, change the necessary fields and
rename it, as required.

When you have created the form and saved it you should set up various properties as described in
the next section and save it again, these basic changes will be much the same for all the examples
shown below. Where additional changes are required they are specified in the appropriate section.

Page 1 of 17
To make the basic changes open the form in Design mode and set up the properties as follows :-

1. Record Source. Set this property to the table or query or SQL string that will supply the
data for the list. A query is preferred to a table here because it gives you a bit more control
over the listed data such as sorting, criteria, etc.
2. Default View. Change this property to ‘Continuous Forms’.
3. Allow Datasheet View, Allow PivotTable View and Allow PivotChart View. Change
these three properties to ‘No’. If you don’t change these, the user could right-click on the
list box and change the form view to a different type (which you would not normally want).
4. Allow Edits, Allow Deletions and Allow Additions. Change these three properties to
‘No’. Normally you cannot edit records in a list box but if you do want to allow this then
you will need to leave these as ‘Yes’ (but then it just becomes a normal subform which
rather defeats the object of this exercise).
5. Scroll Bars. Change this property to ‘Vertical Only’. Depending on how you want the list
box to work, you could also use ‘Neither’ or ‘Both’ but generally a list box only has a
vertical scroll bar.
6. Record Selectors and Navigation Buttons. Change these properties to ‘No’. A list box
does not need these.
7. Dividing Lines. Change this property to ‘No’. You could leave this one as ‘Yes’ which
will then show a line below every row but the list generally looks tidier without those lines
(especially as you cannot change the line color to anything but black).
8. Cycle. Leave this property as ‘All Records’. By doing this (and adding one line of code to
the form’s Form_Current event) the user can use the Arrow keys to move the highlighted
row up and down the list. If you do not require this option then change the property to
‘Current Record’.
9. Tag. Leave this property blank or change it to 0, 1 or 2 (see below for more details).

The remaining properties can be left as their default values.

In addition to the above form properties you should also enable the Form Header/Footer option
on the menu or ribbon to display those form sections. The Header section of the form would
normally hold the column headings (unless you don’t need them) and the Footer section may or
may not be required, depending on how you want the list to look. On the first example the Footer
section is used to display the grand sales total but on the other examples the Footer section is
hidden. If you don’t need either section just click on the Form Header or Form Footer bar and
set the Visible property to ‘No’. You should also click on the Detail bar and change the Back
Color property to white, if you don’t the default form back color will show at the end of the list
which looks kind of messy.

At this point you should add the fields and controls to the Detail section of the form but since these
can vary considerably, depending on the fields and layout, this procedure is covered in more detail
below. Assuming that you have the subform set up with the required fields, etc you can now add
the subform to the main form. To do this open the main form in Design mode and drag the
subform onto it in roughly the required position. Delete the subform label and change the height of
the subform to roughly the size required (the width is usually the right size anyway).

You can now change some properties of the subform control itself. Click on the control and change
the Border Width property to ‘2 pt’, you can use different values but this setting looks the best
(but it depends on what sort of look you want for your form, of course). Now position the subform
in the required position on the main form and adjust the height, as required. Next, double-click on
one of the ‘sizing’ boxes around the edge of the subform which will automatically set the size of
the form to fit the controls within the form (you may need to re-adjust the height again).

Page 2 of 17
 Basic (Non-interactive) List Box
The standard List box control can be set up in a number of different ways, it can be used to just
display a single list of items or it can display multiple columns or the user can click on items to
highlight one or more rows, etc, etc. The first example on the Switchboard menu is the simplest
type, a list of employee sales figures for each year where the user does not need to interact with the
list items.

When you have set up the basic subform (as described above) and set up the query with the
required fields you should drag them onto the form and position them horizontally in the required
order (open the form frmYearSalesSub in Design mode to see how it works). The Detail section
height should be made the same as the height of the Text box controls so that there is no gap
between rows. You would also set up the Text box control properties for the fields like this :-

1. Enabled = ‘No’.
2. Locked = ‘Yes’.
3. Border Style = ‘Transparent’. You could also make this ‘Solid’ if you want boxes around
the controls but this does not look much like a standard List box then.
4. Back Style = ‘Transparent’. In this example this option does not really matter but for the
later examples, where a row is to be highlighted in a different color, it is necessary.
5. The Text box formatting properties can be set up, as required. In this example the first field
is shown as Bold and Center Aligned, the second field is Left Aligned and the third field
is Right Aligned. You would obviously set up your own fields to suit the data used.
6. Name. The Text box names have been changed to Column0, Column1 and Column2 in this
example. Again, this is not necessary for this list box because there is no user interaction
with the controls but in the later forms the names are used to identify the various columns.
7. Conditional Formatting. If you want to change the formatting of some fields depending
on the data in the field (or another field) you can use the standard built-in Conditional
Formatting facilities (although not used in this example).

On a standard List box control a vertical line is displayed between each column (where there are
multiple columns). The first two examples on the Switchboard menu also show these lines which
is done by adding a small vertical grey line between the three Text box controls. However, this is
quite fiddly to set up so on the last two examples they are not provided and the display still looks
pretty good and is much easier to design. You will have to decide which option you prefer.

As you can see in this example, the Header and Footer sections of the subform have been made
visible and the BackColor changed to pale blue, the Header holds the column labels and the
Footer holds a Text box control to display the total sales figures. The Combo Box control on the
main form is used to select all years or a specific year and some VBA code in the AfterUpdate
event of the Combo box is used to refresh the subform control data like this :-

Me.frmYearSalesSub.Requery 'refresh subform

The procedure for setting up the subform controls for the remaining examples is very similar to the
above, except where otherwise stated.

 Single Row Selection List Box


The second example (Order Totals by Customer) shows a list box where the user can select one
item at a time in the list and the selected row is highlighted in yellow. Also, there are facilities
included to read the Index number of the highlighted row, read the data from any column, set the
Index, etc, etc which are basically the same as those provided by the standard list box control. To
make this coding easier there are a group of functions provided in a separate code module which
simulate the functions that come with the standard List box control (although you can, of course,

Page 3 of 17
create your own if you prefer). The group of controls at the right of the form are just there so that
you can test the various facilities and see the VBA code required to perform those tests, see below
for more details).

To provide these extra facilities you will need to add three more Text box controls and one button
to the subform and you will need to use the control names shown below (assuming you want to use
the VBA functions provided).

To set up a subform to be used as a list box that can have items selected or processed you should
first create a new form and add the query and fields to the form, as described in the previous
sections. Where you have multiple columns the Text box names, from left to right, should be
changed to Column0 to ColumnX (where X is the last column number). As before, you can add the
column labels to the Header section and, for this example, make the Footer section invisible.

Important Note: In order to be able select an individual row in the list you must have one unique
numeric field in the query, i.e. a field that has a different value in each row (although it does not
necessarily need to be visible in the list). Usually you would have an AutoNumber type field in a
table which could be used, although it depends on the data format. In this example, the data being
displayed is a list of orders for each customer so the unique value here is the Order No field
(OrderID in the query) and so this field is used as the row identifier field (see below for details).
In addition to that you need to also do the following :-

1. Add two Text box controls to the Footer section and name them txtSelect and txtIndex.
You can delete the associated labels if you wish although it will not affect the operation
either way. If you need to have some of your own controls in the Footer section you should
keep the Footer section visible and make these two new Text box controls invisible. Set
the Default property of the txtIndex control to 0.
2. Add another Text box control to the Detail section, change the Name property to txtRef
and delete its associated label, this control which will act as the highlight control. If you
have already added the main data fields to the form then the easiest way to do this is to
make the height of the Detail section of the form bigger (by dragging the Form Footer bar
down a centimetre or so) and then temporarily placing the new txtRef control below the
others. Now make the width of this control the same width as the form (or however wide
you want the highlight area to be) and make the height of the control the same as the height
of the other controls. Change the ForeColor and BackColor properties to white (or
whatever background color you are using) so that any text in the field will not show up in
the list. Make the Border Style property ‘Transparent’ and make sure the Back Style
property is ‘Normal’. Next, set the Control Source property to the name of the unique
numeric field (OrderID in this example), note that you can also use this field in one of the
displayed fields if you wish, as it is in this example. (Note: This unique field could be set
up as a text type field but if the database has several List box controls that use this method
it would get very complicated to write common functions that could handle Text and
Numeric values so it is simpler to only use Numeric values as the unique identifier field).
Now click on the Conditional Formatting icon on the menu or ribbon and set up the
format parameters as shown in Fig 1 below. When that is done, move the Text box control
up to the top of the Detail section so that it covers the other existing controls and then click
the Send to Back icon on the Format Menu or Ribbon to place it behind the existing
controls. Provided the Back Style property of the other fields is ‘Transparent’, the color in
this control will show through the other controls.
3. Add a new button to the Detail section and make the width the same as the width of the
subform and the height the same as the Text box controls. Change the Name property to
‘cmdSelect’ (without the quotes, of course) and change the Transparent property to ‘Yes’.
Now move the button up to cover the existing Text boxes and then click the Bring to
Front icon on the menu or ribbon to ensure that it is placed above those controls. The

Page 4 of 17
reason for this button is that the user will need to be able to click on a row in the list box in
order to select and highlight it and if the various Text box click events were used for this
we would need to repeat the same event code several times. Also, the mouse cursor would
change to the I cursor (instead of the standard arrow icon) which does not look right for a
list box control and so a transparent button gives a better effect and makes it easier to write
the necessary VBA code. In the cmdSelect_MouseDown event for this button add this
line of code - SelectRow Me, Shift 'highlight selected row
See below for more details on adding VBA code for the list boxes. With all the controls
now added to the Detail section you can set the height back to the original size so that there
is no gap between the rows (unless you really need a gap, of course).
4. Change the subform’s Tag property to 0 (note that this is the Tag property for the form
itself and not the subform control, see the example forms if there is any doubt). The
standard list box control has three ‘modes’ which are defined by the Multi Select property
and these are ‘None’, ‘Simple’ and ‘Extended’ (which equate to 0, 1 and 2 in VBA coding).
In order to simulate these three modes the subform’s Tag property is used here to act as the
list box Multi Select property and the associated code functions check this property when
called from the main form and perform the necessary actions. This makes it very easy for
the programmer to switch from one mode to another just by changing the value in the Tag
property. Since this example does not require multiple selections the Tag property should
be set to 0 (note that on the first example described above, the form did not have any user
selection facilities which meant that as the code functions were not used, the Tag property
was not used there).

Row Highlighting Format


To provide the row highlight color for the selected row you need to enter the Conditional
Formatting parameters for the txtRef Text box control via the Format menu or ribbon. The
Conditional Formatting form (for Access versions prior to Access 2010 anyway) looks
something like Fig 1 below.

Fig 1. Conditional Formatting settings for the txtRef control.

Select Expression Is for the condition and enter the following into the parameter field :-

InStr(1,[txtSelect],”(“ & [txtRef] & “)”)>0

Select the color you want on the highlighted line/s for the BackColor and ForeColor icons (in this
case it is yellow but you can, of course, choose any other color). By making the background and
text colors the same, any text in the txtRef field will not show when a row is selected. Click OK to
save the settings and close the form. Note that the ‘comparison’ parameter could be simplified a bit
for this situation (where only one row at a time is highlighted) but the following examples that
require multiple rows to be selected together will need this code so it is simpler and more flexible
to just use the same code on all examples.

Page 5 of 17
How Does It Work?
When you open the second example form (Order Totals by Customer) you can see that by clicking
on a row in the list, the background for that row changes to yellow and you can click on any other
row so that only one row at a time is selected. Also (as mentioned above) you can use the UP and
DOWN arrow keys to move the highlighted row up and down the list.

When the user clicks on a row in the list the VBA code in the MouseDown event for the hidden
button (cmdSelect) calls the SelectRow sub-routine in the code module (see below for more
details) which simply copies the unique numeric value from the txtRef field for that row to the
txtSelect control in the Footer section of the subform. The reason that the MouseDown event is
used here, rather than the normal Click event, is because the MouseDown event triggers when the
button is pressed which then moves the highlighting from the current row to the new row
immediately whereas the Click event only triggers when the mouse button is released which does
not look quite so good. Also, using the MouseDown event, has the added advantage that you have
access to the x co-ordinate of the mouse which you could use to determine which column the user
clicked on and then take some action based on the column number.

The Conditional Formatting code for the txtRef field on the row compares the contents of the
txtRef field with the contents of the txtSelect control in the Footer section and if they match the
txtRef field colors are changed to yellow (or whatever color you chose) which then shows through
the other fields for that row. To see this code working you can temporarily unhide the form’s
Footer section and you can see the contents of the txtSelect control change as you click on
different records. The Index control should also change to show the current index value of the row
although this data is not actually used for this type of list, see below for more information. Note
also that the value in the txtSelect control is wrapped in brackets, while not strictly necessary for
this type of list it is required for the multiple selection list types as described later.

Adding the VBA code


In order to use the group of code sub-routines and functions provided you should first import the
module modSubformFunctions into your database. The exact method used to do this will vary
slightly depending on which version of Access you are using so see the built-in Help facilities for
details. After you have imported the module you should then Compile the code to ensure that there
are no compile errors.

To update the txtSelect and txtIndex controls in the Footer section when the user clicks a row you
need to add just one line of code to the MouseDown event for the hidden button (cmdSelect) which
will look something like this :-

SelectRow Me, Shift

The SelectRow sub-routine in the imported code module has code which updates the two control
fields with the data from selected row and the value in the Tag property is used to determine which
type of list is being used. The identity of the subform is passed to the sub-routine as the Me
parameter so that you could have several list boxes on different main forms that all use the same
sub-routine. The Shift parameter from the MouseDown event is also passed to the sub-routine
which is not actually used for this particular list box type but is used for one of the multiple
selection types (see below).

This is basically all the code you need to highlight a row when the row is clicked on. However, if
you need the facility to move up and down the list using the keyboard arrow keys you should also
add the same line of code to the subform’s Form_Current event like this :-

SelectRow Me, 0

Page 6 of 17
Note that the only difference here is that the second parameter is set to 0 since there is no Shift
value to send to the sub-routine (and this parameter is not required for this type of list anyway).

In addition to the above code you could, of course, add further code to provide other facilities for
the users. For example, if you double click on a row a form will pop up to show the current Order
No of the row and in your own project you could easily extend this to do other things. Have a look
at the subform (frmOrdersListSub) VBA code to see how this works.

One other item to note is that when a Continuous type form is displayed the first row normally gets
the focus which in this example would highlight the first row (because of the code in the subform’s
Form_Current event). If you do not want this to happen (and it would not normally happen with
a standard list box control) then you should also add a line of code to the Form_Open event of the
main form which will disable this option, something like this :-

Me.frmOrdersListSub!txtSelect = "" 'clear initial selection

You will need to replace the subform name above with your own subform name, of course.

How to Use the Sub-Routines and Functions Provided


The various buttons and Text boxes on the example forms can be used to test the sub-routines and
functions that have been included in the imported code module and which simulate the equivalent
functions that are available in a standard list box control, where possible. You would not normally
need these controls on your own forms, of course, but they can be used to see how the code works
and most of those only require one line of code.

The functions listed here are for the second example form (Order Totals by Customer), the
examples below for multiple row selection have some others which are described in the following
sections. In all the code examples below a reference to the subform being used as the list box must
be passed to the sub-routine or function as an in-line parameter. In the descriptions below these are
just shown as Me.SubformName which you would replace with the name of your subform. See
the VBA code in the main forms to see how it works in practice.

List Count Function


The ListCount function for a list box returns the total number of records shown in the list. To
provide this same facility with the subform list box you can use the GetListCount sub-routine
like this :-

x = GetListCount(Me.SubformName) ‘fetch record count

On the example form you can click the Get List Count button to display the total number of
records in the list in the associated Text box.

List Index Function


The list box ListIndex function would normally return -1 if no rows are selected or a value
between 0 and the number of rows -1 for the currently selected row. To do this you can use the
GetListIndex function like this :-

x = GetListIndex(Me.SubformName) ‘fetch current index

On the example form click the Get List Index button with no records selected which will then
display -1 in the associated Text box and then select a row and try again to show the Index number
for that row (which is zero based, of course).

Page 7 of 17
Select / Deselect Row Procedure
To select a row and make it the current row you can use the RowSelected procedure (the
standard list box equivalent to this function is lstbox.Selected(n) = True (or False to
deselect a row) where n is the row index number. For the subform version the procedure call is
something like this :-

RowSelected Me.SubformName, RowNumber, True ‘select specified row

where RowNumber is the number of the row to be highlighted (and remember that the row
numbering starts at 0) and the third parameter is set to True to select the specified row or set to
False to de-select the specified row.

To try this out on the example form you can enter a row number (between 0 and 829 for this list) in
the Row field and click the Set Selected button to highlight the selected row. If the selected row is
not already visible on the form the list will be moved so that the new selected row is at the top of
the subform (or within the subform if the row number is less than the number of visible rows).
Note that if you try and select a row number that is not valid (i.e. higher than the number of rows
in the list) the command is just ignored.

As mentioned above, you can also de-select an already selected row by using False as the last
parameter and you can also test if a particular row is selected or not (see the next form example
below for more details).

Get Column Data Function


To read the data from a specified column for the current row you can use the GetColumnData
function. As described above, you should first make sure that the fields on the subform are named
Column0 to Column7 (or however many columns there are) and then you can use the column
number to identify which column you want to check (in the same way as the standard list box
does). The function call would look something like this :-

x = GetColumnData(Me.SubformName, n) 'get column n data

where n is the number of the column you want to read (between 0 and the highest number
column). Note that the returned data is a Variant type (since the column data could be any type)
and so you may need to format the data before copying it into a Text box or whatever.

To try this out on the example form first enter a number between 0 and 6 in the Col field, click on
a row and then click the Get Column Data button. The contents of the selected column for the
highlighted row will be displayed in the Data field below the button, if a row is not selected or the
column number is not valid, a Zero Length String will be returned. Note that you can format the
returned data in the normal way, for example, if you click on columns 5 or 6 the currency fields
will return just the numbers so if they are being displayed on a form or report you may need to
format them first, i.e. something like this :-

x = Format(GetColumnData(Me.SubformName, n),”Currency”)

Page 8 of 17
Get Column / Row Data Function
The previous function can be used to return the data for a column in the currently selected row but
the standard list box control also has the option to fetch the data from any column in any row and,
for the subform version, this facility is provided by the GetColRowData function. It is similar to
the previous function except that you can specify the column and row numbers and it does not
matter if a row is selected or not. The function call would look something like this :-

x = GetColRowData(Me.SubformName, Col, Row) 'get col/row data

where Col is the column number and Row is the row index number.

As in the previous function, a Variant type data is returned and the Column and Row numbers
must be valid, a Zero Length String is returned if they are not valid numbers. On the example form
you can test this out by entering valid column and row numbers in the Col and Row fields and
clicking the Get Col Row Data button to display the field data in the Data field below the button.

Set Row Value Procedure


On a standard list box control you can highlight a row by setting the ‘bound’ column to a value
that exists in one of the rows. The equivalent command for the subform method is the SetValue
procedure and the calling code would look something like this :-

SetValue Me.SubformName, n 'set list row to selected value

where n is an existing numeric value in the reference field.

For the subform list box the unique numeric value that is used in the txtRef control acts like the
‘bound’ column on a standard list box control and this is the value that you would pass to the
procedure in order to select that row. In other words, if the reference field for the subform list is
the OrderID field (as it is in the example form) then you would pass the required OrderID value
to the sub-routine to highlight that row in the list.

You can test this facility on the example form by entering a valid Order No into the Value field
(10682 for example), pressing the TAB key and then clicking the Set Row Value button to
highlight row 15 in the list which is the row for Order No 10682. If you enter an invalid value
then any existing selections are removed.

Clear Current Selection Procedure


To deselect the currently selected row you can use the ClrCurrent procedure like this :-

ClrCurrent Me.SubformName 'clear current selection

You can test this facility by selecting a row and then clicking the Clear Current Selection button.
Note that on the multiple selections forms described below this operation will clear all selected
rows.

Additional Code Procedures


As well as the procedures and functions shown here there are a few more which only apply to the
forms that use multiple row selections and these are described in the following sections. Also,
there is a table which shows a summary of all the procedures and functions that are available in the
imported code module at the end of this document.

Page 9 of 17
Fetching Data from Multiple Rows
With the standard list box control you can easily scan through each row in the list and read the
current data or set/clear the selection, etc, etc. It is not easy to provide a simple procedure or
function to do this since there are so many variations that could be used but it is easy enough to
execute the same sort of procedure using the Access RecordsetClone function. The code snippet
below shows a method of scanning every record in the subform list and reading the data from one
of the columns or fields.

Private Sub cmdTest_Click()

Dim rst As Recordset


Dim vResult As Variant
Dim vControl As String

vControl = Me.SubformName.Form("Column3").ControlSource
Set rst = Me.SubformName.Form.RecordsetClone
rst.MoveFirst
Do Until rst.EOF
vResult = rst(vControl) ‘fetch Column3 data for current row
rst.MoveNext
Loop
rst.Close
Set rst = Nothing

End Sub

In this example the data from column 3 for each row is copied into variable vResult, in your own
code this data would then be processed in some way, of course.

The first line of the code fetches the name of the field from the Control Source property of Text
box ‘Column3’ and copies it to variable vControl. Then the rst recordset object is set to a copy of
all records in the subform and each record is processed within the Do…Loop. If you know the
name of the field that you want to process you could simplify the code a bit, for example you could
remove the first line of code (where the variable vControl is set) and replace the vResult line with
something like this which copies the current OrderID value into vResult :-

vResult = rst!OrderID

You can try this out on the example form by opening the VBA code window for the Click event
of the button cmdTest and setting a break point on the rst.MoveNext line, click the Test button
on the form and the vResult variable will show the order date (i.e. column 3) for each record in
turn when the code hits the break point.

This code snippet is just an example of reading data from a recordset clone and in your own
database project you would need to design your own code depending on the requirements of the
program. If you need to design some code which uses a similar subform list format you could
modify this code to try it out before you implement it in your own database.

Adding Other Options to the List


In addition to all of the above you could also add more facilities to the subform itself to give the
users more options. For example, you could add some code to the DblClick event of the button
cmdSelect which could open a form that could show information about the selected record (note
you cannot use the usual Click event because that will be the same as using the MouseDown
event which is already in use). See the subform VBA code module for an example of opening
another form or message box and displaying the contents of a field on the subform.

Page 10 of 17
 Multiple Row Selection (“Simple”) List Box
The standard list box control can also be set up to allow multiple rows to be selected at the same
time by setting its Multi Select property to ‘Simple’ and, as discussed above, this can also be
simulated on the subform list system but with more flexibility in that the highlight color can be any
color. To see an example of how the form could look you can open the third option on the main
menu called “Product Sales by Supplier”. Clicking on one or more rows will highlight them in
pink and clicking on a highlighted row will change it back to white. Also, in this example, the Cat
Icon column is displayed as a simple image and the Dis column as a standad Check box, see below
for more details.

To set up a multi selection subform list like this you should create a new form in the same way as
the previous ‘single selection’ form but with the following changes :-

1. Set the Tag property of the subform to 1 (to identify the subform as a ‘Simple’ multi
selection type list) which then allows the various procedures to use the appropriate code.
2. This is basically all you need to do to make the list into a multi select list but there is one
other minor change that makes the form look better. When you create the txtRef Text box
(which is the control that shows the highlight color) you should change the height of the
control so that it is 1 pixel shorter than the other controls and then move it down by 1 pixel.
The easiest way to do this is to change the control’s Height property on the Properties
sheet by trial and error (for an 8 point font one pixel is approximately 0.023 cm) and then
move the control down one pixel with the CTRL + DOWN arrow key. Doing this provides
a 1 pixel gap between lines where adjacent rows have been highlighted and looks a bit
better than having a large block of rows colored together.

Adding Other Column Types


In addition to formatting the various columns to suit the data you can also have different fonts in
different columns to enhance the display options. For example, the category icon (Cat Icon)
column shows a number in a circle which is related to the category, i.e. Beverages shows a 1 in a
circle, Condiments shows a 2 and so on (not very useful maybe but it illustrates the point). If you
look at the Font Name property for the ‘Column3’ Text box you can see that it is set to
‘Wingdings’ and also that the Font Size property is set to 12 since that looks better for an image
than the 8 points used in the other Text box controls. This field (CatIcon) in the query
(qrySuppliers) for the subform translates the CategoryID number into the appropriate character
in the ‘Wingdings’ font using the Choose function, although there are a large number of other
images (and fonts) that could be used instead.

Also, the ‘Discontinued’ (Dis) column shows a standard Check box control instead of a Text box
control which looks better for this type of field. Note that, as with the other columns, the Check
box control Name property is set to ‘Column6’ so that it can be used with the various procedures
provided and that it will return 0 (False) or -1 (True) if you use the function to read its status.

How Does It Work?


The way that the ‘Multiple Selections’ facility works is almost identical to the previous ‘Single
Selection’ form except that instead of the value of the unique field replacing the contents of the
txtSelect control it adds it to the contents of that control. For example, if you clicked on the first
record on the test form then the ProductID (with brackets) is copied to the txtSelect Text box
control as ProductID is the unique field in this example. If you click on another row then the
ProductID value for that row would be added to the txtSelect control and so on. If you click an
already highlighted row then the ProductID value for that row is deleted from the txtSelect
control which would then un-highlight that row. To see this working you can unhide the Footer
section of the form to see the contents of the txtSelect control change as you select/deselect rows.

Page 11 of 17
The Conditional Formatting code that you entered for the txtRef control in the previous form
checks whether the ProductID value for each row is present in the txtSelect Text box control and
if it is, then the color of the txtRef field is changed to whatever color you chose.

Adding the VBA code


The VBA code required for this type of list is basically the same as the previous example except
that you do not need any code in the Form_Current event for the subform because you would not
normally want the row selection to be changed by the Up and Down Arrow keys in this case.

To update the form display you need to add just one line of code, as before, to the MouseDown
event for the hidden button (cmdSelect) which will look something like this :-

SelectRow Me, Shift

The SelectRow sub-routine, in the imported code module, reads the value of the Tag property of
the form and changes the way the field value is copied to the txtSelect control, as described above.
Note that the value of the CONTROL and SHIFT keys are not used for this type of list box so the
Shift parameter is ignored here.

How to Use the Sub-Routines and Functions Provided


As described above, the various buttons and text boxes on this example form can be used to test
the sub-routines and functions that have been included in the imported code module. Some of the
buttons on this form do exactly the same as the previous example and so will not be described
again here and those are Get List Count, Get List Index, Get Column Data, Get Col/Row Data
and Clear All Selections. However, you should note that the Get List Index and Get Column
Data functions will use the index of the row that was last clicked on even though the row may not
be highlighted at the time (and this is also how the standard list box control works).

The additional functions are described here.

Items Selected Function


With a ‘multiple selections’ list you may want to know how many rows are currently selected and
you can do this with the ItemsSelected function, something like this :-

x = ItemsSelected(Me.SubformName) ‘get count of selected rows

On the demo form there is no button to use this option but the No of Items Selected field shows
the current number of selected rows, which you can see if you select and deselect different rows.
This field is updated in ‘real time’ because the function has been added to the Text box control’s
Control Source property (in the same way as you can do for the standard list box) like this :-

=ItemsSelected([frmSuppliersSub])

where frmSuppliersSub is the name of your subform.

Select / Deselect Row Procedure


To select or deselect a row you can use the RowSelected procedure (which was described in
more detail in the previous section) and the code would look something like this :-

RowSelected Me.SubformName, RowNumber, True ‘select specified row

where RowNumber is the number of the row to be highlighted and the third parameter is set to
True to select the specified row or set to False to de-select the specified row (note that using this

Page 12 of 17
procedure also sets the Index to the specified row and that the No of Items Selected field is
updated automatically each time).

To test this on the demo form just enter a valid row number into the Row field and click the Set
Selected button to highlight that row or click the Clear Selected button to un-highlight the same
row.

Get Selected Row Function


In addition to selecting and deselecting a row, you may also want to test if a row is currently
selected and you can do this by using the GetSelected function like this :-

x = GetSelected(Me.SubformName, RowNumber) 'fetch row status

where RowNumber is the index number of the row to be tested. The function returns a Boolean
data type of True if the row is currently selected and False if it is not selected.

You can test this with the demo form by first entering a valid row number in the Row field and
then clicking the Get Selected button. If the specified row is highlighted the Selected field will
show ‘True’ and will show ‘False’ if it is not highlighted.

Fetching Data from Multiple Selected Rows


With the standard list box control you can easily scan through each row in the list and read the data
for the currently selected rows. As described above, you can execute the same sort of procedure
using the RecordsetClone function. The code snippet below shows a method of scanning every
record in the subform list and reading the data from only those that are currently highlighted.

Dim rst As Recordset


Dim vResult As Variant
Dim vControl As String

vControl = Me.SubformName.Form("Column2").ControlSource
Set rst = Me.SubformName.Form.RecordsetClone
rst.MoveFirst
Do Until rst.EOF
If GetSelected(Me.SubformName, rst.AbsolutePosition) = True Then
vResult = rst(vControl) 'process collected data here
End If
rst.MoveNext
Loop
rst.Close
Set rst = Nothing

This code is basically the same as the routine in the previous section except that before the data is
read from the specified column, the status of the row is checked to see if it is a ‘selected’ row or
not. The GetSelected function is called and the current row index number (a numeric value in
rst.AbsolutePosition) is passed to the function which then returns True if the row is
selected or False if not. The data in variable vResult would then be processed as appropriate.

As described in the previous section, you can try this out on the example form by opening the
VBA code window for the Click event of the button cmdTest and setting a break point on the
vResult = rst(vControl)line, click the Test button on the form and the vResult variable
will show the contents of column 2 for each selected record in turn when the code hits the break
point (note that in this case you will first need to execute the code on this line with the F8 key to
get the data into the variable).

Page 13 of 17
 Multiple Row Selection (“Extended”) List Box
The standard list box control can also be set up to allow multiple rows to be selected in different
modes by setting its Multi Select property to ‘Extended and using the SHIFT and CTRL keys on
the keyboard. To see an example of how the form could look you can open the fourth option on the
main menu called “Supplier Products” which shows a list of all products along with the supplier
name, category, country and total sales. The last column displays a bitmap image which is
associated with the Category column, see below for more information.

This subform is slightly different in that the Text box controls use the Calibri font and a font size
of 10 pts (which is the default font for A2007 and later) and the Tag property is set to 2 to
designate it as an ‘Extended’ type list box. Apart from the Tag property value the form would be
set up in exactly the same way as the previous example.

How Does It Work?


The code required for the subform is exactly the same as the previous form, the cmdSelect
button’s MouseDown event is used to call the SelectRow procedure, as before. The way the rows
are selected can also be changed by using the SHIFT and CTRL keys on the keyboard like this :-

1. If a row is clicked then that row is highlighted and any other row/s are un-highlighted
(which is basically the same as the second example form). Note however, that you cannot
use the UP and DOWN arrow keys to move the highlighted row because if you add the
SelectRow procedure to the form’s Current event (as in the earlier example) it interferes
with the facilities described below.
2. If a row is clicked while the CTRL key is held down then that row is highlighted (or un-
highlighted if it is already highlighted) and any other highlighted rows are not changed
(this is the same as the third example form).
3. If a row is clicked while the SHIFT key is held down then all the rows between that row
and the previous row that was clicked on are highlighted including the current and last
selected rows. Any other selected rows that are outside this range are not changed.

Note. The method used to highlight the rows with the SHIFT key is slightly different than the
standard list box control which appears to maintain a separate list of items that have been selected
using the SHIFT-Click method. You can test out this difference with the standard list box control
on the fifth menu option (see below for more information) which has been set up as a Multi-Select
Extended List box for test purposes. To see how the standard list box control works, try this :–

Click on the first Exotic Liquids item (ID = 1).


SHIFT-Click on the last Exotic Liquids row (ID = 3) which will then highlight all three Exotic
Liquids items.
CTRL-Click on the first G’Day, Mate item (ID = 52) to highlight that row.
SHIFT-Click on the first Bigfoot Breweries (ID = 67) which will highlight all the items between
this row and the ID = 52 row.
SHIFT-Click on the last G’Day, Mate row (ID = 51) which will highlight the three G’Day, Mate
rows but also leaves the three previous Exotic Liquids rows highlighted which means that,
internally, the list box code must keep a record of any rows that have been selected using the
SHIFT-Click option and when the highlight range is changed, those rows are then kept
highlighted.

If you now try the same operation on the subform list example, this does not happen in quite the
same way. It would probably be possible to provide the exact same facility but would make the
code and form a lot more complicated because you would need to have extra fields on the subform
to hold a list of previously selected rows and copy them back into the txtSelect control at the right
time to ensure the rows are highlighted with the Conditional Formatting facilities and for a fairly
obscure facility like this it does not seem worth the extra complication.
Page 14 of 17
Adding Graphic Images to a List Box
Yet another advantage of using a subform instead of a standard list box is that you can also have
images displayed on each row. In the example form, the last column is displayed as an image
which is associated with each product category (the images themselves in this example don’t really
mean anything, they were just picked at random from a selection of same size images). To provide
an image on a list you should add a Bound Object Frame control to the form, change the name to
‘Column5’ (or whatever number is appropriate) and set the Control Source to the name of the
field in the bound query (i.e. CatPicture in this example). You will need to decide which option to
use for the Size Mode property (Clip, Stretch or Zoom) by trial and error which will depend on the
size and nature of the images. The various other properties such as Border Style, Border Color
etc should be set up depending how you want the image to look. Note that if you leave the
Enabled property as ‘Yes’ and the hidden button (cmdSelect) does not cover the image control
then the user could double-click on it and change the image (probably not a good idea but the
option is there). There are a number of properties that are specific to this type of control which
would not normally be required for this type of application but if you need them you should be
able to find more information in the Access Help file or on the Internet.

Now you also need to add the image itself to the appropriate table, in this case it is table
tblCategories. If you double-click on the table in the Database window you can see that the
CatPicture field (which is an OLE Object type field) shows ‘Bitmap Image’ for each record and
if you double-click on one of those the Windows Paint program should open and display the
image. You can edit it using this program or you can change it to a different image with Paint by
clicking on Paste –> Paste from .. and choose a different image in the File Selector pop up form.
There are several ways of manipulating images with this type of OLE field which are not covered
here so if you need more information on this you should look for it in Access Help or the Internet.

Linked or Embedded Images


Normally it is not advisable to include ‘embedded’ bitmap images in a table as this can ‘bloat’ the
database file for large images but in this case the images are very small (less than 1KB each) and
there are only eight of them so for this type of application it should not be a problem. In fact, when
the CatPicture field was deleted from the Categories table, the overall size of the database file
reduced by just 4096 bytes. If you need much larger image files (which probably would not fit on a
Continuous form anyway) you should used ‘linked’ images that are stored on the hard disk but this
does, of course, add further complications such as setting up paths to the files, etc.

Using Queries with Images


One thing to note about using OLE Object fields in a table is that they do not work well with
‘aggregate’ type queries. The query for this example form sums the total sales per product code
which requires an aggregate query and you cannot then add the OLE Object field (i.e.
CatPicture) to the query. The way around this is to have a separate query to do the additions
(qryProducts) and then use a simple query (qryProductsSub) to combine the output from
qryProducts and the Categories table. If you look at those two queries you can see how this
works.

Additional VBA Code


The buttons and text boxes on this form are basically the same as those on the previous forms. The
Test button, however, has some code to demonstrate how to highlight a range of rows. If you click
on the Test button the rows from row 4 to row 10 are selected. The code is very simple which you
can see by opening the VBA code window for the main form (frmProducts), it is just a For..Next
loop which selects the current row in variable vCount. Note that the Painting command is used
here to disable screen updates while the rows are being selected (try REMing out the
Me.Painting = False line to see what difference it makes). Also, if you ever use this
command you should also add some error trapping to the code and turn screen painting back on if

Page 15 of 17
an error occurs, otherwise the database will appear to ‘lock up’ because screen updates will still be
disabled.

 List Box Control Form


The last form (item 5 on the menu) is just a simple standard List box control which shows a list of
suppliers and products which was used to test how various facilities worked on a normal list box.
The form has been left in the demo so that you can use it to try out any code of your own that
requires a list box control. The button and Text box control do not do anything but you could add
some code to them to test out some facility or other relating to the list box control.

Conclusion
The examples described above are only basic guidelines on using a Continuous type form in place
of a List box control, there are obviously hundreds of different ways of using a List box and some
may not be suitable for use with a Continuous form.

Also, the group of VBA functions and sub-routines provided in the code module provided may not
work as you want in certain circumstances so you may need to modify the code to work in your
own particular database application but they should provide enough information to allow you to do
that easily.

If any bugs are found in the code or errors in this document then please contact me at :-
peter.hibbs@btinternet.com

History
Version 1.0 January 2014.

Page 16 of 17
SUMMARY OF VBA PROCEDURES
Select and highlight current row.
SelectRow Me, Shift
The Shift parameter defines the
value of the SHIFT or CONTROL
key.
In the procedures below the Me.SubformName parameter defines the name of the subform
control being used to show the list.
x = GetListCount(Me.SubformName) Fetch total number of rows in list.
Fetch the row index of the
currently selected row (0 based) or
x = GetListIndex(Me.SubformName) -1 if no row is selected. (Only
relevant for ‘single row selection’
type list).
Select or de-select row defined in
RowNumber, third parameter is set
RowSelected Me.SubformName, RowNumber, True
to True to select row or False to
de-select row.
Fetch data from a column for the
currently selected row, parameter
x = GetColumnData(Me.SubformName, n)
n defines the column number
(starts at 0).
Fetch data from any column and
row, Col defines the column
x = GetColRowData(Me.SubformName, Col, Row)
number and Row defines the row
number (both start at 0).
Select a row based on ‘bound’
SetValue Me.SubformName, n
column data, parameter n is set to
the value of the ‘bound’ column
(i.e. values in txtRef).
ClrCurrent Me.SubformName
De-select all currently selected
rows on subform.
x = ItemsSelected(Me.SubformName)
Fetch the number of rows that are
currently selected.
Fetch the status of a specified row,
the RowNumber parameter
x = GetSelected(Me.SubformName, RowNumber) specifies the row number, returns
True if row selected or False if
not selected.

Page 17 of 17

You might also like