Wiki Home

Collection Class

(Updated: 2006.11.03 09:39:24 PM)
Namespace: VFP
VFP 8.0 finally adds a native collection class.

see also Collection, VFP home grown collection, COM collection, VFP Collection


Using the Collection Class to store Objects (There is a gotcha)
The collection class REMOVE method will not destroy an object that was ADDed, but it will remove the item from the collection (i.e. the COUNT is decremented and the item is gone).

HOWEVER, if the ADDed object is destroyed before REMOVE is attempted, the collection will automatically remove the ADDed object from the collection. Example: a form object reference is stored in the collection. The form's close button is then clicked and the form goes through the Destroy/Unload process. The collection will then automatically remove the item.

Try this to show it to yourself from the command line:
cc = createobject("collection")
cc.add(createobject("form"))
cc[1].show

Now click the form's close button.

? cc.count

The count will now equal zero. Now try it again but instead of clicking the form's close button, do the following:

cc.remove(1)

Note that the form is still there but the count is zero! Also, if a collection item references a contained object (e.g. a listbox on a form), the item in the collection will not prevent the parent object from being destroyed. This is in contrast to a normal memory variable that contains a reference to a contained object; if such a memory variable exists, the parent object cannot be released. -- Ben Creighton


Another collection class bug with FOR EACH - Jamie Osborn
LOCAL loCol,loFlds,loItem

loCol = CREATEOBJECT("Collection")

CREATE TABLE test (f1 C(10),f2 C(10))

INSERT INTO test values ("Hello","World")

SCATTER NAME loFlds
loFlds.F2 = "There"
loCol.Add(loFlds)

FOR EACH loItem IN loCol
  *-- The following wait window will make the code work correctly
  *-- as will assigning something to loItem.F2 at this point.
  * WAIT "F2 field = '" + loItem.F2 + "'" WINDOW
  GATHER NAME loItem
ENDFOR

BROWSE NORMAL

Yes, this one has been posted to MS. The fix is to avoid FOR EACH with collections. Here's some more detail: http://www.west-wind.com/wwthreads/ShowMsg.wwt?MsgId=0Y40USMEN - lc
Collection class bug:

Creating a collection and adding custom objects to the collection by calling CREATEOBJECT("custom") within the collection's Add() method causes the PRG or class method to return an object rather than the desired return value. The object that is returned is the custom object that was added to the collection.

Put the following code in a PRG and capture and check the return value:
LOCAL loCol
loCol = CREATEOBJECT("collection")
loCol.Add(CREATEOBJECT("custom"), "1")
loCol("1").AddProperty("nId", 1)
RETURN .T.

It's a bug but it isn't in the Add method. Not using default property of the collection class (Item) is the source of the problem. The fix is to reference collection items with Item property. -- Sergey Berezniker
loCol.Item("1").AddProperty("nId", 1)


A simple workaround is to create the custom object, assigning it to a variable, and then add the object variable to the collection, like this:
LOCAL loCol, loCus
loCol = CREATEOBJECT("collection")
loCus = CREATEOBJECT("custom")
loCus.AddProperty("nId", 1)
loCol.Add(loCus, "1")
RETURN .T.

Jared Capson
PS. sorry if I put this in the wrong spot, wasn't sure where to post it.
You can use collections to create a set of related items. Collections offer a way to work with contained objects and use standard ways to access and iterate through items.
HOWEVER, the "standard ways" to access and iterate basically is supposed to mean "FOR EACH", yet there is a bug in VFP8 whose workaround is to "avoid using FOR EACH with collections".... doesn't "FOR EACH" ONLY work with collections? Plus, there is a bug using the "default property" Items (both bugs are noted above). So, if you are forced to explicitly state ".Items", and you can't use "FOR EACH", then you can easily implement a practically identical collection class in VFP6: Vfp 6 Collection Class - ?wgcs

Furthermore collections can be made into ordered collections by using a key in the collection command.

One example I can think of for this class is a forms manager in a framework:

DEFINE CLASS FormsManager AS Collection
   nCascadeSpacing = 3
   Function Cascade
      LOCAL lnTop AS Integer, lnLeft AS Integer
      STORE 0 TO lnTop, lnLeft
      _Screen.LockScreen = .T.
      FOR EACH oForm IN This
         oForm.Top = lnTop
         oForm.Left = lnLeft
         lnTop = lnTop + This.nCascadeSpacing
         lnLeft = lnLeft + This.nCascadeSpacing
      ENDFOR
      _Screen.LockScreen = .F.
   ENDFUNC
