I'm quite new to VBA (2 months in) and I'm trying to add three variables to a scripting dictionary in order to reformat an Excel Table and I am running into an error.
I have tried to add three variables by
countrydict.Add country, data, time
But I get an error message
Run-time error '450':
Wrong number of arguments or invalid property assignment
However it works if I write
countrydict.Add country, data 'or
countrydict.Add country, time
Dim lastrow As Long
Dim iter As Long
Dim diter As Long
Dim countrydict As Object
Dim country As String
Dim data As String
Dim time As String
Dim key As Variant
Dim i As Long
Const StartRow As Byte = 2
lastrow = Range("A" & StartRow).End(xlDown).Row
Set countrydict = CreateObject("Scripting.Dictionary")
Dim diter2 As Long, arr, arr2
With ActiveSheet
For iter = 2 To lastrow
country = Trim(.Cells(iter, 1).Value) '<<<<<
data = Trim(.Cells(iter, 2).Value) '<<<<<
time = Trim(.Cells(iter, 3).Text) '<<<<<
If countrydict.Exists(country) Then
If Not InStr(1, countrydict(country), data) > 0 Then
countrydict(country) = countrydict(country) & _
"|" & data & "/" & time
End If
Else
countrydict.Add country, data, time '<<<<<<<
End If
Next
iter = 2
For Each key In countrydict
.Cells(iter, 1).Value = key & ":"
.Cells(iter, 1).Font.Bold = True
.Cells(iter, 1).Font.ColorIndex = 30
iter = iter + 1
arr = Split(countrydict(key), "|")
For diter = 0 To UBound(arr)
arr2 = Split(arr(diter), "/")
.Cells(iter, 1).Value = arr2(0)
.Cells(iter, 2).Value = arr2(1)
Next diter
Next key
End With
End Sub
The expected result is to reformat a table in this format
"A" "B" "C"
EU Sales 10:00
EU Tax 12:00
USA Sales 09:00
USA Tax 10:00
Into this format
EU:
Sales 10:00
Tax 12:00
USA:
Sales 09:00
Tax 10:00
Many thanks for any help. I've been struggeling with this problem for days...
Another possibility is to create a new class to store your data. Store your data in an instance of this class and then pass this object to your dictionary.
This way you could event extend the class to return other stuff, for example all values as a combined string etc... Using public properties you can even set up input validation and what not, but this is probably more than what is needed right now.
I kept the "Class" to the absolute minimum, normally public variables in classes are bad, but since we only use it as custom datatype this does not matter.
Edit: I updatet the class a bit to show some more functionality, but I leave the old one here as an example.
Standard Module "Module1":
Option Explicit
Sub fillDict()
Dim adict As Scripting.Dictionary
Set adict = New Dictionary
Dim info As myRegionData
Dim iter As Long
For iter = 0 To 10
Set info = New myRegionData
info.Region = "someRegion" & iter
info.data = "someData" & iter
info.Time = "someTime" & iter
adict.Add info.Region, info
Next iter
Dim returnInfo As myRegionData
Set returnInfo = adict.Item("someRegion1")
With returnInfo
Debug.Print .Region, .data, .Time 'someRegion1 someData1 someTime1
Debug.Print .fullSentence 'At someTime1 I was in someRegion1 and did someData1
End With
End Sub
Class Module (simple) "myRegionData":
Option Explicit
Public Region As String
Public data As String
Public Time As String
Class Module (extended) "myRegionData":
Option Explicit
Private Type TmyRegionData
'More about this structure:
'https://rubberduckvba.wordpress.com/2018/04/25/private-this-as-tsomething/
Region As String
data As String
Time As String
End Type
Private this As TmyRegionData
Public Property Get Region() As String
Region = this.Region
End Property
Public Property Let Region(value As String)
this.Region = value
End Property
Public Property Get data() As String
data = this.data
End Property
Public Property Let data(value As String)
this.data = value
End Property
Public Property Get Time() As String
Time = this.Time
End Property
Public Property Let Time(value As String)
this.Time = value
End Property
Public Function getFullSentence() As String
getFullSentence = "At " & Time & " I was in " & Region & " and did " & data
End Function
VBA has a dictionary structure. Dictionary is an object, and it can be referenced either with early binding (likeSet countrydict = CreateObject("Scripting.Dictionary")) or with a late binding, referring to Microsoft Scripting Runtime (In VBEditor>Extras>Libraries):
The latter has the advantage, that it is a bit faster and pressing Ctrl+space one would see the Intelli-Sense:
Concerning the question with multiple variables to a dictionary, then an array with those is a possibility:
Sub MyDictionary()
Dim myDict As New Scripting.Dictionary
If Not myDict.Exists("Slim") Then
Debug.Print "Adding Slim"
myDict.Add "Slim", Array("Eminem", "has", "a", "daughter!")
End If
If Not myDict.Exists("Barcelona") Then
Debug.Print "Adding Barcelona"
myDict.Add "Barcelona", Array("I", "have", "been there", 2018)
End If
If Not myDict.Exists("Barcelona") Then
myDict.Add "Barcelona", Array("I", "have", "been there", 2018)
Else
Debug.Print "Barcelona already exists!"
End If
'Keys
Dim key As Variant
For Each key In myDict.Keys
Debug.Print "--------------"
Debug.Print "Key -> "; key
Dim arrItem As Variant
For Each arrItem In myDict(key)
Debug.Print arrItem
Next
Next key
End Sub
This is the result of the code:
Adding Slim
Adding Barcelona
Barcelona already exists!
--------------
Key -> Slim
Eminem
has
a
daughter!
--------------
Key -> Barcelona
I
have
been there
2018
If the value of the dictionary is not an array, e.g. adding somewhere myDict.Add "notArray", 124, an error would pop up once it tries to print the array. This can be avoided with the usage of IsArray built-in function.
Related
So I have some json formatted data, in which an article name (the field in my case is "description courte") can be used multiple times and have a different price each time, I want to get those prices and display them in a listbox and let the user pick which one to parse in the column "price" which is found.offset(0,3). Note that I only search for fields that exist in the Range("G:G") This is what I did so far :
This code is returning an error : index does not belong in the selection (sorry if translated badly) at the
Set Found = Range("G:G").Find(ArtDict.Items()(Index).Name)
Code example
Sub prix()
Dim http As New WinHttpRequest
Dim resp As String
Dim url As String
url = "https://api.airtable.com/v0/appY6Wo3AmLHqHkjr/Materiaux?api_key=key_here" & Fields
http.Open "GET", url, False
http.Send
Dim JSON As Object
Dim Found As Range
Dim ArtDict As New Dictionary, Article As class_Article
Dim Index As Long, count As Long
Set JSON = JsonConverter.ParseJson(http.ResponseText)
For Index = 1 To JSON("records").count
Set Found = Range("G:G").Find(ArtDict.Items()(Index).Name)
If Not ArtDict.Exists(JSON("records")(Index)("fields")("description courte")) Then
'If this article doesn't exist in the article dictionary, then create the article object and add it to the dictionary
Set Article = New class_Article
Article.Name = JSON("records")(Index)("fields")("description courte")
Article.ParsePrice JSON("records")(Index)("fields")("prix unitaire HT")
Debug.Print Article.Name, Article.HighPrice, Article.LowPrice
ArtDict.Add Article.Name, Article
Else
Set Article = ArtDict(JSON("records")(Index)("fields")("description courte"))
Article.ParsePrice JSON("records")(Index)("fields")("prix unitaire HT")
Debug.Print Article.Name, Article.HighPrice, Article.LowPrice
Set ArtDict(JSON("records")(Index)("fields")("description courte")) = Article
End If
If Not Found Is Nothing Then
count = Found.Offset(0, 4).Value + 1
If count > 1 Then
UserForm1.Show
UserForm1.ListBox1.AddItem (Article.HighPrice)
UserForm1.ListBox1.AddItem (Article.LowPrice)
Found.Offset(0, 3) = UserForm1.ListBox1.Value
End If
End If
Next Index
End Sub
JSON SAMPLE
{
"records": [
{
"id": "rec0MS66BnYY0vK32",
"fields": {
"id": 124,
"article": "osmo 24m2 3062MAT 0.75L",
"categorie": [
"recvw95DBiWvk3zaH"
],
"udv": 1,
"unité": [
"recYQ9wpLDgNDk5BW"
],
"prix HT de l'udv": 29.09,
"date d'achat": "2019-08-01",
"distributeur": "cotet mtp",
"reference distributeur": "OSMO-ORI-0.75-M",
"id facture": "FA19036300",
"created on": "2020-02-07",
"by": "remyvignaux",
"description courte": "osmo 3062MAT",
"prix unitaire HT": 29.09
},
"createdTime": "2021-02-28T20:53:00.000Z"
},....etc
CLASS_ARTICLE
Option Explicit
Public Name As String
Public HighPrice As Currency
Public LowPrice As Currency
Private Sub Class_Initialize()
HighPrice = -922337203685477.5807# 'Set value to lowest possible value
LowPrice = 922337203685477.5807# 'Set value to the highest possible value
End Sub
Public Function ParsePrice(ByVal NewPrice As Currency) As Boolean
HighPrice = IIf(NewPrice > HighPrice, NewPrice, HighPrice)
LowPrice = IIf(NewPrice < LowPrice, NewPrice, LowPrice)
End Function
Parse the JSON into a collection of articles (using a dictionary) and then process each article in turn. The price selection can be an article method. I used an input box just to show the principle but you could use listbox. The results are shown on Sheet1.
Option Explicit
Sub prix()
' get json into a string
Dim fso, ts, s
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.OpenTextFile("c:\temp\json.txt")
s = ts.ReadAll
ts.Close
Dim dictArt As New Dictionary, oArt As Class_Article ' holds articles
Dim name As String, price As Currency
Dim JSON As Object, rec, fld
Set JSON = JsonConverter.ParseJson(s)
' parse json
For Each rec In JSON("records")
Set fld = rec("fields")
name = fld("description courte")
price = fld("prix unitaire HT")
If dictArt.Exists(name) Then
dictArt(name).AddPrice price
Else
Set oArt = New Class_Article
oArt.name = name
oArt.AddPrice price
dictArt.Add name, oArt
End If
Next
' result to sheet1
Dim key, i As Long: i = 1
Sheet1.Cells.Clear
Sheet1.Range("A1:C1") = Array("Description", "Price Count", "Price")
For Each key In dictArt
i = i + 1
Set oArt = dictArt(key)
Sheet1.Cells(i, 1) = oArt.Name
Sheet1.Cells(i, 2) = oArt.PriceCount
' if more than once give options
If oArt.PriceCount > 1 Then
Sheet1.Cells(i, 1).Select
Sheet1.Cells(i, 3).Interior.Color = vbYellow
oArt.SelectPrice
If oArt.bSelected Then
Sheet1.Cells(i, 3) = oArt.price
Sheet1.Cells(i, 3).Interior.Color = xlNone
End If
Else
Sheet1.Cells(i, 3) = oArt.price
End If
Next
End Sub
' Class_Article
Public name As String
Public price As Currency
Public PriceCount As Integer
Public bSelected As Boolean
Private prices As New Collection
Private i As Integer
Public Sub AddPrice(ByVal price As Currency)
PriceCount = PriceCount + 1
Me.price = price
prices.Add price, CStr(PriceCount)
End Sub
Sub SelectPrice()
Dim msg As String
bSelected = False
' build option list
msg = name & " has " & PriceCount & " prices"
For i = 1 To PriceCount
msg = msg & vbCr & "(" & i & ") " & prices(i)
Next
' user selects
begin:
i = Application.InputBox(msg, "Select Price 1 to " & PriceCount, 1, Type:=1) ' int
If i < 1 Then
Exit Sub
ElseIf i > PriceCount Then
GoTo begin
End If
' selected price
price = prices(i)
bSelected = True
End Sub
The single most annoying feature in Excel's built-in VBA editor is—in my opinion—the aggressive autoformatting of the code, which insists on rewriting what I have typed as soon as the cursor leaves the line. It is particularly distressing that the editor collapses all whitespace, thus preventing any meaningful code alignment. For example, if I try to align a sequence of assignments by the equals sign with values aligned by the decimal separator:
price = 10.01
quantity = 3.2
vat = 0.11
the editor inevitably scrambles it by collapsing all spaces:
price = 10.01
quantity = 3.2
vat = 0.11
Is there any way to avoid this kind unwelcome autoformatting?
Assignment cosmetics :-)
There's neither a special VBE property to change the VBE (autoformatting) options directly nor a way to do it programatically. - So afaik VBE irrevocably forces autoformatting upon the user by partial workarounds.
a) Class method
For the sake of the art and just for fun an actually (very) basic class approach to give you a starting idea; assignment arguments are passed as strings allowing any optical formatting - if that's what you really want:
Example call in current module
Sub ExampleCall()
Dim x As New cVars
x.Add "price = 11.11" ' wrong assignment
'...
x.Add "price = 10.01" ' later correction
x.Add "quantity = 1241.01"
x.Add "vat = 0.11"
Debug.Print "The price is $ " & x.Value("price")
End Sub
Class module cVars
Option Explicit
Private dict As Object
Sub Add(ByVal NewValue As Variant)
'split string tokens via equal sign
Dim tmp
tmp = Split(Replace(Replace(NewValue, vbTab, ""), " ", "") & "=", "=")
'Identify key and value item
Dim myKey As String, myVal
myKey = tmp(0)
myVal = tmp(1): If IsNumeric(myVal) Then myVal = Val(myVal)
'Add to dictionary
If dict.exists(myKey) Then
dict(myKey) = myVal
Else
dict.Add myKey, myVal
End If
'Debug.Print "dict(" & myKey & ") =" & dict(myKey)
End Sub
Public Property Get Value(ByVal myVarName As String) As Variant
'get variable value
Value = dict(myVarName)
End Property
Private Sub Class_Initialize()
'set (late bound) dict to memory
If dict Is Nothing Then Set dict = CreateObject("Scripting.Dictionary")
End Sub
Private Sub Class_Terminate()
Set dict = Nothing
End Sub
Edit #1 as of 3/3 2021
b) Rem Evaluation method
Once again only for the sake of the art a way to read assignments entered into outcommented code lines via, yes via Rem (heaving a deep sigh for this archaic use originating from former Basic times) as it allows to format data with any wanted spaces or tabs and won't be mixed up hopefully with current outcommentings via apostrophe '.
This Test procedure only needs the usual declarations plus some assignment calls as well as the mentioned Rem part. Two simple help procedures get code lines, analyze them via a dictionary class cVars and eventually assign them.
Note that the following example
needs a library reference to Microsoft Visual Basic Extensibility 5.3 and
uses the unchanged class cVars of section a) simply to avoid rewriting it.
Option Explicit
Private Const THISMODULE As String = "Module1" ' << change to current code module name
Sub Test() ' procedure name of example call
'Declare vars
Dim price As Double: Assign "price", price
Dim quantity As Double: Assign "quantity", quantity
Dim vat As Double: Assign "vat", vat
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Enter assignments via Rem(ark)
'(allowing any user defined formatting therein)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Rem price = 10.01
Rem quantity = 1241.01
Rem vat = 0.11
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Debug.Print quantity & " à $" & price & " = " & Format(quantity * price, "$#,##0.00")
End Sub
Help procedure Assign evaluating Rem codelines in procedure Test
Sub Assign(ByVal myVarName As String, ByRef myvar)
Const MyProc As String = "Test"
Dim codelines
getCodelines codelines, THISMODULE, ProcedureName:=MyProc
'Debug.Print Join(codelines, vbNewLine)
Dim x As New cVars ' set class instance to memory
Dim line As Variant, curAssignment
For Each line In codelines
curAssignment = Split(line, "Rem ")(1) ' remove Rem prefix from codelines
If curAssignment Like myVarName & "*" Then
x.Add curAssignment
myvar = x.Value(myVarName)
End If
Next
End Sub
Help procedure getCodelines
Called by above proc Assign. Returns the relevant Rem Codelines from the calling procedure Test. - Of course it would have been possible to filter only one codeline.
Sub getCodelines(ByRef arr, ByVal ModuleName As String, ByVal ProcedureName As String)
Const SEARCH As String = "Rem "
'a) set project
Dim VBProj As Object
Set VBProj = ThisWorkbook.VBProject
If VBProj.Protection = vbext_pp_locked Then Exit Sub ' escape locked projects
'b) set component
Dim VBComp As Object
Set VBComp = VBProj.VBComponents(ModuleName)
Dim pk As vbext_ProcKind
'd) get relevant code lines
With VBComp.CodeModule
'count procedure header lines
Dim HeaderCount As Long: HeaderCount = .ProcBodyLine(ProcedureName, pk) - .ProcStartLine(ProcedureName, pk)
'get procedure code
Dim codelines
codelines = Split(.lines(.ProcBodyLine(ProcedureName, pk), .ProcCountLines(ProcedureName, pk) - HeaderCount), vbNewLine)
'filter code lines containing "Rem" entries
codelines = Filter(codelines, SEARCH, True)
End With
'return elements
arr = codelines
End Sub
Don't forget to integrate the class module CVars from section a)!
I'm trying to implement my own collection in VBA in which to store a list of objects. I'm new to VBA and I need to find a way in which to implement a solution.
What I have: a list of objects with different properties on which the most important is the duration of a specific action.
What I need: I want a sorted list of objects by using a comparator based on a time property (duration of a specific action).
What kind of Collection do you recommend? I need to create my own helper method in order to sort the elements of the collection? VBA has a comparator interface that I can implement on my own collection object?
I have solved quite similar problem using Types.
Option Compare Database
Option Explicit
Type tPersonalData
FamilyName As String
Name As String
BirthDate As Date
End Type
Type tHuman
PersonalData As tPersonalData
Height As Integer
CashAmount As Integer
End Type
Public Sub subUsage()
Dim Humans(10) As tHuman
Dim HumanCurrent As tHuman
With HumanCurrent
.CashAmount = 100
.Height = 190
.PersonalData.FamilyName = "Smith"
.PersonalData.Name = "John"
.PersonalData.BirthDate = CDate("01.01.1980")
End With
Humans(1) = HumanCurrent
End Sub
So after that you can implement your own sorting functions for an array, using given field.
I have created a sorting method based on Van Ng's, in order to sort my objects like that:
Public Sub sortedCollectionByDuration()
Dim vItm As ClsCron
Dim i As Long, j As Long
Dim vTemp As Variant
Set vTemp = New ClsCron
Set vItm = New ClsCron
For i = 1 To myCol.Count - 1
For j = i + 1 To myCol.Count
If myCol(i).Duration > myCol(j).Duration Then
Set vTemp = myCol(j)
myCol.Remove j
myCol.add vTemp, , i
End If
Next j
Next i
-- testing or logging
For Each vItm In myCol
Debug.Print vItm.ModuleName & " " & vItm.FunctionName & VBA.Format(vItm.Duration, "HH:MM:SS")
Next vItm
End Sub
I have this following subroutine In Access VBA:
Sub SampleReadCurve()
Dim rs As Recordset
Dim iRow As Long, iField As Long
Dim strSQL As String
Dim CurveID As Long
Dim MarkRunID As Long
Dim MaxOfMarkAsofDate As Date
Dim userdate As String
CurveID = 15
Dim I As Integer
Dim x As Date
userdate = InputBox("Please Enter the Date (mm/dd/yyyy)")
x = userdate
For I = 0 To 150
MaxOfMarkAsofDate = x - I
strSQL = "SELECT * FROM VolatilityOutput WHERE CurveID=" & CurveID & " AND MaxOfMarkAsofDate=#" & MaxOfMarkAsofDate & "# ORDER BY MaxOfMarkasOfDate, MaturityDate"
Set rs = CurrentDb.OpenRecordset(strSQL, Type:=dbOpenDynaset, Options:=dbSeeChanges)
If rs.RecordCount <> 0 Then
rs.MoveFirst
rs.MoveLast
Dim BucketTermAmt As Long
Dim BucketTermUnit As String
Dim BucketDate As Date
Dim MarkAsOfDate As Date
Dim InterpRate As Double
BucketTermAmt = 3
BucketTermUnit = "m"
BucketDate = DateAdd(BucketTermUnit, BucketTermAmt, MaxOfMarkAsofDate)
InterpRate = CurveInterpolateRecordset(rs, BucketDate)
Debug.Print BucketDate, InterpRate
End If
Next I
End Function
This subroutine put outs a 2x2 list of 76 numbers, consisting of a date and an associated rate. I want to store this list of values as a Collection, so that I can use it as an input in another function. Is it possible to use collections to do this? What would be the appropriate syntax?
I agree with comments that a data dictionary is probably the way to go. The reason is that with a dictionary you can actually loop through the keys if needed. You will need to add a reference to the Microsoft Scripting Runtime. Here is a brief example:
Public Function a()
Dim dRates As New Scripting.Dictionary
Dim key As Variant
dRates.Add #1/1/2016#, 1
dRates.Add #2/1/2016#, 1.5
dRates.Add #3/1/2016#, 2
'You can either access the rate directly with the key:
Debug.Print dRates(#2/1/2016#)
'Or you can loop through the keys/values
For Each key in dRates.Keys
Debug.Print key & " - " & dRates(key)
Next
'Or, you can pass the entire collection to a function
Call b(dRates)
End Function
Public Function b(d As Scripting.Dictionary)
For Each key In d.Keys
Debug.Print key & " - " & d(key)
Next
End Function
This will provide the following output:
1.5
1/1/2016 - 1
2/1/2016 - 1.5
3/1/2016 - 2
1/1/2016 - 1
2/1/2016 - 1.5
3/1/2016 - 2
I'm trying to create a dictionary which has a collection for every key. The reason for this is that I want to retrieve several values from the same key later on. In this example I want to have the total value (val) of a unique key as well as the number of occurrences (n):
sub update()
Dim dict As Dictionary
Dim coll As Collection
Set dict = New Dictionary
Set coll = New Collection
coll.Add 100, "val"
coll.Add 3, "n"
dict.Add "coll", coll
Debug.Print dict.item("coll")("val")
Debug.Print dict.item("coll")("n")
This works fine so far, the problem occurs when I try to update the value in the collection (object doesn't support this):
dict.item("coll")("val") = dict.item("coll")("val") + 100
What I tried:
If I use an array instead of the collection, there is no error but the value doesn't change.
It only works if I read out the collection to variables, change the value, create a new collection, remove the old from the dictionary and add the new collection.
Is there any way to do it like my approach above in a single line?
I would also be happy for an alternative solution to the task.
Once you added an item to the collection, you cannot change it that easily. Such expression:
coll("n") = 5
will cause Run-time error '424': Object required.
You can check it by yourself on the simple example below:
Sub testCol()
Dim col As New VBA.Collection
Call col.Add(1, "a")
col("a") = 2 '<-- this line will cause Run-time error '424'
End Sub
The only way to change the value assigned to the specified key in the given collection is by removing this value and adding another value with the same key.
Below is the simple example how to change the value assigned to a collection with key [a] from 1 to 2:
Sub testCol()
Dim col As New VBA.Collection
With col
Call .Add(1, "a")
Call .Remove("a")
Call .Add(2, "a")
End With
End Sub
Below is your code modified in order to allow you to change the value assigned to the given key in the collection:
Sub update()
Dim dict As Dictionary
Dim coll As Collection
Set dict = New Dictionary
Set coll = New Collection
coll.Add 100, "val"
coll.Add 3, "n"
dict.Add "coll", coll
Debug.Print dict.Item("coll")("val")
Debug.Print dict.Item("coll")("n")
'This works fine so far, the problem occurs when I try to update the value in the collection (object doesn't support this):
Dim newValue As Variant
With dict.Item("coll")
newValue = .Item("val") + 100
On Error Resume Next '<---- [On Error Resume Next] to avoid error if there is no such key in this collection yet.
Call .Remove("val")
On Error GoTo 0
Call .Add(newValue, "val")
End With
End Sub
It is not elegant perhaps, but maybe you can write a sub to update a collection by a key:
Sub UpdateCol(ByRef C As Collection, k As Variant, v As Variant)
On Error Resume Next
C.Remove k
On Error GoTo 0
C.Add v, k
End Sub
Used like this:
Sub Update()
Dim dict As Dictionary
Dim coll As Collection
Set dict = New Dictionary
Set coll = New Collection
coll.Add 100, "val"
coll.Add 3, "n"
dict.Add "coll", coll
Debug.Print dict.Item("coll")("val")
Debug.Print dict.Item("coll")("n")
UpdateCol dict.Item("coll"), "val", dict.Item("coll")("val") + 100
Debug.Print dict.Item("coll")("val")
End Sub
With output as expected:
100
3
200
Here is an approach using a User Defined Object (Class). Hoepfully you can adapt this to your problem.
Rename the Class Module cMyStuff or something else meaningful.
Class Module
Option Explicit
Private pTotalVal As Long
Private pCounter As Long
Public Property Get TotalVal() As Long
TotalVal = pTotalVal
End Property
Public Property Let TotalVal(Value As Long)
pTotalVal = Value
End Property
Public Property Get Counter() As Long
Counter = pCounter
End Property
Public Property Let Counter(Value As Long)
pCounter = Value
End Property
Regular Module
Option Explicit
Sub Update()
Dim cMS As cMyStuff, dMS As Dictionary
Dim I As Long
Set dMS = New Dictionary
For I = 1 To 3
Set cMS = New cMyStuff
With cMS
.Counter = 1
.TotalVal = I * 10
If Not dMS.Exists("coll") Then
dMS.Add "coll", cMS
Else
With dMS("coll")
.TotalVal = .TotalVal + cMS.TotalVal
.Counter = .Counter + 1
End With
End If
End With
Next I
With dMS("coll")
Debug.Print "Total Value", .TotalVal
Debug.Print "Counter", .Counter
End With
End Sub
Results in Immediate Window
Total Value 60
Counter 3