Enumeration through a collection in a custom class (1 Viewer)

ChrisO

Registered User.
Local time
Tomorrow, 06:28
Joined
Apr 30, 2003
Messages
3,202
Class.mdb is attached.

Chris.
 

Attachments

  • Class.zip
    20.8 KB · Views: 149

MarkK

bit cruncher
Local time
Today, 13:28
Joined
Mar 17, 2004
Messages
8,187
Chris, yeah, exactly. But check out the classes here. Notice that cSnapHost and cSnapSides are themselves collections such that I can declare an instance, call the load method with appropriate parameters, and it IS a collection, so I can write code like . . .
Code:
Private Sub TrySideSnap(TargForm As cSnapForm)
[COLOR="Green"]'   Enumerates Me.Sides to see if any side will snap against the target form[/COLOR]
    Dim Side As cSnapSide
    Dim oSide As cSnapSide

    For Each Side In [COLOR="DarkRed"][B]Me.Sides[/B][/COLOR]                   [COLOR="Green"]'enumerate my sides...'[/COLOR]
        Set oSide = Side.WillSnapTo(TargForm)  [COLOR="Green"] '...and if WillSnapTo...[/COLOR]
        If Not oSide Is Nothing Then            [COLOR="Green"]'...returns a cSnapSide...[/COLOR]
            DoSnap Side.index, oSide            [COLOR="Green"]'...then do the snap[/COLOR]
        End If
    Next

End Sub
. . . and the Me.Sides object is my custom class. And check out the object browser, those objects' item properties are default properties (see the little blue dots?)
 

ChrisO

Registered User.
Local time
Tomorrow, 06:28
Joined
Apr 30, 2003
Messages
3,202
Mark.

Yes, it’s a good way of doing things. I download and tested it before approving it a couple of days ago. I still need to look at it further because these things take time, well they do for me.

One of the things I dislike about threads like this is all the talk but very little practical work done. Big words go around and around and render the brain dumbfounded by the process of liquefaction. :eek:

At least we now have some solid code to work on if we can figure out what’s required. :confused:

Chris.
 

Galaxiom

Super Moderator
Staff member
Local time
Tomorrow, 06:28
Joined
Jan 20, 2009
Messages
12,856
I find the wording of this thread very confusing and would need some working code to see exactly what is required.

I also had to read it a few times to really understand. It is absolutely worth it though and worth playing around with the code.

What Lagbolt has linked to is awesome and is exactly what Michael was looking for.

I have seen the default property attribute before but I hadn't come across the one that allows enumeration of the private collection from outside the object using the standard syntax.

However it doesn't quite make the use of the custom collection class "indistinguishable" from a standard collection.

Even though the Private Collection can be designated as the default property to help support the For Each construct it doesn't return the private collection by referring to the instance as one can do in a standard collection.

So we can't just say:
Set colWhatever = CustomCollection
as can be done with a standard collections.

I don't understand why this would be since it clearly works in the For Each construct.
 

Galaxiom

Super Moderator
Staff member
Local time
Tomorrow, 06:28
Joined
Jan 20, 2009
Messages
12,856
One of the things I dislike about threads like this is all the talk but very little practical work done.

Good point.

In the interests of having something right here in the thread, here is a sample database which avoids distractions by including nothing but what is required to demonstrate how to support the enumeration of a Private Collection within a user defined class instance.


(Don't forget to export the class module and look for the Attributes with a text editor. Pasting the class code straight from the screen won't work.)


Note I have not used custom names for the Methods and Properties (eg AddItem, RemoveItem, GetCount, GetItem) as Chris did in his sample.

In my experience it is not necessary to avoid the reserved words in this situation. The standard Collection terms (Add, Count, Item, Remove,) can be used because they have a context as Properties and Methods of that specific class.

The fact that they are also Properties and Methods of the standard Collection class doesn't matter. After all, property names repeat in every Access Object Class without causing conflicts.


When extending a standard object class by creating a custom class I find it makes it easier to use if I emulate the original members exactly.
 

Attachments

  • EnumerateCustomCollection.zip
    11.5 KB · Views: 137

ChrisO

