classes, collections, & dictionaries, oh my!

classes, collections, & dictionaries, oh my!

Anonymous
Not applicable
469 Views
11 Replies
Message 1 of 12

classes, collections, & dictionaries, oh my!

Anonymous
Not applicable
I've been diligently working and studying all weekend trying to create a
class module that serves as a wrapper for a collection, or more likely a
dictionary, object. I'm having a few problems and I'm thinking that I may
simply be implementing my ideas incorrectly. I want to create a small
object model like so:

WorkingStates
|
|__WorkingState

Where WorkingStates is a collection, or dictionary, containing WorkingState
objects similar to the Layers--Layer relationship in the acad model. What
I've done is to create a project with two class modules named WorkingStates
& WorkingState. I'm storing the info to populate these objects in the
registry. The code to retrieve this info is working correctly. What I need
to know is what is the proper method for populating these objects. I want
to be able to work with a single working state,
WorkingStates.Item("WD").MyCoolMethod(I realize that I must provide the Item
method myself), & also be able process all WorkingState objects with a For
Each WorkingState In WorkingStates loop. My first thought was to populate
the WorkingStates object in the class_Initialize() event with something like
this:

Option Explicit
Private colWorkingStates As Collection

Private Sub Class_Initialize()
Dim mobjWorkingState As WorkingState
Dim tmpArray As Variant
Dim i As Integer

'Get all working states saved in registry
tmpArray = GetAllSettings("WorkingStates", "States")

'populate collection
Set colWorkingStates = New Collection
For i = LBound(tmpArray) To UBound(tmpArray)
Set mobjWorkingState = New WorkingState
colWorkingStates.Add Item:=mobjWorkingState.Item(tmpArray(i, 0)), _
Key:=tmpArray(i, 0)
Set mobjWorkingState = Nothing
Next i

'set properties
lngCount = colWorkingStates.Count

End Sub

Private Sub Class_Terminate()
Set colWorkingStates = Nothing
End Sub

My concern here is that this will run through code and populate every
WorkingState object stored in the registry, even if I only want to work with
a single WorkingState. Is there another way? I have put the code to
populate the WorkingState object in the Item method of the WorkingState. If
anyone has an outline of how this is normally done, I'd appreciate any help
at all 🙂

My next problem is that I'm having trouble implementing a For Each loop
using these objects.

Dim objWSS as WorkingStates
Dim objWS as WorkingState

Set objWSS = New WorkingStates
For Each objWS in objWSS
*DoStuff*
Next objWSS

My reference material states that we must provide an interface for the For
Each...Next loop by creating this property:
Public Property Get NewEnum() As IUnknown
Set NewEnum = colWorkingStates.[_NewEnum]
End Property
Which I've obviously done, but can't seem to get the loop to work. Thanks
to all for your help.
--
Bobby C. Jones
0 Likes
470 Views
11 Replies
Replies (11)
Message 2 of 12

Anonymous
Not applicable
> My concern here is that this will run through code and
> populate every WorkingState object stored in the registry,
> even if I only want to work with a single WorkingState.
> Is there another way?

If you put that code in the collection's Initialize event, you take a
performance hit when the collection is created but you speed up access
to the contained objects. The same code placed in the Item routine
speeds up collection creation but slows down item access on the first
pass since you must create the item if it has not been previously
accessed. The easiest way to do this is use an internal Collection
object. When an item is requested, try to get it from the collection.
If it is not in the collection, create the item, append it to the
collection and return a reference to the new item.

> My next problem is that I'm having trouble implementing
> a For Each loop using these objects.

What the help doesn't make too clear is that the GetNewEnum method
must be assigned a Procedure ID of -4.

--
"That's no ordinary rabbit."
http://www.acadx.com


"Bobby Jones" wrote in message
news:8C0BBAE424122F79B19ED152DCDD8EB4@in.WebX.maYIadrTaRb...
> I've been diligently working and studying all weekend trying to
create a
> class module that serves as a wrapper for a collection, or more
likely a
> dictionary, object. I'm having a few problems and I'm thinking that
I may
> simply be implementing my ideas incorrectly.
0 Likes
Message 3 of 12

Anonymous
Not applicable
> What the help doesn't make too clear is that the GetNewEnum method
> must be assigned a Procedure ID of -4.

Frank -- how does one go about setting the ProcedureID to -4? I currently
create my class in AutoCAD 2000, export it, bring it in to VB6, save it, and
import it into AutoCAD 2000. Is there a way to handle this entirely within
AutoCAD VBA? - Lanny
0 Likes
Message 4 of 12

