call an object of a collection vba - vba

I have a collection of employees (object) and each employee has his/her own properties(attributes) such as ID, Age, etc.
I have defined a class module (named clsemployee) as below:
Public ID As Integer
Public Age As Integer
.
.
And I added the properties of ID, age and etc. to the collection as below in a module:
Public Sub employee_collection() ' this collection saves all of the employees records
Dim employee As Collection
Set employee = New Collection
Dim n As Integer
Dim i As Integer
Dim E1 As Variant
Dim j As Integer
n = 528
Dim a, b As String
For i = 3 To n
a = "A" + CStr(i) ' to get the values from the excel sheet
b = "B" + CStr(i)
Set E1 = New clsEmployee
E1.ID = Sheets("A").Range(a).Value ' save the valus of each employee in the collection
E1.Age = Sheets("A").Range(b).Value
employee.Add E1
Next i
End Sub
I do not know how to call this collection in my other modules (sub). Should I call it by value or call by reference? I do not want to repeat defining this employee in each and every sub that I have.

To expand upon what cyboashu said:
Global employee as Collection
Public Sub employee_collection()
Set employee = New Collection
....'rest of code here
End Sub
Public Sub use_collection()
Debug.print employee.count
End Sub
Note that the Global declaration needs to be in a module, also as stated by cyboashu.
Run the employee_collection code 1 time when you want to populate the collection with the employees. Then, you can simply use the collection in any further procedures as it has already been filled.
Note that it is possible for the Global variables to be reset. See here for a good explanation of this.

Related

Skip block of text from adding to dictionary vb.net