Registered User.
Local time
Tomorrow, 06:28
Joined
Apr 30, 2003
Messages
3,202
I still don’t understand the reason to iterate a collection by Item.

In a collection the Item does not need to be unique but the Key, if any, must be unique.
It would seem that any iteration of a collection should be done by the Key and not by the Item.

Chris.
 

MarkK

bit cruncher
Local time
Today, 13:28
Joined
Mar 17, 2004
Messages
8,187
I agree that it's not important how you iterate over a collection. In the snap forms example each form compares itself to each other form to see if a snap condition exists, and an object reference is required to do that so a For...Each...Next loop is tidy IMO, but it's not essential . . .
Code:
Private Sub TrySnap()
[COLOR="Green"]'   Enumerates other forms in the host, to see if a snap condition exists[/COLOR]
    Dim Target As cSnapForm
    
    For Each Target In Me.Host          [COLOR="Green"]'enumerate hosted snap forms[/COLOR]
        If Not Target Is Me Then        [COLOR="Green"]'don't compare to self[/COLOR]
            TrySideSnap Target          [COLOR="Green"]'try to execute a snap[/COLOR]
        End If
    Next
End Sub
. . . but you could do this too . . .
Code:
Private Sub TrySnap2()
[COLOR="Green"]'   Enumerates other forms in the host, to see if a snap condition exists[/COLOR]
    Dim i As Integer
    
    For i = 0 To Me.Host.Count - 1      [COLOR="Green"]'enumerate hosted snap forms[/COLOR]
        If Not Me.Host(i) Is Me Then    [COLOR="Green"]'don't compare to self[/COLOR]
            TrySideSnap Me.Host(i)      [COLOR="Green"]'try to execute a snap[/COLOR]
        End If
    Next
End Sub
I find the first routine is clearer code, but not by much. What matters about a collection, to me, is not how you enumerate it, but the fact that it contains datatypes that are very rich, that come with their own methods and data and capabilities.

Like, think of the controls collection of a form. No add method, and who cares what the count is, but when you enumerate it you retrieve a list of very useful things, and when you find the right one you can update the data in a table through the functionality of the control class. Or the fields collection of a recordset.

So you can create lists of objects of your own design that deliver very rich functionality. If you've ever enumerated the controls collection of a form looking for particular textboxes, why not enumerate the Orders collection of Customer looking for particular orders? What if that cOrder class knows how to read and write to and from the table? That's what I'm selling, is the power of classes.

And Galaxiom, I'm not saying it's indistinguishable from a VBA collection, but a VBA collection is a generic thing. A Form.Controls collection doesn't have an Add or Remove method. What I think makes it a collection is that you can enumerate it directly, as in the code blocks above. The Me.Host reference IS A collection, not, in respect to calling code, something that HAS A collection in it, and regardless of whether it implements the same members as a VBA.Collection.

And as far as "all the talk" and "no practical work" goes, yes and no. It's possible a bunch of talk can motivate practical work to occur beyond the scope of a thread, or bog us down.

Cheers,
 

mdlueck

Sr. Application Developer
Local time
Today, 16:28
Joined
Jun 23, 2011
Messages
2,631
. . . but you could do this too . . .
Code:
Private Sub TrySnap2()
[COLOR=Green]'   Enumerates other forms in the host, to see if a snap condition exists[/COLOR]
    Dim i As Integer
    
    For i = 0 To Me.Host.Count - 1      [COLOR=Green]'enumerate hosted snap forms[/COLOR]
        If Not Me.Host(i) Is Me Then    [COLOR=Green]'don't compare to self[/COLOR]
            TrySideSnap Me.Host(i)      [COLOR=Green]'try to execute a snap[/COLOR]
        End If
    Next
End Sub
I find the first routine is clearer code, but not by much. What matters about a collection, to me, is not how you enumerate it, but the fact that it contains datatypes that are very rich, that come with their own methods and data and capabilities.

You answered an unspoken concern of mine with how simple this was to implement in VBA. In my case, order matters. Thank you for providing an example of how to process a collection in an orderly manner. I would have still accepted an unordered solution, but this sample looks like exactly what I was looking for in my custom collection.