Anonymous
Not applicable
Would be nice since i don't have vb 🙂
--
Bobby C. Jones

"Lanny Schiele" wrote in message
news:1D4F10586524BDA0CE0A027A4E23C7A0@in.WebX.maYIadrTaRb...
> > What the help doesn't make too clear is that the GetNewEnum method
> > must be assigned a Procedure ID of -4.
>
> Frank -- how does one go about setting the ProcedureID to -4? I currently
> create my class in AutoCAD 2000, export it, bring it in to VB6, save it,
and
> import it into AutoCAD 2000. Is there a way to handle this entirely
within
> AutoCAD VBA? - Lanny
>
>
0 Likes
Message 5 of 12

Anonymous
Not applicable
I don't know of a way to do this within the VBAIDE but you could open
your class file in a text editor and the line noted below:

Public Property Get NewEnum() As IUnknown
Attribute NewEnum.VB_UserMemId = -4 'Add this line

Looks like it's time for someone to create a member attribute addin.

--
"That's no ordinary rabbit."
http://www.acadx.com


"Bobby Jones" wrote in message
news:CA357EB46E292C6AF7DD6110EF8E89E6@in.WebX.maYIadrTaRb...
> Would be nice since i don't have vb 🙂
> --
> Bobby C. Jones
0 Likes
Message 6 of 12

Anonymous
Not applicable
B E A U T I F U L Frank. Worked like a charm! I don't suppose that you
know what attribute to set to hide a procedure or a private variable?
NewEnum & my private property variables, like lngCount, are showing up in
the object browser 😞

I'm still digesting your other suggestions and rethinking my approach to
these objects and collections. Is it uncommon to create a class that
requires this syntax:

Dim ws as Variant
Dim objWSS as WorkingStates

Set objWSS = New WorkingStates
For Each ws in objWSS
objWSS.item(ws).MyCoolProperty = "Ice"
Debug.Print objWSS.Item(ws).Name
Next ws

I think that this syntax is required if I use a dictionary object instead of
a collection object, but I don't want to create some weird non-standard code
that nobody knows how to use. It also sidesteps my concerns about the
performance hit on initializing the class. The majority of the time this
code will be used like this:

WorkingStates.Item("WD").SetCurrent

Where I'm only interested in a single working state and don't care about
initializing all of them. Thanks for all the help.
--
Bobby C. Jones
p.s. - I'll be giving you a break and will be directing my next set of
questions at the guys that got me thinking that I needed to switch from lisp
to vb 🙂
0 Likes
Message 7 of 12

Anonymous
Not applicable
> I don't suppose that you know what attribute
> to set to hide a procedure or a private variable?

Nope. VB/A makes viewing hidden members a simple right-click so
there's not much use in trying to do this. You can get around this to
some extent by using VB's version of coclasses but it won't work with
VBA.

> Dim ws as Variant
> Dim objWSS as WorkingStates
>
> Set objWSS = New WorkingStates
> For Each ws in objWSS
> objWSS.item(ws).MyCoolProperty = "Ice"
> Debug.Print objWSS.Item(ws).Name
> Next ws

That's definitely a different approach. The enumerator should be a
WorkingState object, not an index into a dictionary containing
WorkingState objects. In addition, your collection object should be
structured so that Item is the default property allowing code like
this: objWSS("WD").SetCurrent.

> p.s. - I'll be giving you a break

I don't mind one bit. Keeps my mind from getting rusty.

--
"That's no ordinary rabbit."
http://www.acadx.com

Visit my site for a chance to win
a free, autographed copy of
Jerry Winters' "AutoCAD Visual Basics"

"Bobby Jones" wrote in message
news:252BA2DBFB05A80E167EB45C89AAFA59@in.WebX.maYIadrTaRb...
> B E A U T I F U L Frank. Worked like a charm!
0 Likes
Message 8 of 12

Anonymous
Not applicable


I figured it was. Oh well. I just changed from a collection to a
dictionary and the code choked on the NewEnum property. I'll stick with the
collection object for now and see if I can harass Paul Lomax into sending me
an example of how to imlement the dictionary object he's touting 🙂 I'm
going to see if I can get some code slapped together this afternoon and post
it here for review & scrutiny. Thanks again.
--
Bobby C. Jones
0 Likes
Message 9 of 12

Anonymous
Not applicable
> You can get around this to some extent by using
> VB's version of coclasses but it won't work with
> VBA.

After a quick test, I found that *all* of the private members in a
coclass are unviewable in the Object Browser, even with "Show hidden
members" selected.

