Add classes to a collection - vba

I have a class called "Transport":
Public Frequency As Double
Public SourceDest As String
Now, I want to read out some cells in Excel and fill a collection with these objects of that class.
Collection:
Dim JobList As New Collection
Sub:
Dim tmp As New Transport
For i = 1 To 20
tmp.Frequency = Round(Frequencies(i), 5)
tmp.SourceDest = Jobs(i)
JobList.Add tmp
Next
Unfortunately, it just adds the same class 20 times, but I want different classes. How can I solve this?
In general, I am new to VBA and all I want is to read two columns in a table, put the "pairs" together (like so: {Double, String}) in a kind of list {Double, String},{Double, String}, .... I tried types before, but apparently they can't be stored in collections, so I chose classes.

In order to create a different object of the class each time you loop, you have to 'tell' VBA to create it inside the loop.
In line 4 a new instance of Transport Class is created in each loop
Dim tmp As Transport
For i = 1 To 20
Set tmp = New Transport
tmp.Frequency = Round(Frequencies(i), 5)
tmp.SourceDest = Jobs(i)
JobList.Add tmp
Next

Related

VBA - "subobject" of a class

I want to build the structure of skills and those skills areas. For example: skill multiplication in the maths area.
My areas have some properties (name or weight), and my skills also have properties (name, weight, rank and so on).
I would like to use the following syntax:
Areas(1).Skills(2).Name = "solving equations"
Is it possible? Thanks to your help I can now use Areas(1).Name in my code and now I'm looking for help with the next step.
I suppose if I have Areas(1).Skill.Name syntax, I can handle with adding that index for skills, so the problem is exactly how to create a "subobject" of a class (using Area.Skill.Name still having the Area as an object)?
In order to do that you first need to insert to class modules into you VBA project.
In VBE right clikc your VBA project: Insert > Class module.
Add two class modules:
First you'll call Skill - in properties you'll change the name to Skill. Double click it and enter this code there:
Public Name As String
Public Weight As Double
Public Rank As Long
'any other fields you want
Second class module - again in properties change the name to Area.
From your description I assumed that you want to have collection of skills there. In order to do that, you need to click Tools from ribbon, go to References and add Microsoft.Scripting.Runtime. Then paste the code for this class:
Public Name As String
Public Weight As Double
Private pSkill As Collection
Property Get Skills() As Collection
Set Skill = pSkill
End Property
Property Let Skills(arg As Collection)
Set pSkill = arg
End Property
Note that we don't specify type of elements in pSkill, so you need to be careful when dealing with it.
After all this is done, you can use it as you want, like in this code:
Sub s()
Dim s1 As Skills, s2 As Skills
Set s1 = New Skill
Set s2 = New Skill
s1.Name = "solving equations"
s1.Weight = 2.03
s1.Rank = 1
s2.Name = "counting to ten"
s2.Weight = 1.01
s2.Rank = 2
Dim a(1) As Area, someSkills As Collection
Set someSkills = New Collection
someSkills.Add s1
someSkills.Add s2
Set a(0) = New Area
a(0).Skill = someSkills
MsgBox a(0).Skills(1).Name
End Sub

Initializing an indefinite number of classes in VBA: Using variable to name instance of class

As always, this may be something of a newb question but here goes:
I have a Class with 15 properties. Each class represents information about an item of stock (how many there are, how many recently shipped etc). Each time a class is initialized by passing it a stock code, it gathers all the data from other sources and stores it as properties of the class.
I want to be able to initialize n number of classes, dependent on the length of a list (never more than 200). I want to name those classes by their stock code, so that I can call up the information later on and add to it. The only problem is I don't know how to use a variable to name a class. I don't really want to write out 200 classes long hand because I'm sure there is a better way to do it than Diming: Stock1 As C_ICODE, Stock2 As C_ICODE, Stock3 As C_ICODE etc and initializing them in order, until input (from ActiveCell) = "" or it hits the maximum list length of 200. I would like to create as many class instances as there are stock codes if possible, and generate them something like this:
PseudoCode:
For Each xlCell In xlRange
strIN = xlCell.Value
Dim ICode(strIN) As New C_ICODE
ICode(strIN).lIcode = strIN
Next
Letting classname.lIcode = strIN provides the class with all the user input it needs and then it carries out various functions and subroutines to get the other 14 properties.
I would be very grateful if someone could let me know if this sort of thing is possible in VBA, and if so, how I could go about it? Definitely struggling to find relevant information.
You can use Dictionary object:
Dim ICode As Object
Set ICode = CreateObject("Scripting.Dictionary")
For Each xlCell In xlRange
strIN = xlCell.Value
ICode.Add strIN, New C_ICODE
ICode(strIN).lIcode = strIN
Next
I just did a quick test of this and it seems as though it might work for you. You can create an array to hold multiple instances of your class.
Sub thing()
Dim cArray(1 To 10) As Class1
Dim x As Long
For x = 1 To UBound(cArray)
Set cArray(x) = New Class1
Next
' Assume the class has a property Let/Get for SomeProperty:
For x = 1 To UBound(cArray)
cArray(x).SomeProperty = x * 10
Next
For x = 1 To UBound(cArray)
Debug.Print cArray(x).SomeProperty
Next
End Sub

