2.8 Collections

Collections

Creating a unique object reference for each object you create might not be a good way to go, especially when you create a set of similar objects, like a lot of forms. Even if every object has its own reference, you might want to have an easy way to iterate through objects, for example. Collections provide this functionality.

Collections are arrays of object references. You've already seen a simple collection in the form manager example above. It used an ApplicationForms array to provide object references to all the forms I wanted to launch.

The advantages of collections are obvious. They make it easy to provide object references in a scenario where you need multiple references to similar objects. They also make it very easy to handle these objects. Let's assume you wanted to iterate through all forms. This could simply be done using FOR NEXT or FOR EACH loops.

Collections usually follow a simple naming convention. All collections are named in plural to show that they reference multiple objects. That's why I called my form manager collection "ApplicationForms". This is pretty simple and doesn't seem like much, but I always have a hard time remembering what each property, object or collection is called. Bringing the plural rule to mind actually saved me quite a lot of recompilation time.

In the form manager example, I used a collection to actually provide object references that keep the objects alive. However, collections can also host copies of references to simplify object handling. Each FoxPro form, for instance, has an Objects collection that grants us access to every object in that form, even though each object has its very own object reference.

Accessing objects through a collection is very easy; it's a lot like accessing items in an array. You simply use the name of the collection plus the item index instead of the object name. Suppose you have a grid with several columns. You can access each column directly with its name, or you can use the Columns collection. Both ways are demonstrated in the following example:

SomeObject.oGrid.colPriceColumn.Header1.Caption = "Price"
SomeObject.oGrid.Column(5).Header1.Caption = "Price"

The fifth column is called "colPriceColumn" and it has several member objects, including the header and maybe a textbox.

FoxPro's collections

Visual FoxPro provides a variety of built-in collections. Most container objects have a Controls collection. In addition to that, some objects have special collections that are unique. FormSets have a Forms collection, PageFrames feature a Pages collection, Grids have a Columns collection, and so forth.

Working with these collections is straightforward. FoxPro takes care of everything. It makes sure each item in the collection points to the right object, it makes sure the collection always has the correct number of items, and it also makes sure that there are no outstanding references that keep objects alive that are supposed to die.

The most commonly used collection in Visual FoxPro is Controls. It makes accessing member objects of containers very easy and generic. You can iterate through this collection using either a FOR EACH or a FOR NEXT loop. When using the FOR NEXT version, you can use the ControlCount property of the container to determine the number of items.

There isn't a lot you can do with these collections other than access objects. You can't just redimension these collections because FoxPro knows the difference between its own collections and arrays. However, some collections provide an interface (such as an AddItem() method) to add or remove items from a collection.

The item order in a collection does not necessarily reflect visible sequences you see on the screen. In a grid, for instance, columns might be dragged all over the place, but the collection would always keep the original sequence. Usually other properties help to query object sequences.

The collections you create yourself

You can also create collections personally, using arrays as object references, as demonstrated in the form manager example. However, Visual FoxPro does not recognize these collections as real collections. You have to deal with all the details yourself. This means you have to make sure the array gets redimensioned, keep all the items in the array in sync, and make sure your collection doesn't accidentally keep objects alive that are supposed to be destroyed.

Let's have another look at the form manager example. Here is the code that maintained the collection:

FUNCTION NewForm( FormClass, FormClassLib )
* We check if we have to redimension the array
IF Alen(THIS.ApplicationForms,1)=1 AND NOT VarType(THIS.ApplicationForms(1))="O"
DIMENSION THIS.ApplicationForms( Alen(THIS.ApplicationForms,1) + 1 )
ENDIF

* Now we create the new form and show it
*-- more code goes here

* We check if the form has been created
IF VarType(THIS.ApplicationForms(Alen(THIS.ApplicationForms,1)))="O"
* Form has been created
THIS.ApplicationForms(Alen(THIS.ApplicationForms,1)).Visible = .T.
* We return a reference to the new form
RETURN THIS.ApplicationForms(Alen(THIS.ApplicationForms,1))
ELSE
* Form hasn't been created,
* so we have to delete the reference from the array
IF Alen(THIS.ApplicationForms,1) = 1
* The array only has one item, so we can't make it any smaller
THIS.ApplicationForms(1)=.NULL.
ELSE
* We shrink the array
DIMENSION THIS.ApplicationForms(Alen(THIS.ApplicationForm,1) - 1 )
ENDIF
RETURN .NULL.
ENDIF
ENDFUNC