Also, I've been thinking that maybe your architecture needs some
reworking. If the typical use for your WorkingState is only one at a
time, why bother with a collection object at all? Just write one
publicly creatable parent class and offer a method to retrieve the
number of working states stored in the registry as well as a method
that will retrieve the working state at any given index. The
WorkingState object should be public but not creatable.

This kind of arrangement is used quite often in C++ and doesn't
require much more work than a collection:

' Collection
Dim ws As WorkingState
Dim wsCollection As WorkingStates

Set wsCollection = New WorkingStates
For Each ws In wsCollection
ws.DoSomething
Next

' Index
Dim wsCollection As WorkingStates
Dim i As Integer

Set wsCollection = New WorkingStates
i = wsCollection.GetNumStates

For i = 1 to i ' 0 to i - 1 (take your pick)
wsCollection(i).DoSomething
' wsCollection.Item(i).DoSomething
' wsCollection.GetState(i).DoSomething
Next

If you use a dictionary to catalog your states, you could use a
GetStates method which returns a variant array containing the return
value of your dictionary's GetKeys method. Then it's just another For
Each loop for your user:

Dim wsCollection As WorkingStates
Dim key, keys

Set wsCollection = New WorkingStates
keys = wsCollection.GetStates

For Each key In keys
wsCollection(key).DoSomething
Next


--
"That's no ordinary rabbit."
http://www.acadx.com

Visit my site for a chance to win
a free, autographed copy of
Jerry Winters' "AutoCAD Visual Basics"
0 Likes
Message 10 of 12

Anonymous
Not applicable
I agree. The main reason I started this thread was because I was unhappy
with my architecture. Now, thanks to your suggestions, I think I'm starting
to get on the right track. Here's what I've got so far in the WorkingStates
class:

Option Explicit
Private dicWorkingStates As Dictionary
Private lngCount As Long

Private Sub Class_Initialize()
Dim tmpArray As Variant
Dim i As Integer

'Get all working states saved in registry
tmpArray = GetAllSettings("WorkingStates", "States")

'populate dictionary with WS names
Set dicWorkingStates = New Dictionary
For i = LBound(tmpArray) To UBound(tmpArray)
dicWorkingStates.Add Key:=tmpArray(i, 0), Item:=tmpArray(i, 0)
Next i

'set properties
lngCount = dicWorkingStates.Count

End Sub

Private Sub Class_Terminate()
Set dicWorkingStates = Nothing
End Sub

Public Property Get Count() As Long
Count = lngCount
End Property

Public Function Item(Name As Variant) As WorkingState
If dicWorkingStates.Exists(Name) Then
Dim mobjWS As WorkingState
Set mobjWS = New WorkingState
With mobjWS
.Name = Name
.Layers = GetAllSettings("WorkingStates", "States\" & Name)
End With
Item = mobjWS
End If
End Function

Public Function GetStates() As Variant
GetStates = dicWorkingStates.Keys
End Function

There's still a lot of work for me to do, but I think that it will go much
faster now that I've cleared my mind of the mental roadblocks. What do I
need to do to make the item method the default method? And any advise on
raising errors? I haven't done any reasearch on this yet, so don't answer
unless you want to feed my lazyness attributes 🙂 Thanks a lot Frank.
--
Bobby C. Jones
0 Likes
Message 11 of 12

Anonymous
Not applicable
That looks workable. One question though. I see a way to use the
output of the GetStates method to obtain individual states but no such
method exists for Count. Without a method that accepts a numeric index
as an argument, do you need a Count property? Or will a dictionary
take a number in place of a key?

As for the marking the Item method as the default, open the class file
in a text editor and add the noted line:

Public Function Item() As WorkingState
Attribute Item.VB_UserMemId = 0 'Add this part
...
End Function

Good luck and happy programming 😉

--
"That's no ordinary rabbit."
http://www.acadx.com


"Bobby Jones" wrote in message
news:FCFD78DAD8B01162104A358E3749DC73@in.WebX.maYIadrTaRb...
> I agree. The main reason I started this thread was
> because I was unhappy with my architecture.
> Now, thanks to your suggestions, I think I'm
> starting to get on the right track.
0 Likes
Message 12 of 12

Anonymous
Not applicable
Hmmm, a dictionary will only accept a key value, not an index value. And
thinking about how this class will be used, I also don't see any need for a
count property. Joe was right, creating a class really does force you think
thru the entire process. The wad of scribbled notes in file 13 under my
desk is evidence of just how much thought I've put into it. Now if I can
just find the one or two good thoughts.....

Thanks again for the default method tip. Now on to making this an actual
working piece of code 🙂
--
Bobby C. Jones
I'll be back...
0 Likes