In VBA, there is no distinction between class level and instance level methods. Object Rexx (ooRexx) has such a distinction. So I was thinking to have a private pointer position attribute. Then I realized that the lack of class/instance methods would make that a nightmare. I need not worry, just code then enumeration loop differently! :cool:
 

ChrisO

Registered User.
Local time
Tomorrow, 06:28
Joined
Apr 30, 2003
Messages
3,202
Mark.

What I mean by “all the talk but very little practical work done” is that the original post in this tread seems to come down to “How do I step through a user defined Collection?”

Code:
Option Compare Database
Option Explicit

Private MyCol As Collection


Private Sub cmdTest_Click()
    Dim I As Integer
    Dim X As Variant
    
    Set MyCol = New Collection
    
    MyCol.Add "A"
    MyCol.Add "B"
    
    For I = 1 To MyCol.Count
        Debug.Print MyCol(I)
    Next I
    
    For Each X In MyCol
        Debug.Print X
    Next X

End Sub

By the way, the Index is from 1 to Count.

They work the same way in a module behind a Form or Report, Standard module or Class module. All the other stuff just seems extraneous and misleading to me.

Chris.
 

Galaxiom

Super Moderator
Staff member
Local time
Tomorrow, 06:28
Joined
Jan 20, 2009
Messages
12,856
What I mean by “all the talk but very little practical work done” is that the original post in this tread seems to come down to “How do I step through a user defined Collection?”

Chris, you seem to be missing the whole point. Iteration through a standard collection is a trivial task.

The thread is about iteration through a private collection encapsulated inside a class. Michael wanted to be able to treat the custom class in the same way as as a standard collection, hence he wanted to use the For Each loop on the class itself.

Sure the internal collection can be addressed via a property but it is interesting that the encapsulating class can be set up to be treated as though it is a standard collection itself. It is all good knowledge.

If nothing else it is useful to be aware of this possibility if it was encountered in someone else's code. I for one would have been scratching my head had I come across it without being aware of the attributes that make it possible.

To some extent then I would have to at least partly agree with your observation.
All the other stuff just seems extraneous and misleading to me.
 
Last edited:

MarkK

bit cruncher
Local time
Today, 13:28
Joined
Mar 17, 2004
Messages
8,187
I don't always write object-oriented code, but when I do, I use custom collections...

 

ChrisO

Registered User.
Local time
Tomorrow, 06:28
Joined
Apr 30, 2003
Messages
3,202
Thanks, Galaxiom, I think I understand it better now.

>>If nothing else it is useful to be aware of this possibility if it was encountered in someone else's code. I for one would have been scratching my head had I come across it without being aware of the attributes that make it possible.<<

I think that may be the best thing said so far in this thread.

Given the two possibilities you have already written and made available in your upload in post #25…

1. For Each itm In col
a. Needs exporting, modifying and importing from text.
b. The modifications are not visible in the header.
c. Requires:-
Property Get NewEnum() As IUnknown
Set NewEnum = Items.[_NewEnum]
End Property
d. Which, in turn, requires a reference to OLE Automation.

2. For Each itm In col.Coll
a. Only requires a totally visible:-
Function Coll() As Collection
Set Coll = Items
End Function

I’m not trying to diminish the possible importance of Mark’s code in other situations, I don’t know the other situations, but is it really worth the penalty paid here?

Another consideration is this…
Michael’s comment in post #28 indicates he did not know of the collection’s Index method. You, Mark and I, may call this a trivial task but Michael did not know about it in post #28.

Without that ‘trivial’ information, Michael wants to use a collection in a Class module and index the Class module directly to get at the collection which it contains? The ‘trivial’ stuff was not even understood in post #28 but was pushed along by big confusing words in, and after, post #1.

To me…
the ‘trivial’ stuff comes before the big words.
‘For Each itm In col.Coll’ comes before ‘For Each itm In col’
‘For I = 1 To col.Coll.Count’ comes before ‘For Each itm In col.Coll’
And iterating the collection by Key comes before all of the above.

People can choose their own poison but I wouldn’t touch ‘For Each itm In col’ if there was another way to do it.

