Collections are a common way to store multiple instances of things.

For example, a TreeView control has a Nodes collection and Microsoft Word has a Documents collection. Until recently, Visual FoxPro developers wanting to use collections often created their own classes that were nothing more than fancy wrappers for arrays. However, in addition to being a lot of code to write, home-built collections don't support the FOR EACH syntax, which is especially awkward when they're exposed in COM servers. Visual FoxPro 8.0 solves this problem by providing a true Collection base class.

The Collection base class has only a few properties, events, and methods. The Add method adds an item to the collection, the Remove method removes an item, and the Item method returns an item. The Count property indicates how many items are in the collection. An "item" can be a scalar value such as text or a number, but most commonly is an object. In addition to the item itself, the collection can store a key for the item, such as a name. An item can be located in the collection by:

Position: Collection.Item(2) returns the second item in the collection

Key: Collection.Item('Doug') returns the item that has "Doug" as its key

Since Item is the default method, you can omit it if desired; Collection.Item(2) and Collection(2) do the same thing.

Collections can be simple replacements for arrays. Each item in a collection is similar to a row in an array. However, because they're objects, collections have many more capabilities than arrays. This article will look at three specific uses for collections.

Use Collections Instead of Arrays

Some objects need to store a collection of things. For example, a forms manager needs information about every open form in an application. In addition to an object reference to the form, it may also keep information about which toolbar the form uses (so you don't have multiple instances of the same toolbar), whether the form was added to the Window menu or not, the instance number of the form (in case the same form can be opened more than once), and so on. Until VFP 8, this information was often kept in an array, with one row per form and one column for each type of information.

However, as the number of columns increases, it becomes more and more difficult to keep track of what's in the array and where. Was it the fourth column that stored the instance number or the seventh? Also, because arrays in VFP can't have 0 rows, you have to be careful removing items from the array when a form is closed:

lnForms = alen(This.aForms, 1) ? 1
lnCols = alen(This.aForms, 2)
if lnForms = 0
  This.aForms = .NULL.
else
* lnForm is the row number of the closed form
  adel(This.aForms, lnForm)
  dimension This.aForms[lnForms, lnCols]
endif

This complexity disappears when you use a collection. Instead of a row in an array, a form is represented by an object in a collection. The object contains a reference to the form and the other information required. Which code would you rather write (or read, for that matter) to find the instance number of a form?

* Array-based code
lnPos = ascan(This.aForms, 'CustomerForm')
lnRow = asubscript(This.aForms, lnPos, 1)
lnInstance = This.aForms[lnRow, 4]

* Collection-based code
lnInstance = ;
  This.oForms('CustomerForm').nInstance

Removing an object from the collection is easy, because there's no need to worry about array dimensions. Simply call the Remove method of the collection.

Pass Collections as Parameters

Suppose you want to call a function that fills an array and the array is a member of an object. Other than a kidney stone, there isn't anything harder to pass than a member array. Since arrays must be passed by reference using the @ operator, and you can't use @ with a member array, you have to pass a local array and then ACOPY() the local array into the member array. However, to avoid an error, you must DIMENSION the member array properly first. I have a lot of code similar to this in various applications:

dimension laItems[1]
SomeFunction(@laItems)
lnRows = alen(laItems, 1)
lnCols = alen(laItems, 2)
dimension This.aItems(lnRows, lnCols)
acopy(laItems, This.aItems)

Using a member collection rather than a member array (and assuming SomeFunction can work with a collection), this becomes as simple as:

This.oItems = createobject('Collection')
SomeFunction(This.oItems)

Use Collections of Collections

The items in a collection can be anything, including other collections. In addition to acting like multi-dimensional arrays, collections of collections allow you to address objects at any level of detail using simple syntax.

Suppose you want to work with meta data. Wouldn't it be nice to retrieve the data type for a field using code like the following?

Tables('Products').Fields('ProductID').DataType

If Tables is a collection of Table objects, and a Table object has a Fields collection of Field objects, and a Field object has a DataType property, this is easily done.

Listing 1 shows an example of this. The Init method of the Tables class populates the collections of tables and fields by reading meta data from a table called CoreMeta.dbf. This table has columns of information about tables and fields, including cRecType ("T" for a table and "F" for a field), cObjectNam (the name of the table or field), and cType (the data type of a field).

To determine the number of fields in the Customer table, use the following:

Tables('customer').Fields.Count

You can get the descriptive name for the Orders.Order_Date field with this:

Tables('orders').Fields('order_date').Caption

Summary

The new VFP 8 Collection base class makes it easy to create and work with collections of items. While arrays still have their place, I predict collections will replace them in the majority of uses in applications as VFP developers become more familiar with them.