Add Class objects to Collection using a Function in VBA - vba

I have this class:
Option Explicit
Public Code As String
Public ArticleType As String
Public Division As String
Public Devise As String
Public GroupePrix As String
Property Get CodeOnly() As String
CodeOnly = Replace(Code, "*", "")
End Property
And this function:
Function addFabricant(cod As String, art As String, div As String, dev As String, grp As String) As Fabricant
Dim Fab As New Fabricant
Fab.Code = cod
Fab.ArticleType = art
Fab.Division = div
Fab.Devise = dev
Fab.GroupePrix = grp
Set addFabricant = Fab
End Function
And I want to do something like this:
Set Fabricants = New Collection
'This is where the code fail with error 438
Fabricants.Add (addFabricant("Code", "Sample", " ", "DogeCoin", "420"))
...
But it does not work. I'm new to VBA so I may be missing something.
I know that :
Set test = addFabricant("Code", "Sample", " ", "DogeCoin", "420")
Fabricants.Add (test)
is working, but it would add double the code to add all the Fabricant this way.

I think you want "user defined types"
Public Type MyType
MyInt As Integer
MyString As String
MyDoubleArr(2) As Double
End Type
Then use it as a collection or an array.
Dim MyArr(2) As MyType
MyArr(0).MyInt = 31
MyArr(0).MyString = "VBA"
MyArr(0).MyDoubleArr(0) = 1
MyArr(0).MyDoubleArr(1) = 2
MyArr(0).MyDoubleArr(2) = 3
MyArr(1).MyInt = 32
MyArr(1).MyString = "is"
MyArr(1).MyDoubleArr(0) = 11
MyArr(1).MyDoubleArr(1) = 22
MyArr(1).MyDoubleArr(2) = 33
MyArr(2).MyInt = 33
MyArr(2).MyString = "cool"
MyArr(2).MyDoubleArr(0) = 111
MyArr(2).MyDoubleArr(1) = 222
MyArr(2).MyDoubleArr(2) = 333

I would create a wrapper class for your collection and add methods to the FabricantCollection to do what you want. Here is a FabricantCollection started for you:
Option Compare Database
Option Explicit
Private Const MODULE_NAME As String = "FabricantCollection"
Private m_oCollection As Collection
Private Sub Class_Initialize()
Set m_oCollection = New Collection
End Sub
Private Sub Class_Terminate()
If Not m_oCollection Is Nothing Then
Set m_oCollection = Nothing
End If
End Sub
Public Function Add(oFabricant As Fabricant) As Long
m_oCollection.Add oFabricant, oFabricant.Code
Add = m_oCollection.Count
End Function
Public Sub Clear()
Set m_oCollection = New BaseCollection
End Sub
Public Property Get Count() As Long
Count = m_oCollection.Count
End Property
Public Function Item(ByVal vKey As Variant) As Fabricant
Set Item = m_oCollection.Item(vKey)
End Function
Public Function Remove(ByVal vKey As Variant) As Fabricant
Set Remove = m_oCollection.Item(vKey)
m_oCollection.Remove vKey
End Function
Public Function AddNew( _
ByVal sCode As String, _
ByVal sArt As String, _
ByVal sDiv As String, _
ByVal sDev As String, _
ByVal sGrp As String) As Fabricant
Dim oFabricant As Fabricant
If Not oFabricant Is Nothing Then
Err.Raise vbObjectError + 3334, MODULE_NAME & ".AddNew", "Item already exists with that key: " & sCode
Exit Function
End If
Set oFabricant = New Fabricant
With oFabricant
.Code = sCode
.ArticleType = sArt
.Division = sDiv
.Devise = sDev
.GroupePrix = sGrp
End With
Add oFabricant
Set AddNew = oFabricant
End Function
Public Property Get Fabricant( _
ByVal sCode As String) As Variant
Set Fabricant = m_oCollection.Item(sCode)
End Property

Related

VBA Class Module - When to Stop and Where to declare public constant?