Chris.
 

ChrisO

Registered User.
Local time
Tomorrow, 06:28
Joined
Apr 30, 2003
Messages
3,202
Mark, I fail to see the significance of post #31

Chris.
 

MarkK

bit cruncher
Local time
Today, 13:28
Joined
Mar 17, 2004
Messages
8,187
You don't know the Dos Equis guy?
 

mdlueck

Sr. Application Developer
Local time
Today, 16:28
Joined
Jun 23, 2011
Messages
2,631
Michael wanted to be able to treat the custom class in the same way as as a standard collection, hence he wanted to use the For Each loop on the class itself.

Bing bing bing bing...

1) I needed the public interface of my class to be able to be used as a collection in a For Each loop
2) I needed to be able to define an order that the enumeration of the objects contain in the collection would be returned. I need to process DB records in FIFO order. FIFO order is guaranteed by the SQL Server autonumber ID, so I needed to retain that order in my collection class. (I am assuming if I ORDER BY in my query, Add the entries to the Collection in sorted order, then they will come back out of the collection based on coding in Post #28 syntax in the same order, jah?)

If nothing else it is useful to be aware of this possibility if it was encountered in someone else's code. I for one would have been scratching my head had I come across it without being aware of the attributes that make it possible.

I plan to leave plenty of header comments, details that I exported / imported the module, which methods to deviniately NOT mess with copy'ing/paste'ing the code, etc...

I don't always write object-oriented code, but when I do, I use custom collections...

At the client location they did a celeb look-alike contest. A manager looked like this celeb's twin, so a photo of each was one of the entries.
 
Last edited:

ChrisO

Registered User.
Local time
Tomorrow, 06:28
Joined
Apr 30, 2003
Messages
3,202
>>I plan to leave plenty of header comments, details that I exported / imported the module, which methods to deviniately NOT mess with copy'ing/paste'ing the code, etc...<<

That’s another reason not to do it.

Chris.
 

mdlueck

Sr. Application Developer
Local time
Today, 16:28
Joined
Jun 23, 2011
Messages
2,631
That’s another reason not to do it.

Do you have an alternate suggestion by which to arrive at the same interface and success without needing to export / hand edit / import the class module?
 

ChrisO

Registered User.
Local time
Tomorrow, 06:28
Joined
Apr 30, 2003
Messages
3,202
Sure, and I have already said so in post #32.

Look at Galaxiom’s upload in post #25 because he has already set it up for you…
Change the line:-
For Each itm In col
to
For Each itm In col.Coll

Or
Declare a variable I and use:-
For I = 1 To col.Coll.Count

At the moment you have said that you only want a FIFO from a recordset load of the collection. With only that in mind, any iteration from start to end will do. ‘For Each’ or ‘For I’ really doesn’t matter.

However, eventually you may want to modify something in the class, or the collection in that class, and write it back to the table on which the recordset is based. To do that an iteration will not work. You will need a unique Key in the collection so that the Item in the collection is written back to the unique record it was based on.

We can look upon the Items in a collection as not being unique. If they are objects then they will be unique because only the pointer to the object is in the Item member. Pointers to objects are unique but, then again, the same object could be loaded more than once, so it too can’t be guaranteed.

A search on the Item in a collection then becomes more like a FindFirst on a recordset. You might find one or you might find the first of many. The Key member of the collection then is the only unique key.


So, just as a general principle…
We are working in a database where data is held in tables.
If we modify that data, and want to save it, we need to write it back to the table.
To write it back to the table we need a unique Key.

If this is what you eventually want to do then it would appear that the SQL Server autonumber ID would be the unique Key in the collection.

But first look at Galaxiom’s upload in post #25.

Chris.
 

ChrisO

Registered User.
Local time
Tomorrow, 06:28
Joined
Apr 30, 2003
Messages
3,202
From post #34.
>>You don't know the Dos Equis guy?<<

No; should I?

Chris.
 

MarkK

bit cruncher
Local time
Today, 13:28
Joined
Mar 17, 2004
Messages
8,187
If you knew the Dos Equis guy you might have chuckled. :)
 

Users who are viewing this thread

Top Bottom