Why won't this list of struct allow me to assign values to the field?

Public Structure testStruct
Dim blah as integer
Dim foo as string
Dim bar as double
End Structure
'in another file ....
Public Function blahFooBar() as Boolean
Dim tStrList as List (Of testStruct) = new List (Of testStruct)
For i as integer = 0 To 10
tStrList.Add(new testStruct)
tStrList.Item(i).blah = 1
tStrList.Item(i).foo = "Why won't I work?"
tStrList.Item(i).bar = 100.100
'last 3 lines give me error below
Next
return True
End Function
The error I get is: Expression is a value and therefore cannot be the target of an assignment.
Why?
I second the opinion to use a class rather than a struct.
The reason you are having difficulty is that your struct is a value type. When you access the instance of the value type in the list, you get a copy of the value. You are then attempting to change the value of the copy, which results in the error. If you had used a class, then your code would have worked as written.
try the following in your For loop:
Dim tmp As New testStruct()
tmp.blah = 1
tmp.foo = "Why won't I work?"
tmp.bar = 100.100
tStrList.Add(tmp)
Looking into this I think it has something to do with the way .NET copies the struct when you access it via the List(of t).
More information is available here.
Try creating the object first as
Dim X = New testStruct
and setting the properties on THAT as in
testStruct.blah = "fiddlesticks"
BEFORE adding it to the list.

How do you assign values to structure elements in a List in VB.NET?

I have a user-defined structure in a list that I am trying to change the value for in an individual element within the list of structures. Accessing the element is not a problem. However, when I try to update the value, the compiler complains:
"Expression is a value and therefore cannot be the target of the
assignment"
For example:
Public Structure Person
Dim first as String
Dim last as String
Dim age as Integer
End Structure
_
Public Sub ListTest()
Dim newPerson as Person
Dim records as List (Of Person)
records = new List (Of Person)
person.first = "Yogi"
person.last = "bear"
person.age = 35
records.Add(person)
records(0).first = "Papa" ' <<== Causes the error
End Sub
As the other comments said, when you refer to records(0), you get a copy of the struct since it is a value type. What you can do (if you can't change it to a Class) is something like this:
Dim p As Person = records(0)
p.first = "Papa"
records(0) = p
Although, I think it's just easier to use a Class.
There are actually two important concepts to remember here.
One is that, as Hans and Chris have pointed out, Structure Person declares a value type of which copies are passed between method calls.
You can still access (i.e., get and set) the members of a value type, though. After all, this works:
Dim people(0) As Person
people(0).first = "Yogi"
people(0).last = "Bear"
people(0).age = 35
So the other important point to realize is that records(0) accesses the List(Of Person) class's special Item property, which is a sugary wrapper around two method calls (a getter and setter). It is not a direct array access; if it were (i.e., if records were an array), your original code would actually have worked.
I had the same problem, and I fixed it by adding a simple Sub to the structure that changes the value of the property.
Public Structure Person
Dim first as String
Dim last as String
Dim age as Integer
Public Sub ChangeFirst(value as String)
me.first = value
End Sub
End Structure

Retrieving data from a VB.NET arraylist of objects

I am trying to retrieve the correct value from an ArrayList of objects (.NET 1.1 Framework):
I have the following defined:
Public AlList As New ArrayList
Public Class ItemInfo
Public ItemNo As Int16
Public ItemType As String
Public Reports As Array
Public PDFs As Array
End Class
The form_load event code contains:
Dim AnItemObj As New ItemInfo
Then a loop that includes:
AnItemObj.ItemNo = AFile.RecordId
AnItemObj.ItemType = temp
AlList.Add(AnItemObj)
So I should now have an ArrayList of these objects, however if I try to retrieve the data:
MsgBox(AlList(5).ItemNo)
I always get the ItemNo of the last value in the list.
What am I missing?
Put the following code:
Dim AnItemObj As New ItemInfo
inside the loop which adds AnItemObj to the list.
When you add a reference type to a list, you are only adding the reference, not the value.
This means that if you add 10 times the same instance to a list, it will add 10 times the same reference to the list. But if afterward you still have a reference to this instance you can modify its properties and as all 10 entries in the list point to the same reference in memory, all 10 entries will be modified.
So, you've got:
Dim AnItemObj As New ItemInfo
For ...
AnItemObj.ItemNo = AFile.RecordId
AnItemObj.ItemType = temp
AlList.Add(AnItemObj)
Next
What is happening here is you're creating a single object, setting the values on it, and adding a reference to it, to your list. You're then changing your ItemInfo and addign another reference to the same item to your list
You need to construct a new object on each loop, loosely thus:
Dim AnItemObj As ItemInfo
For ...
AnItemObj = New ItemInfo
AnItemObj.ItemNo = AFile.RecordId
AnItemObj.ItemType = temp
AlList.Add(AnItemObj)
Next
Are you creating a new instance of iteminfo for each increment of the loop?
I can't see your full loop code but I imagine the cause is not setting AnItemObj to a New ItemInfo object. So you just end up modifying the same object and adding it the the list again (all items in the list point to the same object).
AnItemObj = New ItemInfo()
AnItemObj.ItemNo = AFile.RecordId
AnItemObj.ItemType = temp
AlList.Add(AnItemObj)