I have been coding vba for quite sometime now and it is only recently that I have begun diving into doing up some class module.
some questions on my mind is when should I stop including functions and properties in a class ?
i.e. I am creating a class module where by it record table properties (i.e. row of header column) I went on further to create a retrieve data function where it gets data from other table with the same class.
Question 1: am I over doing it? I tend to want to put all the function in one class.
Question 2: if I want to set all classes to have the same default initialization do I declare a public const in the class module itself or the normal modules?
Here is my class module :
Private pHeaderNames As Object
Private pHeaderRow As Long
Private pSubHeaderNames As Object
Private pSubHeaderRow As Long
Private pDataRowStart As Long
Private pInputColStart As Long
Private pTableColStart As Long
Private pHeaderLastCol As Long
Private pTblWorksheet As Worksheet
Private pFileURL As String
Private pFileName As String
'---------------------------------------------- Initialization
Private Sub Class_Initialize()
pHeaderRow = 1
pDataRowStart = 2
pTableColStart = 1
pInputColStart = pTableColStart
pSubHeaderRow = pHeaderRow
pHeaderLastCol = Cells(pHeaderRow, Columns.Count).End(xlToLeft).Column
Set pHeaderNames = CreateObject("Scripting.Dictionary")
Set pSubHeaderNames = CreateObject("Scripting.Dictionary")
End Sub
Public Property Get FileURL() As String
FileURL = pFileURL
End Property
Public Property Let FileURL(Value As String)
pFileURL = Value
End Property
Public Property Get FileName() As String
FileName = pFileName
End Property
Public Property Let FileName(Value As String)
pFileName = Value
End Property
Function OpenWorkbook(URL As String, Name As String)
pFileURL = URL
pFileName = Name
Call Workbook_open(pFileURL, pFileName)
End Function
Public Property Let SetSheet(Value As String)
If pFileName - "" Then
Set pTblWorksheet = ActiveWorkbook.Worksheets(Value)
Else
Set pTblWorksheet = Workbook(pFileName).Worksheets(Value)
End If
End Property
Public Property Get TblWorksheet() As Worksheet
TblWorksheet = pTblWorksheet
End Property
Public Property Let TblWorksheet(Sheet As Worksheet)
pTblWorksheet = Sheet
End Property
'---------------------------------------------- HeaderLastCol
Public Property Get HeaderLastCol() As Long
HeaderLastCol = pHeaderLastCol
End Property
Public Property Let HeaderLastCol(Value As Long)
pHeaderLastCol = Value
End Property
'---------------------------------------------- HeaderRow
Public Property Let HeaderRow(Value As Long)
pHeaderLastCol = Cells(pHeaderRow, Columns.Count).End(xlToLeft).Column
pHeaderRow = Value
End Property
Public Property Get HeaderRow() As Long
HeaderRow = pHeaderRow
End Property
'---------------------------------------------- SubHeaderRow
Public Property Let SubHeaderRow(Value As Long)
pSubHeaderRow = Value
End Property
Public Property Get SubHeaderRow() As Long
SubHeaderRow = pSubHeaderRow
End Property
'---------------------------------------------- InputColStart
Sub SetInputColStart(KEY As Variant)
pInputColStart = pHeaderNames(KEY)
End Sub
Public Property Get InputColStart() As Long
InputColStart = pInputColStart
End Property
Public Property Let InputColStart(Value As Long)
pInputColStart = Value
End Property
'---------------------------------------------- DataRowStart
Public Property Get DataRowStart() As Long
DataRowStart = pDataRowStart
End Property
Public Property Let DataRowStart(Value As Long)
pDataRowStart = Value
End Property
'---------------------------------------------- TableColStart
Public Property Get TableColStart() As Long
TableColStart = pTableColStart
End Property
Public Property Let TableColStart(Value As Long)
pTableColStart = Value
End Property
'---------------------------------------------- HeaderName
Sub GetHeaderNames()
With pHeaderNames
For i = pTableColStart To pHeaderLastCol
If Not .Exists(UCase(Cells(pHeaderRow, i).Value)) Then
.Add UCase(Cells(pHeaderRow, i).Value), i
End If
Next i
End With
End Sub
Function HeaderName(KEY As String)
If pHeaderNames.Exists(KEY) Then
HeaderName = pHeaderNames(KEY)
Else
HeaderName = ""
End If
End Function
Function CountHeaderNames()
CountHeaderNames = pHeaderNames.Count
End Function
Function PrintHeaderObject()
For Each KEY In pHeaderNames.keys
Debug.Print KEY, pHeaderNames(KEY)
Next
End Function
'---------------------------------------------- SubHeaderName
Sub GetSubHeaderNames()
With pSubHeaderNames
For i = pTableColStart To pHeaderLastCol
If Not .Exists(UCase(Cells(pSubHeaderRow, i).Value)) Then
.Add UCase(Cells(pSubHeaderRow, i).Value), i
End If
Next i
End With
End Sub
Function SubHeaderName(KEY As String)
If pSubHeaderNames.Exists(KEY) Then
SubHeaderName = pSubHeaderNames(KEY)
Else
SubHeaderName = "" 'or raise an error...
End If
End Function
Function CountSubHeaderNames()
CountSubHeaderNames = pSubHeaderNames.Count
End Function
Function PrintSubHeaderObject()
For Each KEY In pSubHeaderNames.keys
Debug.Print KEY, pSubHeaderNames(KEY)
Next
End Function
Function RetrieveData(FromSht As Worksheet, ByVal FromTable As cTable)
Dim KEY As String
'CurrentSht = ActiveSheet
For i = pTableColStart To pHeaderLastCol
KEY = Cells(pHeaderRow, i).Value
If FromTable.HeaderName(KEY) = "" Then
GoTo Nexti
Else
With FromSht
.Activate
rD_LastRow = 10
Set Source = .Range(.Cells(FromTable.DataRowStart, FromTable.HeaderName(KEY)), _
.Cells(rD_LastRow, FromTable.HeaderName(KEY)))
End With
With CurrentSht
.Activate
.Range(.Cells(DataRowStart, i), _
.Cells(DataRowStart, i)) _
.Resize(Source.Rows.Count, Source.Columns.Count).Cells.Value = Source.Cells.Value
End With
End If
Nexti:
Next i
End Function
Here is the module, so you can see that I have always need to declare headerRow , subHeaderRow and Datarowstart, even thou I have a default initialization set in the class module, is there away to change the default initialization based on the main module? or I just have to do it in the class module? (I trying to make the class portable)
Sub test()
Dim sht As Worksheet
Set wb = ActiveWorkbook
Set sht = wb.Sheets("Skin(Units)")
With Worksheets("Skin(Units)")
.Activate
Set SkinUnits = New cTable
Debug.Print TypeName(SkinUnits)
SkinUnits.HeaderRow = 1
SkinUnits.SubHeaderRow = 3
SkinUnits.DataRowStart = 4
SkinUnits.GetHeaderNames
SkinUnits.GetSubHeaderNames
SkinUnits.PrintHeaderObject
SkinUnits.PrintSubHeaderObject
SkinUnits.SetInputColStart ("Start")
End With
With Worksheets("Pain(Units)")
.Activate
Set PainUnits = New cTable
PainUnits.HeaderRow = 1
PainUnits.SubHeaderRow = 3
PainUnits.DataRowStart = 4
PainUnits.GetHeaderNames
PainUnits.GetSubHeaderNames
PainUnits.PrintHeaderObject
PainUnits.PrintSubHeaderObject
PainUnits.SetInputColStart ("Start")
Debug.Print PainUnits.HeaderName("SKU")
Debug.Print TypeName(sht), TypeName(SkinUnits)
Call test22222(SkinUnits)
Call PainUnits.RetrieveData(sht, SkinUnits)
End With
End Sub
Function test22222(ByVal X As cTable)
Debug.Print X.HeaderRow
End Function