I want to know if there is a way to read a text file that lets say has content like so:
Store - 001P
Owner - Greg
Price - 45000
Employees - 30
Store - 002
Owner- Jen
Price - 34400
Now lets say I only want to work with the store information in the block where the store number contains a P. Is there a way to read the text file to check for the P after the delimiter, and then skip to the next instance of "store" in the text file? I tried to find which function is best as in different languages you can GoTo function, but I cannot find anything on this.
This is a snippet of my current code:
Dim columnV As New Dictionary(Of String, Integer)
Dim descriptionV As String
Dim quantityV As Integer
For Each totalLine As String In MyData
descriptionV = Split(totalLine, "-")(0)
quantityV = Split(totalLine, "-")(1)
If columnV.ContainsKey(descriptionV) Then
columnV(descriptionV) = colSums(descriptionV) + quantityV
Else
columnV.Add(descriptionV, quantityV)
End If
'Next
Ok, if I read this correct, you only want to worry about and process stores that end in a "P". Did I get that right?
and your data is like this then:
Store - 001P \n Owner - Greg \n Price - 45000 \n Employees - 30 \n
Store - 002\n Owner- Jen \n Price - 34400 \n
So in code we could go:
dim store as string
dim Owner as string
dim Price as decimal
dim employees as integer
' your code to read the data
now we have the process loop
For Each totalLine As String In MyData
store = trim(split(split(totalLine,"\n)(0),"-")(1))
Owner = trim(split(split(totalLine,"\n)(1),"-")(1))
Price = trim(split(split(totalLine,"\n")(3),"-")(1))
Employees = trim(split(split(totalLine,"\n")(4),"-")(1))
if strings.right(store,1) = "P" then
' our process code
If columnV.ContainsKey(store) Then
end if
Next
So you can use Strings.Right(some text,1) to get the right most character. You simply then have a if/then block around that code, and thus you skip anything that does not have a store with "P" as the last letter. The above is air code, but the simple concept here is to first:
Split out the line into the correct parts.
Check if store ends in "P" with the above if/then, and thus only stores ending in P will be processed.
However, your sample code and the variable names used really does not make a lot of sense.
It appears as if a record can be represented across multiple lines, but not a defined number of multiple lines (e.g. 001P is over 4 lines but 002 is over 3 lines).
It also appears as if the line represents the data in a {property} - {value} format.
The first thing that I would do is create a class to represent your record.
The second thing would be to get every line from your text file and iterate over them.
The third thing would be take the incoming information and convert it to your custom class which would be added to a List.
The last thing would be to then filter the List where Stores do not contain the letter "P".
Here is the code, in action:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Public Module Module1
Public Sub Main()
IO.File.WriteAllLines("test.txt", {
"Store - 001P",
"Owner - Greg",
"Price - 45000",
"Employees - 30",
"Store - 002",
"Owner - Jen",
"Price - 34400"
})
Dim lines() As String = IO.File.ReadAllLines("test.txt")
Dim stores As List(Of Store) = ConvertLinesToStores(lines)
Dim storesWithP() As Store = stores.Where(Function(s) s.StoreId.Contains("P")).ToArray()
Console.WriteLine("Stores with P: ")
Console.WriteLine(String.Join(Environment.NewLine, storesWithP.Select(Function(s) s.StoreId).ToArray()))
End Sub
Private Function ConvertLinesToStores(ByVal lines() As String) As List(Of Store)
Dim stores As New List(Of Store)
Dim item As Store
For Each line As String In lines
Dim parts() As String = line.Split(" - ")
If (lines.Length < 3) Then
Continue For
End If
Dim propertyName As String = parts(0)
Dim propertyValue As String = parts(2)
Dim employeeCount As Integer
If (propertyName = "Store") Then
If (item IsNot Nothing) Then
stores.Add(item)
End If
item = New Store() With {.StoreId = propertyValue}
ElseIf (propertyName = "Owner") Then
item.Owner = propertyValue
ElseIf (propertyName = "Price") Then
item.Price = propertyValue
ElseIf (propertyName = "Employees" AndAlso Integer.TryParse(propertyValue, employeeCount)) Then
item.EmployeeCount = employeeCount
End If
Next
If (item IsNot Nothing) Then
stores.Add(item)
End If
Return stores
End Function
End Module
Public Class Store
Public Property StoreId As String
Public Property Owner As String
Public Property Price As Integer
Public Property EmployeeCount As Integer
End Class
Fiddle: Live Demo
Assuming your text file has a consistent pattern the following will give you a list of your stores with all the properties.
My Stores.txt looks like this...
Store - 001P
Owner - Greg
Price - 45000
Employees - 30
Store - 002
Owner- Jen
Price - 34400
Employees - 20
Store - 03P
Owner - Mary
Price - 50000
Employees - 22
Store - 04P
Owner - Bill
Price - 42000
Employees - 90
I created a simple class to hold the store data.
Public Class Store
Public Property ID As String
Public Property Owner As String
Public Property Price As Decimal
Public Property Employees As Integer
End Class
First I read the file getting an array of lines. I loop through the lines of the file skipping ahead by 5 on each iteration (Step 5).This should hit the Store line (Store appears every 5 lines).
If it contains a P then I create a new Store and set it properties by moving down one line (the i + 1 etc.). After the properties are set, I add the Store to the list.
Now you have a List(Of Store) containing only the stores who's Id contains a P. You can access the items by index or in a For Each loop and have all the properties available.
Private StoreList As New List(Of Store)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim lines = File.ReadAllLines("C:\Users\maryo\Desktop\Stores.txt")
For i = 0 To lines.Length - 4 Step 5
Dim ID = lines(i).Split("-"c)(1).Trim
If ID.Contains("P") Then
Dim st As New Store
st.ID = ID
st.Owner = lines(i + 1).Split("-"c)(1).Trim
st.Price = CDec(lines(i + 2).Split("-"c)(1).Trim)
st.Employees = CInt(lines(i + 3).Split("-"c)(1).Trim)
StoreList.Add(st)
End If
Next
'Just to check if my list contains what I expected.
DataGridView1.DataSource = StoreList
End Sub

How to define and use a collection of ClassA inside a ClassB

Say I have a class called Person, with a Name and an Age properties, and another collection called Family, with properties like Income, Address, and a collection of Persons inside it.
I cannot find a full example on how to implement this concept.
Furthermore, as I am new to both collections and classes, I am not succeeding also in making a small subroutine to use these two functions.
Here is my best try, based on the limited resources available on the internet:
' Inside the class Module Person .........................
Public pName As String
Public pAge As Integer
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(value As String)
pName = value
End Property
' Inside the class Module Family .........................
' ... Income and address Properties are supposed
' ... declared and will not be used in this trial
Private colPersons As New Collection
Function AddP(aName As String, anAge As integer)
'create a new person and add to collection
Dim P As New Person
P.Name = aName
P.Age = anAge
colPersons.Add R ' ERROR! Variable colPersons not Defined!
End Function
Property Get Count() As Long
'return the number of people
Count = colPersons.Count
End Property
Property Get Item(NameOrNumber As Variant) As Person
'return this particular person
Set Item = Person(NameOrNumber)
End Property
And now the Subroutine that tries to use the above:
Sub CreateCollectionOfPersonsInsideAFamily()
'create a new collection of people
Dim Family_A As New Family
'add 3 people to it
Family_A.AddP "Joe", 13
Family_A.AddP "Tina", 33
Family_A.AddP "Jean", 43
'list out the people
Dim i As Integer
For i = 1 To Family_A.Count
Debug.Print Family_A.Item(i).Name
Next i
End Sub
Naturally this is giving errors: Variable Not defined (see above comment)
Sorry for the inconvenience... But the problem was that the line:
Private colPersons As New Collection
should not be place after other properties have been declared (here not shown: Address and Income)
After placing this line in the declaration area at the top of its class, all the code has proved to be correct.

VBA class instances

I'm having an issue in VBA where every item in the array is being replaced every time i add something to that array.
I am attempting to go through the rows in a given range and cast every row of that into a custom class (named 'CustomRow' in below example). there is also a manager class (called 'CustomRow_Manager' below) which contains an array of rows and has a function to add new rows.
When the first row is added it works fine:
https://drive.google.com/file/d/0B6b_N7sDgjmvTmx4NDN3cmtYeGs/view?usp=sharing
however when it loops around to the second row it replaces the contents of the first row as well as add a second entry:
https://drive.google.com/file/d/0B6b_N7sDgjmvNXNLM3FCNUR0VHc/view?usp=sharing
Any ideas on how this can be solved?
I've created a bit of code which shows the issue, watch the 'rowArray' variable in the 'CustomRow_Manager' class
Macro file
https://drive.google.com/file/d/0B6b_N7sDgjmvUXYwNG5YdkoySHc/view?usp=sharing
otherwise code is below:
Data
A B C
1 X1 X2 X3
2 xx11 xx12 xx13
3 xx21 xx22 xx23
4 xx31 xx32 xx33
Module "Module1"
Public Sub Start()
Dim cusRng As Range, row As Range
Set cusRng = Range("A1:C4")
Dim manager As New CustomRow_Manager
Dim index As Integer
index = 0
For Each row In cusRng.Rows
Dim cusR As New CustomRow
Call cusR.SetData(row, index)
Call manager.AddRow(cusR)
index = index + 1
Next row
End Sub
Class module "CustomRow"
Dim iOne As String
Dim itwo As String
Dim ithree As String
Dim irowNum As Integer
Public Property Get One() As String
One = iOne
End Property
Public Property Let One(Value As String)
iOne = Value
End Property
Public Property Get Two() As String
Two = itwo
End Property
Public Property Let Two(Value As String)
itwo = Value
End Property
Public Property Get Three() As String
Three = ithree
End Property
Public Property Let Three(Value As String)
ithree = Value
End Property
Public Property Get RowNum() As Integer
RowNum = irowNum
End Property
Public Property Let RowNum(Value As Integer)
irowNum = Value
End Property
Public Function SetData(row As Range, i As Integer)
One = row.Cells(1, 1).Text
Two = row.Cells(1, 2).Text
Three = row.Cells(1, 3).Text
RowNum = i
End Function
Class module "CustomRow_Manager"
Dim rowArray(4) As New CustomRow
Dim totalRow As Integer
Public Function AddRow(r As CustomRow)
Set rowArray(totalRow) = r
If totalRow > 1 Then
MsgBox rowArray(totalRow).One & rowArray(totalRow - 1).One
End If
totalRow = totalRow + 1
End Function
Your issue is using
Dim cusR As New CustomRow
inside the For loop. This line is actually only executed once (note that when you single F8 step through the code it does not stop on that line)
Each itteration of the For loop uses the same instance of cusR. Therefore all instances of manager added to the class point to the same cusR
Replace this
For Each row In cusRng.Rows
Dim cusR As New CustomRow
with this
Dim cusR As CustomRow
For Each row In cusRng.Rows
Set cusR = New CustomRow
This explicitly instantiates a new instance of the class

Loop through properties of a class in vba

I have created this class,
Interval.cls
public x as new collection
public y as new collection
public z as string
I wish to loop through the properties because I want the user to choose input x,y,z in the form so at my sub
sub test()
dim inter as new Intervals
inter.x.add "a"
inter.x.add "a"
inter.x.add "b"
inter.x.add "b"
inter.x.add "b"
userString1 = x
userString2 = a
' I want to make it dynamic so that whatever the user wants i can provide the results.
'i just want to make it possible to compare the userString to my properties
for each i in inter
'so i wish for i in this loop to be my properties x,y,z so i can make the if statement
if ( i = userString1) then
end if
next i
end sub
I know i can maybe make a tweek in the class to make it iterable, i don't know how to do it
any help is appreciated
'in class
Public Property Get Item(i As Integer) As Variant
Select Case ndx
Case 1: Item = Me.x
Case 2: Item = Me.y
Case 3: Item = Me.z
End Select
End Property
'in sub
Dim c as Collection
Dim s as String
For i = 1 to 3
if i < 3 then
set c = inter.Item(i)
'iterate through collection
else
s = inter.Item(i)
end if
next i
something like this is probably the easiest way to go, i didnt test it but hopefully it at least gets you started
Is this the sort of thing you are after?
'in class
Public x As New Collection
Public y As New Collection
Public z As String
Public Property Get Item(i As Integer) As Variant
Select Case i
Case 1: Item = Me.x
Case 2: Item = Me.y
Case 3: Item = Me.z
End Select
End Property
Sub try()
Dim userString2 As String
Dim userString1 As String
Dim inter As Interval
Dim i As Integer
Set inter = New Interval
inter.x.Add "a"
inter.x.Add "a"
inter.x.Add "b"
inter.x.Add "b"
inter.x.Add "b"
userString2 = "aabbb"
For i = 1 To inter.x.Count
userString1 = userString1 & inter.x.Item(i)
Next i
If userString1 = userString2 Then
'<do whatever>
End If
End Sub
OK how about forgetting the class and just using an array? The array of strings can be of any length and you can dynamically control it's size by the calling procedure.
Sub tryWithArray(ByRef StringArray() As String)
Dim userString2 As String
Dim i As Integer
userString2 = "b"
For i = 1 To UBound(StringArray())
If userString2 = StringArray(i) Then
'do something
End If
Next i
End Sub
I know this is an old post, but I wanted to share a solution I came up with when I had a similar issue.
VBA doesn't seem to offer any abstraction that allows you to refer to class members without naming them directly.
However, there is a way to loop through the members of a class in code. It’s kind of clunky, and it adds overhead, but makes it possible to address some or all of the members of your class from one or more loops without having to hard-code the property names every time. It might be useful if you have to step through the class from multiple places in your code.
Say you have a class with three properties (string A, integer B and double C), defined as usual with Property Let/Get statements, and private backing fields.
Now, add a function to your class definition that uses a named argument to return each of the values from the class itself.
Public Function MyValue (MyName As String) As Variant
Select Case MyName
Case “ValueA”
MyValue = Me.A
Case “ValueB”
MyValue = Me.B
Case “ValueC”
MyValue = Me.C
End Select
End Function
From your main code, you can now just pass in the string argument to get the desired value from the class. By creating a variant array of the desired name arguments in your code, you can loop through the array and retrieve the values from any instance of your class.
Dim VarList as Variant
Dim IntCounter as Integer
VarList = Array(“ValueB”, “ValueC”, “ValueA”)
For IntCounter = 0 to UBound(VarList)
Debug.Print MyClassInstance.MyValue(Cstr(VarList(IntCounter)))
Debug.Print MySecondClassInstance.MyValue(Cstr(VarList(IntCounter)))
Next IntCounter
This allows you to loop through as many or as few of the values from your class as you need, in any order, by just changing the order of the arguments in the array. (You would need to create a similar class function if you wanted to assign incoming values to the instance fields.)
As I said, it’s not perfect: it adds overhead, and unless all of the properties in your class happen to be the same type, it requires the function to return variant values, possibly forcing the caller to cast the returned values. But if you have to deal with numerous property values in multiple places in your code, it can be considerably less complicated than hard-coded direct assignments for each property name.

Looping through All collections in VBA

I have a program where I create several different collections in VBA. After the program completes, I need to delete the records in each collection. I have been able to delete the collections statically with the following code:
Sub Empty_Collections()
Dim Count As Integer
Dim i As Long
Count = Managers.Count
For i = 1 To Count
Managers.Remove (Managers.Count)
Next i
Count = FS.Count
For i = 1 To Count
FS.Remove (FS.Count)
Next i
Count = Staff.Count
For i = 1 To Count
Staff.Remove (Staff.Count)
Next i
Count = Clusters.Count
For i = 1 To Count
Clusters.Remove (Clusters.Count)
Next i
End Sub
However, as I may add additional Collections in the future, is it possible to have code similar to this:
Dim Item As Collection
Dim Count As Integer
Dim i As Long
For Each Item In Worksheets
Count = Item.Count
For i = 1 To Count
Item.Remove (Item.Count)
Next i
Next
While I hesitate to create globals like this, here is a possible solution:
In the ThisWorkbook Excel Object, add the following:
Private pCollections As Collection
Public Property Get Collections() As Collection
If pCollections Is Nothing Then: Set pCollections = New Collection
Set Collections = pCollections
End Property
Public Property Set Collections(Value As Collection)
Set pCollections = Value
End Property
Public Sub AddCollection(Name As String)
Dim Coll As New Collection
Me.Collections.Add Coll, Name
End Sub
Public Sub EmptyCollections()
Dim Coll As Collection
For Each Coll In Me.Collections
EmptyCollection Coll
Next Coll
End Sub
Private Sub EmptyCollection(ByRef Coll As Collection)
Do While Coll.Count > 0
' Remove items from the end of the collection
Coll.Remove Coll.Count
Loop
End Sub
Then add and work with collections as follows:
' Add collections
' (without helper)
Dim Managers As New Collection
ThisWorkbook.Collections.Add Managers, "Managers"
' (with helper)
ThisWorkbook.AddCollection "FS"
ThisWorkbook.AddCollection "Staff"
ThisWorkbook.AddCollection "Clusters"
' Add items to collection
' (access collection via named key for ease-of-use)
ThisWorkbook.Collections("Managers").Add "Alice"
ThisWorkbook.Collections("Managers").Add "Bob"
' (or original reference if desired
Managers.Add "Carl"
Debug.Print Managers.Count ' => 3
Debug.Print ThisWorkbook.Collections("Managers").Count ' => 3
' Add collection later
ThisWorkbook.AddCollection "FutureCollection"
' Empty all collections
ThisWorkbook.EmptyCollections
This may be a little more complex than what you are looking for, but it has the advantage of adding collections to a central location so that they can all be emptied later.