At the very beginning, I check if the collection has to be redimensioned. FoxPro arrays can't have a length of 0, which brings up a bad situation at the beginning. So I simply define an array with one item, and whenever I run through the code, I check if the array has a length of 1. If it does, I check if the first item is an object reference. If it is an object reference, I have to add an item; otherwise, I can simply use the existing one.

Then comes the code that actually instantiates the form. I removed that code from this example to keep it simple.

The next program line checks if the object creation was successful. If not, I have to remove the item from the array. Of course, I can only remove an item if the length of the array is at least two items. Otherwise, I'd end up with a length of 0, which wouldn't be valid in FoxPro.

However, this code doesn't deal with all possible scenarios. What if the user closes a form using the window's Close box? In this case the form would just disappear, leaving a .NULL. value in one item of the collection. In order to handle this scenario perfectly, I'd need to change the form classes so they tell the manager object that they are about to die. This is not a good way to go. Whenever other objects require redesign in order to make other objects work, there is something wrong and you should think of a better way.

In this example, I could simply iterate through the collection before I add a new item, and clean up the collection. This wouldn't be a perfect solution, because it could still throw the collection out of sync from time to time, but for most uses it should do the trick. If this weren't good enough, I'd have to use an observer component which, on the other hand, might introduce a performance problem.

This code iterates through the collection before continuing with the standard behavior:

FUNCTION NewForm( FormClass, FormClassLib )
* First of all, we make sure the collection is OK
THIS.CleanCollection()

* We check if we have to redimension the array
IF Alen(THIS.ApplicationForms,1)=1 AND NOT ;
VarType(THIS.ApplicationForms(1))="O"
DIMENSION THIS.ApplicationForms( Alen(THIS.ApplicationForms,1) + 1 )
ENDIF

* Now we create the new form and show it
*-- more code goes here

* We check if the form has been created
IF VarType(THIS.ApplicationForms(Alen(THIS.ApplicationForms,1)))="O"
* Form has been created
THIS.ApplicationForms(Alen(THIS.ApplicationForms,1)).Visible = .T.
* We return a reference to the new form
RETURN THIS.ApplicationForms(Alen(THIS.ApplicationForms,1))
ELSE
* Form hasn't been created,
* so we have to delete the reference from the array
IF Alen(THIS.ApplicationForms,1) = 1
* The array only has one item, so we can't make it any smaller
THIS.ApplicationForms(1)=.NULL.
ELSE
* We shrink the array
DIMENSION THIS.ApplicationForms(Alen(THIS.ApplicationForm,1) - 1 )
ENDIF
RETURN .NULL.
ENDIF
ENDFUNC

PROTECTED FUNCTION CleanCollection
LOCAL loObject
LOCAL lnCounter
lnCounter = 1
FOR EACH loObject IN THIS.ApplicationForms
IF NOT VarType(loObject) = "O"
* The current item is not an object reference
IF Alen(THIS.ApplicationForms) > 1
* We delete the item from the array
Adel( THIS.ApplicationForms, lnCounter )
DIMENSION THIS.ApplicationForms( Alen( THIS.AppliactionForms ) -1 )
lnCounter = lnCounter - 1
ELSE
* The array has only one item which has to remain
ENDIF
ENDIF
lnCounter = lnCounter + 1
ENDFOR
ENDFUNC

As you can see, I created a separate method that makes sure all the items in the collection are object references. Doing it this way, I can check the collection from every method that uses it, just to make sure everything is fine. I could have made this method even more generic by passing the array's name as a parameter. This might be a good idea in a real-world scenario (unless performance is extremely important) but to keep the demo simple, I didn't do that here.

Creating VB-style collections

Visual Basic handles collections very nicely. They all have a name instead of a numeric index that nobody can remember anyway. In addition, they usually act much like an object. In other words, the collection has properties and methods. The good news is that in Visual FoxPro 6.0 you can create collections that are much like the ones in VB.