How to use GetType and GetFields?

I'm updating a program and there is about 40 classes. I need to create a function that takes two lists of type object as parameters. Both lists only have one item in them (A version of the item BEFORE any changes were made, and AFTER the changes happened). I'm using these objects to create a single object to implement an UNDO button. With these parameters I need to get the type of each and make sure they match, if not then something went wrong. Next I'll need to read in the fields/properties/members (Not sure what to choose) and then compare them to each other and find what changed so I can set that as the item description. I don't want to trace the whole code and add specific functions for each and I know there has got to be a way to do this generically. I have created this small mock-up program that semi works for what I am trying to do. I can get the class type out of the object in list, but i have no idea how to get fields or whatever.
I'm using a large database with entity framework.
Also Using VB.NET!
Thanks for the help!
Here's code for generic program:
Imports System.Reflection
Module Module1
Sub Main()
Dim Myself As New Human("Matthew", "Cucco", Now, "Blonde", 19, False)
Dim NotMe As New Human("Jake", "Cucco", Now, "Blonde", 19, False)
Dim Him As New Employee("Matt", "Cucco", Now, "Blonde", 19, False, 215, "LuK", True)
Dim Her As New Customer("Jessie", "Keller", Now, "Blonde", 19, True, 25, "Cereal", "me#gmail.com")
Dim ListofPeople As IList(Of Object) = {Myself, NotMe, Him, Her}
Dim ListofPeople2 As IList(Of Object) = {Myself, NotMe, Him, Her}
ObjectsAreSameClass(ListofPeople, ListofPeople2)
Console.ReadKey()
End Sub
Private Function ObjectsAreSameClass(object1 As IList(Of Object), object2 As IList(Of Object)) As Boolean
Dim ObjectType As Type = object1.First.GetType()
Dim AreSameClass As Boolean = Nothing
Console.WriteLine(ObjectType.ToString)
If (object1.First.GetType() = object2.First.GetType()) Then
AreSameClass = True
Console.WriteLine("Object1 is of type: " + object1.First.GetType().Name)
Console.WriteLine("Object2 is of type: " + object2.First.GetType().Name)
If (object1.First.GetType().Name = "Human") Then
Console.WriteLine("Yep this works")
End If
Else
AreSameClass = False
Console.WriteLine("Object1 is of type: " + object1.First.GetType().Name)
Console.WriteLine("Object2 is of type: " + object2.First.GetType().Name)
If (object1.First.GetType().Name = "Human") Then
Console.WriteLine("Yep this works")
Console.WriteLine(object1.First.GetType().GetFields().ToString)
End If
End If
Dim MyField As PropertyInfo() = ObjectType.GetProperties()
Dim i As Integer
For i = 0 To MyField.Length - 1
Console.WriteLine(MyField(i).ToString)
Next i
Console.WriteLine("Objects are equal? t/f : " + AreSameClass.ToString)
Return AreSameClass
End Function
Public Class Human
Public FirstName As String
Public LastName As String
Public Birthdate As Date
Public HairColor As String
Public Age As Integer
Public Gender As Boolean 'False for male, true for female
Public Sub New()
FirstName = ""
LastName = ""
Birthdate = Now
HairColor = ""
Age = 0
Gender = False
End Sub
Public Sub New(f As String, l As String, b As Date, h As String, a As Integer, g As Boolean)
FirstName = f
LastName = l
Birthdate = b
HairColor = h
Age = a
Gender = g
End Sub
End Class
Public Class Employee
Inherits Human
Dim EmployeeId As Integer
Dim PlaceOfEmployment As String
Dim IsManager As Boolean
Public Sub New()
MyBase.New()
EmployeeId = 0
PlaceOfEmployment = ""
IsManager = False
End Sub
Public Sub New(f As String, l As String, b As Date, h As String, a As Integer, g As Boolean, i As Integer, p As String, m As Boolean)
MyBase.New(f, l, b, h, a, g)
EmployeeId = i
PlaceOfEmployment = p
IsManager = m
End Sub
End Class
Public Class Customer
Inherits Human
'used for testing
Dim IdNumber As Integer
Dim FavoriteItem As String
Dim email As String
Public Sub New()
MyBase.New()
IdNumber = 0
FavoriteItem = ""
email = ""
End Sub
Public Sub New(f As String, l As String, b As Date, h As String, a As Integer, g As Boolean, i As Integer, fav As String, e As String)
MyBase.New(f, l, b, h, a, g)
IdNumber = i
FavoriteItem = fav
email = e
End Sub
End Class
End Module
This Currently displays this:
TestProject.Module1+Human
Object1 is of type: Human
Object2 is of type: Human
Yep this works
Objects are equal? t/f : True
Also for reference, here is my main program that I will be implementing this into:
Function NewItem(Before As IEnumerable(Of Object), After As IEnumerable(Of Object), ObjectType As String)
ObjectsAreSameClass(Before, After, ObjectType) 'Check if objects are same class
Dim BeforeFields() As FieldInfo = GetFieldData(Before) 'gets all field info, saves to an array
Dim AfterFields() As FieldInfo = GetFieldData(After)
'Now check and make sure the objects are not the same
Dim ThisChanged As FieldInfo
If (ObjectValuesAreEqual(BeforeFields, AfterFields) = True) Then
'These objects did not not change
ThisChanged = Nothing
Else
'Change occured, find out where
ThisChanged = FindWhatChanged(BeforeFields, AfterFields)
End If
'Create a new UndoRedo item and give it these values
Dim UndoRedoNow As New ClsUndoRedo
UndoRedoNow.BeforeObject = Before.Single
UndoRedoNow.AfterObject = After.Single
UndoRedoNow.ObjectCounter += 1
UndoRedoNow.WhatChanged = ThisChanged
If WhatGroupChanged.isDeleted Then
UndoRedoNow.WhatAction = Before.Single.GetType().ToString + " item was Deleted"
ElseIf WhatGroupChanged.isNew Then
UndoRedoNow.WhatAction = After.Single.GetType().ToString + " item was created"
ElseIf WhatGroupChanged.isChanged Then
UndoRedoNow.WhatAction = After.Single.GetType().ToString + " item was changed"
End If
UndoRedoNow.WhatGroupChanged.isRedo = False 'Make sure it is not a redo object
'Now add object to list
ChangeLog.Add(UndoRedoNow)
Return Nothing
End Function
Private Function ObjectsAreSameClass(before As IEnumerable(Of Object), after As IEnumerable(Of Object), WhatType As String) As Boolean
Dim AreSameClass As Boolean = False
Try
If (before.Single.GetType() = after.Single.GetType() Or (before Is Nothing) Or (after Is Nothing)) Then
'Objects are of the same class or nothing
If before Is Nothing Then
WhatGroupChanged.isNew = True 'New item
ElseIf after Is Nothing Then
WhatGroupChanged.isDeleted = True 'Deleted item
Else
WhatGroupChanged.isChanged = True 'item was changed
End If
AreSameClass = True
End If
Catch
'Need to raise error
End Try
Return AreSameClass
End Function
''' <summary>
''' This function will return all of the fields for a certain class as well as the data stored in them
''' </summary>
''' <param name="list"></param>
''' <returns></returns>
Public Shared Function GetFieldData(ByVal list As IList(Of Object)) As FieldInfo()
Dim fields() As FieldInfo = list.Single.GetType().GetFields()
Return fields
End Function
''' <summary>
''' This function will check that the values in the datafields are not equal
''' </summary>
''' <param name="Before"></param>
''' <param name="After"></param>
''' <returns></returns>
Private Function ObjectValuesAreEqual(Before() As FieldInfo, After() As FieldInfo) As Boolean
Dim isEqual As Boolean = New Boolean 'This will keep track of if the elements are equal or not
For index As Integer = 0 To (Before.Count - 1)
If Before.ElementAt(index).GetValue(Before.ElementAt(index)).Equals(After.ElementAt(index).GetValue(After.ElementAt(index))) Then
'They are equal so set to true
isEqual = True
Else
'They are not equal so set to false and return
isEqual = False
Return isEqual
End If
Next
Return isEqual
End Function
Private Function FindWhatChanged(Before() As FieldInfo, After() As FieldInfo) As FieldInfo
Dim ThisIsChange As FieldInfo
For index As Integer = 0 To (Before.Count - 1)
If Before.ElementAt(index).GetValue(Before.ElementAt(index)).Equals(After.ElementAt(index).GetValue(After.ElementAt(index))) Then
ThisIsChange = After.ElementAt(index)
Return ThisIsChange
Else
'Raise error
End If
Next
End Function
The proper way to preserve type information when working with unknown types is to write a generic function (and if necessary generic classes, structures, etc.).
Using GetType, in a perfect world, should never be needed.
Generic functions look like this:
Public Function MyGenericFunction(Of T)(myArg as T) as Integer
' do something with myArg1, myArg2 ... without knowing their exact type
Return 0
End Function
' or with multiple types
Public Function MyGenericFunction2(Of T1, T2, ... )(myArg1 as T1, myArg2 as T2, ...) as T1()
' do something with myArg1, myArg2 ... without knowing their exact type
Return { myArg1 }
End Function
When you call these functions, the generic types are usually automatically deduced from the arguments you passed. If they can't be guessed, you will need to explicitly annotate the types, like this:
Dim x = MyGenericFunction(Of SomeClass1)(foo)
Dim x = MyGenericFunction(Of SomeClass2)(foo)
A full guide here: https://msdn.microsoft.com/en-us/library/w256ka79.aspx
However, if you need to handle specific types with the same function, then what you want to do use is a more narrow tool: overloading, or more technically parametric polymorphism.
What that means is, you will need to provide two (or more) different definitions of the same function ( = having the same name), that accept parameters of different types.
A simple example:
Public Class MyClass1
Public Foo1 As String = "foo1"
End Class
Public Class MyClass2
Public Foo2 As String = "foo2"
End Class
Public Sub MyFunction(arg as MyClass1)
Console.WriteLine(arg.Foo1)
End Sub
Public Sub MyFunction(arg as MyClass2)
Console.WriteLine(arg.Foo2)
End Sub
Dim x as Object
' let's give x a random value of either MyClass1 or MyClass2,
' and we don't know in advance which one
If DateTime.Today.DayOfWeek = DayOfWeek.Tuesday Then
x = new MyClass1
Else
x = new MyClass2
End If
' the program will automatically invoke the correct function based on x's value, and print either "foo1" or "foo2"
MyFunction(x)

Merge two Array Properties in Class

I have a Class (TestClass) with the following three properties:
Private myArr1() As String
Private myArr2() As String
Private myFinalArray() As String
Private Sub Class_Initialize()
ReDim myArr1(0 To 3)
ReDim myArr2(0 To 2)
ReDim myFinalArray(0 To 6)
End Sub
Public Property Get arr1(ByVal index As Long) As Double
arr1 = myArr1(index)
End Property
Public Property Let arr1(ByVal index As Long, ByVal myvalue As Double)
myArr1(index) = myvalue
End Property
Public Property Get arr2(ByVal index As Long) As Double
...
Public Property Let arr2(ByVal index As Long, ByVal myvalue As Double)
...
Public Property Get FinalArray(ByVal index As Long) As Double
...
Public Property Let FinalArray(ByVal index As Long, ByVal myvalue As Double)
...
Here we have just two arrays that I fill with data:
Sub test()
Dim t As TestClass
Set t = New TestClass
For i = 0 To 3
t.arr1(i) = i
Next
For i = 0 To 2
t.arr2(i) = i
Next
t.GetFinalValues (t)
End Sub
My Problem now is that these array elements must be rearranged according to a confused pattern I want to write a property for that but it is not working. My idea was to add the following function to my class:
Public Function GetFinalValues(ByRef t As TestClass) As Double()
'Imput parameter arrX can ben the Value as well as the Bench arrays.
Dim arr1(2) As Double
Dim arr2(3) As Double
Dim i As Integer
Dim arrCollection(6) As Double
arrCollection(0) = t.arr1(0)
arrCollection(1) = t.arr2(0)
arrCollection(2) = t.arr2(1)
arrCollection(3) = t.arr1(1)
arrCollection(4) = t.arr2(2)
arrCollection(6) = t.arr1(2)
arrCollection(5) = t.arr2(3)
'Assign return object
For i = 0 To 6
FinalArray(i) = arrCollection(i)
Next i
GetFinalValues
End Function
If I run this the code stops at t.GetFinalValues(t) giving me the Errormessage: Object doesent support property or method. Anyone who can help me get this working? Or has a rebuild idea for a even better solution to that problem?
EDIT: I added vb.net since this might be a construction problem that is not specified to vba
You have two issues:
First you should remove the parentheses from this line:
t.GetFinalValues (t)
so it's just:
t.GetFinalValues t
Then your function needs to return a String array not Double, since that's the type of the private variable. Your class code becomes something like this:
Private Sub Class_Initialize()
ReDim myArr1(0 To 3)
ReDim myArr2(0 To 2)
ReDim myFinalArray(0 To 6)
End Sub
Public Property Get arr1(ByVal index As Long) As Double
arr1 = myArr1(index)
End Property
Public Property Let arr1(ByVal index As Long, ByVal myvalue As Double)
myArr1(index) = myvalue
End Property
Public Property Get arr2(ByVal index As Long) As Double
arr2 = myArr1(index)
End Property
Public Property Let arr2(ByVal index As Long, ByVal myvalue As Double)
myArr2(index) = myvalue
End Property
Public Property Get FinalArray(ByVal index As Long) As Double
FinalArray = myArr1(index)
End Property
Public Property Let FinalArray(ByVal index As Long, ByVal myvalue As Double)
myFinalArray(index) = myvalue
End Property
Public Function GetFinalValues(ByRef t As TestClass) As String()
'Imput parameter arrX can ben the Value as well as the Bench arrays.
Dim arr1(2) As Double
Dim arr2(3) As Double
Dim i As Integer
Dim arrCollection(6) As Double
arrCollection(0) = t.arr1(0)
arrCollection(1) = t.arr2(0)
arrCollection(2) = t.arr2(1)
arrCollection(3) = t.arr1(1)
arrCollection(4) = t.arr2(2)
arrCollection(6) = t.arr1(2)
arrCollection(5) = t.arr1(3)
'Assign return object
For i = 0 To 6
FinalArray(i) = arrCollection(i)
Next i
GetFinalValues = myFinalArray
End Function
Note that you were also trying to use t.arr2(3) which exceeds the number of elements in arr2 so I assumed you meant t.arr1(3)

Get item in VBA Collection by Name

Ahoy hoy,
I'm trying to do stuff to a custom object in a custom collection by referencing it's name property in VBA Excel. I swear it worked before (or at least didn't throw an error) and now its kaput. I'm getting an invalid call or argument error when I try to Get something by a string. Thanks in advance for even reading this too, any help is appreciated. <\edit>
Here's the collection:
Option Explicit
Private DRAFields As New Collection
Sub Add(Name As String, Optional colNbr As Long, Optional Exists As Boolean)
Dim fld As New DRAFld
fld.colNbr = colNbr
fld.Name = Name
fld.Exists = Exists
DRAFields.Add fld
End Sub
Property Get Item(NameOrNumber As Variant)
Set Item = DRAFields(NameOrNumber) '<------- Error here
End Property
The collections has items added by passing an array of names in to a function and the collection is returned without issue. I can iterate over by using the key. But the error happens if get as such: Debug.Print myFlds.Item("Customer").colNbr
And the object class just in case:
Option Explicit
Private clmNbrPvt As Long
Private namePvt As String
Private existsPvt As Boolean
Public Property Get colNbr() As Long
colNbr = clmNbrPvt
End Property
Public Property Let colNbr(lngParam As Long)
clmNbrPvt = lngParam
End Property
Public Property Get Name() As String
Name = namePvt
End Property
Public Property Let Name(strParam As String)
namePvt = strParam
End Property
Public Property Get Exists() As Boolean
Exists = existsPvt
End Property
Public Property Let Exists(booParam As Boolean)
existsPvt = booParam
End Property
And why not that function too:
Function validateAndBuildDRAFields(ByRef arrReqFields() As String, _
inputSheet As Worksheet, _
Optional VBAModule As String) As clsDRAFields
Dim lEndCol As Long: lEndCol = Standard.zGetLastColumn(inputSheet, 1)
Dim i As Long
Dim x As Long
Dim intExit As Long
Dim myDRAFields As New clsDRAFields
Set validateAndBuildDRAFields = myDRAFields
'Builds myDRAFields items from arrReqFields
For i = LBound(arrReqFields) To UBound(arrReqFields)
myDRAFields.Add arrReqFields(i)
Next i
'checks if required fields exist on input sheet
'if found then sets column number and exists = true
For i = 1 To myDRAFields.Count
For x = 1 To lEndCol
If inputSheet.Cells(1, x) = myDRAFields.Item(i).Name Then
myDRAFields.Item(i).colNbr = x
myDRAFields.Item(i).Exists = True
intExit = intExit + 1
Exit For
End If
Next x
If intExit = UBound(arrReqFields) + 1 Then Exit For
Next i
' tells user if there are any missing fields and ends if true
If (Not intExit = UBound(arrReqFields) + 1) Or _
intExit = 0 Then
For i = 1 To myDRAFields.Count
If myDRAFields.Item(i).Exists = False Then
Call Standard.TheEndWithError("I couldn't find the " & myDRAFields.Item(i).Name & _
" column in your file. Please add " & myDRAFields.Item(i).Name & _
" to your DRA Layout.", False, VBAModule)
End If
Next i
Set myDRAFields = Nothing
Standard.TheEnd
End If
End Function
To access a collection item by its key, you have to supply a key when you add the item to the collection. The key is optional. When you access a collection item with a string, the Item method assumes you want to match the key. When you use an integer, it assumes you want the positional index.
So, change the line in your Add method to
DRAFields.Add fld, fld.Name
and you'll be able to access items by their Name property.

Accessing data from collection VBA

I'm trying to access data from collection by using .item(). What I am trying to do is to collect data in collection function fncPopCcyLst and access it by .item(1) in cbSortCcy to get the row number. This is a test to see if I can store several data in my collection and access them via .item(). However, I get a VBA runtime error '5'. Will someone guide me kindly what I am doing wrong? Thank you.
Below are my codes.
Class Module: clsSngGenUtl
Private prpSngStrVal As String
Private prpSngRowNum As Long
Private prpSngClmNum As Long
'++ Define properties
'== String row number
Public Property Get SngStrVal() As String
SngStrVal = prpSngStrVal
End Property
Public Property Let SngStrVal(ByRef varStrVal As String)
prpSngStrVal = varStrVal
End Property
'++ Define properties
'== Scalar row number
Public Property Get SngRowNum() As Long
SngRowNum = prpSngRowNum
End Property
Public Property Let SngRowNum(ByVal varRowNum As Long)
prpSngRowNum = varRowNum
End Property
'++ Define properties
'== Single column number
Public Property Get SngClmNum() As Long
SngClmNum = prpSngClmNum
End Property
Public Property Let SngClmNum(ByRef varClmNum As Long)
prpSngClmNum = varClmNum
End Property
'++ Define functions
'== function get row number
Public Function fncGetRowNum(ByRef varWbName As Workbook, ByVal varWsName As String, ByRef varSttClm As Long) As Long
On Error GoTo Exception
prpSngRowNum = 0
prpSngRowNum = varWbName.Sheets(Trim(varWsName)).Cells(Rows.Count, varSttClm).End(xlUp).Row
fncGetRowNum = prpSngRowNum
ExitHere:
Exit Function
Exception:
Resume ExitHere
End Function
'== function get column number
Public Function fncGetClmNum(ByRef varWbName As Workbook, ByVal varWsName As String, ByRef varSttRow As Long) As Long
On Error GoTo Exception
prpSngClmNum = 0
prpSngClmNum = varWbName.Sheets(Trim(varWsName)).Cells(varSttRow, Columns.Count).End(xlToLeft).Column
fncGetClmNum = prpSngClmNum
ExitHere:
Exit Function
Exception:
Resume ExitHere
End Function`
Below is my collection class: clsColCcySrt
'++ Declare variables
Private prpColCcySrt As Collection
'++ Define properties
Public Property Get ColCcySrt() As Collection
Set ColCcySrt = prpColCcySrt
End Property
Public Property Set ColCcySrt(varColCcy As Collection)
Set prpColCcySrt = varColCcy
End Property
Public Function fncGetCcyRow(ByRef varStrVal As String) As Long
On Error GoTo Exception
Dim clsSngGen As clsSngGenUtl
Dim varRowNum As Long
varRowNum = 0
For Each clsSngGen In Me.ColCcySrt
varRowNum = clsSngGen.SngRowNum()
Next clsSngGen
'== Return value
fncGetCcyRow = varRowNum
ExitHere:
Exit Function
Exception:
If fncGetCcyRow = 0 Then
MsgBox "Exception: Value is <" & fncGetCcyRow & ">."
End If
Resume ExitHere
End Function
`
"Regular" Module to populate array: fncPopFxLst
`
Public Function fncPopCcyLst(ByRef varWbName As String, ByRef varWsName As String, ByRef varCcyTyp As String) As Collection
Dim clnColCcy As Collection
Dim clsArrGen As clsArrGenUtl
Dim clsSngGen As clsSngGenUtl
Dim varWbName As Workbook
Set clnColCcy = New Collection
'== Start collecting items
Set clsSngGen = New clsSngGenUtl
Set varWbName = ThisWorkbook
clsSngGen.SngStrVal = "Reuters"
clsSngGen.SngRowNum = clsSngGen.fncGetRowNum(varWbName, varWsName, 1)
clnColCcy.Add clsSngGen
Set fncPopCcyLst = clnColCcy
End Function
`
Lastly, the subroutine `
Private Sub cbSortCcy()
Dim clsColCcy As clsColCcySrt
Dim varDirPth As String
Dim varCcySrc As String
Dim varWsStrg As String
Dim varWbStrg As String
varDirPth = tbDirectoryName & "\" & tbFileName
varCcySrc = "Currency"
varWsStrg = "List"
varWbStrg = varDirPth
Set clsColCcy = New clsColCcySrt
Set clsColCcy.ColCcySrt = fncPopCcyLst(varWbStrg, varWsStrg, varCcySrc)
'Debug.Princ clsColCcy.fncGetCcyRow("Reuters")
Debug.Print clsColCcy.ColCcySrt.Item(1)
End Sub
`
VBA Run-time error '5' is:
"Invalid procedure call or argument"
In your class clsColCcySrt, you have a line:
varRowNum = clsSngGen.SngRowNum()
which would be correct if SngRowNum were a function and not a property. Remove the parentheses () to call the property.