ENDDEFINE

Or, alternately, now that we have a standard Collection class interface, we could have a class that accepts a collection and cascades the forms therein. In other words, instead of burdening the forms manager class with GUI duities, we could separate GUI services into a specialized class.

DEFINE CLASS GUISvc AS Custom
   nCascadeSpacing = 3
   Function Cascade( toCollection)
      LOCAL lnTop AS Integer, lnLeft AS Integer
      STORE 0 TO lnTop, lnLeft
      _Screen.LockScreen = .T.
      FOR EACH oObject IN ToCollection
         IF VARTYPE( oObject)= "O" AND oObject.BaseClass= "Form" AND oObject.Visible
           oObject.Top = lnTop
           oObject.Left = lnLeft
           lnTop = lnTop + This.nCascadeSpacing
           lnLeft = lnLeft + This.nCascadeSpacing
         ENDIF
      ENDFOR
      _Screen.LockScreen = .F.
   ENDFUNC
ENDDEFINE


This is now possible since we have a standard interface and we can provide services to any arbitrary collection of forms, not just those in objects of the FormsManager class.

-- Steven Black
Visual FoxPro returns a COM reference when you iterate through the elements of a collection using FOR EACH. This is true even if you only add VFP objects to the collection.
A COM reference often behaves slightly differently (in what way?), for example when dealing with arrays:
Local la1[1], la2[1], oCollection, oTest, oRef
oCollection = CreateObject("Collection")
oTest = CreateObject("Test")
oCollection.Add(oTest)
oTest.Test(@la1)
Display Memory like la1
For each oRef in oCollection
   oRef.Test(@la2)
   Display Memory like la2
   COMARRAY(oRef,11)
   oRef.Test(@la2)
   Display Memory like la2
EndFor

Define Class Test as Custom
Procedure Test(raArray)
   Dimension raArray[2]
   raArray = ""
EndProc
EndDefine

-- Christof Wollenhaupt
What is this example trying to prove??? Sample output and expected output would help here.

One side effect of a COM object reference is that its properties aren't picked up by VFP when trying to "INSERT INTO (alias) FROM NAME (loRecordObject)"
To work around this problem, use the new VFP9 keyword on FOR EACH: FOR EACH oObj IN oCollection FOXOBJECT-- ?wgcs

(As opposed to a VFP Collection)

This piece of code won't do what is expected from it. laElem(1) and laElem(2) have both the same value when you print them. So it seems that you cannot hold arrays properly in a collection object. Is there any workaround or something that I'm not doing the right way?
DIMENSION laElem(2)

oCol = CREATEOBJECT("collection")

laElem(1) = "elem1:val1"
laElem(2) = "elem1:val2"
oCol.add(laElem)

laElem(1) = "elem2:val1"
laElem(2) = "elem2:val2"
oCol.add(laElem)

* Print the content of the collection object
FOR i = 1 TO oCol.count
	laElem = oCol.item(i)
	?"( " + laElem(1) + " , " + laElem(2) + " )"
ENDFOR


As a possible workaround, try using an Empty object and the ADDPROPERTY() function instead of an array:
LOCAL oCol, oItem

oCol = CREATEOBJECT("collection")

* First row of collection
oItem = CREATEOBJECT("empty")
ADDPROPERTY(oItem, "Val1", "elem1:val1")
ADDPROPERTY(oItem, "Val2", "elem1:val2")
oCol.Add(oItem)

* Second row of collection
oItem = CREATEOBJECT("empty")
ADDPROPERTY(oItem, "Val1", "elem2:val1")
ADDPROPERTY(oItem, "Val2", "elem2:val2")
oCol.Add(oItem)

* Print the content of the collection object
FOR i = 1 TO oCol.count
	oItem = oCol.Item(i)
	?"( " + oItem.Val1 + " , " + oItem.Val2 + " )"
ENDFOR

* This is an alternate way to print the content
FOR EACH oItem IN oCol FOXOBJECT
  ? "( " + oItem.Val1 + " , " + oItem.Val2 + " )"

ENDFOR && EACH oItem

-- EvanPauley
Category VFP 8 New Features