I'll deal with the naming part first. Suppose I have a form that has a button and a textbox. I add a collection (array) called "members" that will provide a generic way to access all the members of the form. I can either pass a numeric index to that collection, or the name of the object I want to talk to. I'll use the new access methods to accomplish this goal:

FUNCTION Members_Access
LPARAMETERS lvIndex

DO CASE
CASE VarType(lvIndex) = "C"
DO CASE
CASE Lower(lvIndex) = "button"
RETURN THISFORM.Command1
CASE Lower(lvIndex) = "editbox"
RETURN THISFORM.Edit1
OTHERWISE
IF Type("THIS."+lvIndex) = "O"
RETURN THIS.&lvIndex.
ELSE
RETURN .NULL.
ENDIF
ENDCASE

CASE VarType(lvIndex) = "N"
RETURN THIS.Controls(lvIndex)

OTHERWISE
RETURN .NULL.
ENDCASE
ENDFUNC

The first thing the access method does is to check for the type of the parameter. If it is a character, the method checks to see whether it's a "button" or an "editbox". In this case it returns the reference to one of the existing objects. Otherwise, the method checks if there is a member object that happens to have the name that was passed as a parameter. If so, it returns a reference to this object.

In case the parameter is numeric, I simply use the internal Controls collection and return the item with the index that was passed.

Suppose I wanted to access the first button object on the form. I can do that in three different ways:

? Form.Members(1).Caption
? Form.Members("button").Caption
? Form.Members("Command1").Caption

Now I can access all the members in a nice and easy way. This is very useful. I have a class that I use to filter data. It expects a grid to be in the same form. The algorithm goes out and tries to find that grid itself, which works great unless there is more than one grid in that form. Using a generic collection that allows referencing the grid by some generic name instead of the real name that can be different in every form, it allows me to reuse that class in many more instances.

Note that I didn't have to redimension any array or anything else, because I only used the access method to reroute the calls. This makes handling these collections a lot easier. I don't have to worry about outstanding object references or anything like that.

Now I only need to add methods and properties to my collection. To do so, I have to create another object that actually hosts these members. Whenever the collection is accessed without passing an index, I simply reroute the call to that object. To do so, I have to change the code from above just a little bit. All I do is alter the OTHERWISE case that used to return .NULL.:

OTHERWISE
RETURN THIS.oMembers

Of course, this requires that there is an object called oMembers. This object can have all kinds of properties and methods. Here are a couple of examples:

Form.Members.AddObject("Button2","CommandButton")
Form.Members.Refresh
? Form.Members.Count

For these examples, you might not want to reroute the call to a separate object, but to THIS. The form object already has a Refresh and an AddObject method. It doesn't have a Count property, but that is easy to add. I suggest you create a Count property that has an access method that reroutes the call to the ControlCount property that's maintained internally:

FUNCTION Count_Access
RETURN THIS.ControlCount
ENDFUNC

This way, I created a self-organizing collection that actually grows with the product. It allows me to access every object in the form and all the form's methods in a generic way. At the same time, I don't introduce any major risks. Very cool and very elegant!

Collections provided by others

Collections can be found in almost all object-oriented applications and components. You might encounter some that are quite different from those I've described so far. This is especially true for COM servers and ActiveX controls. Some might grant you access to each item by simply referencing them. Others require querying collections first, which will then return an array of objects. Sometimes it gets even more complex.

However, it seems that most modern implementations feature the simple way that allows you to access each item directly. Also, a lot of collections have an Items subcollection and a Count property, which makes the handling even easier. Here's a simple example that uses Count:

LOCAL lnCounter
FOR lnCounter = 1 TO oObject.Collection.Items.Count
oObject.Collection( lnCounter ).Execute()
ENDFOR

At first sight it might seem weird that a collection is an array of object references, but it can also have properties. But hey, I never said collections and arrays were exactly the same.



Advanced Object Oriented Programming with Visual FoxPro 6. 0
Advanced Object Oriented Programming with Visual FoxPro 6.0
ISBN: 0965509389
EAN: 2147483647
Year: 1998
Pages: 113
Authors: Markus Egger

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net