VBA: Loop over Items in Dictionary with an Object Variable - excel

I am trying to loop over the items in a dictionary with an object variable that refer to inheritance class "Breed", but I am unable to do so with dictionaries but with collections it is pretty simple is there a way to solve this without using the dictionary's keys? because then I will lose the ability to use the intelisense feature.
Here is the code of the class Breed:
Option Explicit
Public Property Get Name() As String
End Property
Public Property Get Color() As String
End Property
Public Property Get Price() As Double
End Property
Here is the code for class Dogs:
Option Explicit
Implements Breed
Private pName As String, pPrice As Double, pColor As String
Public Property Let Name(Val As String)
pName = Val
End Property
Public Property Get Name() As String
Name = pName
End Property
Private Property Get Breed_Name() As String
Breed_Name = Name
End Property
'''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Property Let Price(Val As Double)
pPrice = Val
End Property
Public Property Get Price() As Double
Price = pPrice
End Property
Private Property Get Breed_Price() As Double
Breed_Price = Price
End Property
'''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Property Let Color(Val As String)
pColor = Val
End Property
Public Property Get Color() As String
Color = pColor
End Property
Private Property Get Breed_Color() As String
Breed_Color = Color
End Property
Here is the code for class Cats:
Option Explicit
Implements Breed
Private pName As String, pPrice As Double, pColor As String
Public Property Let Name(Val As String)
pName = Val
End Property
Public Property Get Name() As String
Name = pName
End Property
Private Property Get Breed_Name() As String
Breed_Name = Name
End Property
'''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Property Let Price(Val As Double)
pPrice = Val
End Property
Public Property Get Price() As Double
Price = pPrice
End Property
Private Property Get Breed_Price() As Double
Breed_Price = Price
End Property
'''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Property Let Color(Val As String)
pColor = Val
End Property
Public Property Get Color() As String
Color = pColor
End Property
Private Property Get Breed_Color() As String
Breed_Color = Color
End Property
Here is the code for the regular module with collection but fails with dictionary:
Option Explicit
Sub Main()
Dim C As Cats
Dim D As Dogs
Dim Coll As Collection
Dim B As Breed
Set C = New Cats
C.Name = "Catomon"
C.Color = "Angle White"
C.Price = 800.98
Set D = New Dogs
D.Name = "Dogomon"
D.Color = "Golden White"
D.Price = 1000.23
Set Coll = New Collection
Coll.Add C
Coll.Add D
Set B = New Breed
For Each B In Coll
Debug.Print B.Name, B.Color, B.Price
Next B
Set C = Nothing
Set D = Nothing
Set B = Nothing
Set Coll = Nothing
End Sub

Dictionary methods .Keys() and .Items() return arrays. Only way to iterate over arrays is with an variable of type Variant. With these restrictions, the only way I can think of is casting Variant variable to the type Breed inside the loop. This way, after the casting, you get Intellisense.
Based on the code you posted, an example would be:
Sub MainWithDictionary()
Dim C As Cats
Dim D As Dogs
Dim Dict As Scripting.Dictionary
Dim B As Breed
Dim K As Variant 'new variable
Set C = New Cats
C.Name = "Catomon"
C.Color = "Angle White"
C.Price = 800.98
Set D = New Dogs
D.Name = "Dogomon"
D.Color = "Golden White"
D.Price = 1000.23
Set Dict = New Scripting.Dictionary
'Keys are just placeholders
Dict.Add 1, C
Dict.Add 2, D
For Each K In Dict.Items()
'Cast the Variant result to Breed
Set B = K
'You will have Intellisense on each dictionary items after this
Debug.Print B.Name, B.Color, B.Price
Next K
Set C = Nothing
Set D = Nothing
Set B = Nothing
Set Dict = Nothing
End Sub

Related

Excel VBA declaring an arraylist inside of a class

I'm trying to create a Staff class module, with strings for surname etc, and an arraylist to be used for storing / calling between 0-10 strings dependant on how many are added when used.
The class module is called StaffClass and contains:
Private m_surname As String
Private m_districts as ArrayList
' Surname Prop
Property Get surname() As String
surname = m_surname
End Property
Property Let surname(surname As String)
m_surname = Name
End Property
' District Prop
' This is where i'm getting confused
Private Sub Class_ArrList()
Set m_districts = New ArrayList
End Sub
Property Get districts() As ArrayList
districts = m_districts
End Property
Property Let districts(districts as ArrayList)
m_districts = districts
End Property
The Main Module contains:
Dim newStaff As StaffClass
Set newStaff = New StaffClass
newStaff.surname = "Smith"
' This is where I want to add to the arraylist
newStaff.districts(0) = "50"
I'm aware I'm missing loads, but struggling to find much relating to collections inside classes for VBA.
Hoping you can help!
You can put the arraylist initialization routine in a Class_Initialize, and add a methods to the class to add/insert/etc each item. (Or you could add a method to add the arraylist as a single object).
Also, since ArrayList is an object, you'll need to use the Set keyword when retrieving it.
eg:
Class module
Option Explicit
Private m_surname As String
Private m_districts As ArrayList
' Surname Prop
Property Get surname() As String
surname = m_surname
End Property
Property Let surname(surname As String)
m_surname = surname
End Property
Property Get districts() As ArrayList
Set districts = m_districts
End Property
Function addDistrict(Value As String)
m_districts.Add Value
End Function
Private Sub Class_Initialize()
Set m_districts = New ArrayList
End Sub
Regular Module
Option Explicit
Sub par()
Dim newStaff As StaffClass
Dim V As ArrayList
Set newStaff = New StaffClass
With newStaff
.surname = "Smith"
.addDistrict 50
.addDistrict "xyz"
End With
Set V = newStaff.districts
Stop
End Sub

Seting property of class

I have two classes
class Table which countains 2 informations (name and value)
Option Explicit
Private dName As String
Private dValue As Double
Property Get Name() As String
Name = dName
End Property
Property Let Name(Jmeno As String)
dName = Jmeno
End Property
Property Get Value() As Double
Value = dValue
End Property
Property Let Value(Hodnota As Double)
dValue = Hodnota
End Property
and main class where I use objects(in array) of class Table.
Option Explicit
Private dklicovaTeplota() As cls_Table
Property Get KlicovaTeplota(pozice As Integer) As cls_Table
Set KlicovaTeplota = dklicovaTeplota(pozice)
End Property
Property Set KlicovaTeplota(pozice As Integer, Jmeno As String, Hodnota As Double)
Dim temp As New cls_Table
temp.Name = Jmeno
temp.Value = Hodnota
Set dklicovaTeplota(pozice) = temp
End Property
When I run code I get error:
And when I come back to VBA editor the
Property Set KlicovaTeplota(pozice As Integer, Jmeno As String, Hodnota As Double)
is marked.
I have no idea where my property setting is inconsistent..

Error 91 in class function

Using Access 2010, I'm gathering information and dropping it on an Excel spreadsheet. When I run the code below, I'm getting
Run-time error '91':Object variable or With block not set
in my class on this line Set Cci = ChartColorItems(ColorID) in Public Function GetRGB(ByRef ColorID As String) As Integer
The 'ChartColors' class:
Option Compare Database
Option Explicit
Private pChartColorItems As Collection
Public Property Get ChartColorItems() As Collection
Set ChartColorItems = pChartColorItems
End Property
Public Property Set ChartColorItems(ByRef lChartColorItem As Collection)
Set pChartColorItems = lChartColorItem
End Property
Public Function GetRGB(ByRef ColorID As String) As Integer
Dim Cci As ChartColorItem
Dim x As Integer
'---------------------------------------------------
'Error happens here:
Set Cci = ChartColorItems(ColorID)
'---------------------------------------------------
x = RGB(Cci.Red, Cci.Green, Cci.Blue)
GetRGB = x
Set Cci = Nothing
End Function
Private Sub Class_Initialize()
Dim Cci As ChartColorItem
Dim Colors As Collection
Set Colors = New Collection
Set Cci = New ChartColorItem
Cci.Red = 149
Cci.Green = 55
Cci.Blue = 53
Cci.ColorID = "Pie1"
Colors.Add Cci, Key:=Cci.ColorID
Set Cci = Nothing
Set Cci = New ChartColorItem
Cci.Red = 148
Cci.Green = 138
Cci.Blue = 84
Cci.ColorID = "Pie2"
Colors.Add Cci, Key:=Cci.ColorID
Set Cci = Nothing
End Sub
and the ChartColorItem class:
Option Compare Database
Option Explicit
Private pColorID As String
Private pRed As Integer
Private pGreen As Integer
Private pBlue As Integer
Public Property Get ColorID() As String
ColorID = pColorID
End Property
Public Property Let ColorID(ByRef x As String)
pColorID = x
End Property
Public Property Get Red() As Integer
Red = pRed
End Property
Public Property Let Red(ByRef x As Integer)
pRed = x
End Property
Public Property Get Green() As Integer
Green = pGreen
End Property
Public Property Let Green(ByRef x As Integer)
pGreen = x
End Property
Public Property Get Blue() As Integer
Blue = pBlue
End Property
Public Property Let Blue(ByRef x As Integer)
pBlue = x
End Property
When I debug, the code steps through the ChartColorItems() getter just fine, the error happens after the End Function, and drops me on the line noted above.
This is very similar to some code I wrote earlier this week, the main difference is that I'm populating my ChartColors by using the Class_Initialize sub, since I'm trying to store off a fixed set of colors, whereas my earlier code was gathering data and inserting it into the class in a more 'normal' way.
You defined the private collection at the module level as pCharColorItems, but you never initialize it in the class' intialize method. Instead, you use a locally scoped Colors collection variable.
Private Sub Class_Initialize()
Dim Cci As ChartColorItem
Dim Colors As Collection
Set Colors = New Collection
You need to use pChartColorItems instead.
Private Sub Class_Initialize()
Dim Cci As ChartColorItem
Dim Colors As Collection
Set pChartColorItems = New Collection
Set Cci = New ChartColorItem
Cci.Red = 149
Cci.Green = 55
Cci.Blue = 53
Cci.ColorID = "Pie1"
pChartColorItems.Add Cci, Key:=Cci.ColorID
Set Cci = Nothing
Set Cci = New ChartColorItem
Cci.Red = 148
Cci.Green = 138
Cci.Blue = 84
Cci.ColorID = "Pie2"
pChartColorItems.Add Cci, Key:=Cci.ColorID
Set Cci = Nothing
End Sub
But there's another bug on this line of GetRGB.
x = RGB(Cci.Red, Cci.Green, Cci.Blue)
You declared x as an Integer, when the RGB function returns a long. The value of "Pie1" causes an overflow error.

Excel VBA function take 2 different data type and loop through collection of class module

I have following sub working but get #VALUE! error after change sub into function.
Working sub as follow
Sub Get_Tier2(ByVal myCategory As String, ByVal myMSRP As Double)
Dim ProductTiers As Collection
Dim pt As ProductTier
Dim result As String
Dim CIDTierCount As Integer
CIDTierCount = ThisWorkbook.Worksheets("CIDTier").Range("A:A").CurrentRegion.Rows.Count
Set ProductTiers = New Collection
For i = 2 To CIDTierCount
Set pt = New ProductTier
pt.Category = ThisWorkbook.Worksheets("CIDTier").Rows(i).Columns("B")
pt.Tier = ThisWorkbook.Worksheets("CIDTier").Rows(i).Columns("C")
pt.MSRP_Low = ThisWorkbook.Worksheets("CIDTier").Rows(i).Columns("D")
pt.MSRP_High = ThisWorkbook.Worksheets("CIDTier").Rows(i).Columns("E")
ProductTiers.Add pt
Next
For Each pt In ProductTiers
If StrComp(pt.Category, myCategory) = 0 And pt.MSRP_Low < myMSRP And myMSRP < pt.MSRP_High Then
result = pt.Tier()
MsgBox result
Exit Sub
Else
'do nothing
End If
Next
End Sub
When I change the sub to function and try to use in excel, it will return me #VALUE! in excel
Function Get_Tier(ByVal myCategory As String, ByVal myMSRP As Double) As String
Dim ProductTiers As Collection
Dim pt As ProductTier
Dim result As String
Dim CIDTierCount As Integer
CIDTierCount = ThisWorkbook.Worksheets("CIDTier").Range("A:A").CurrentRegion.Rows.Count
Set ProductTiers = New Collection
For i = 2 To CIDTierCount
Set pt = New ProductTier
pt.Category = ThisWorkbook.Worksheets("CIDTier").Rows(i).Columns("B")
pt.Tier = ThisWorkbook.Worksheets("CIDTier").Rows(i).Columns("C")
pt.MSRP_Low = ThisWorkbook.Worksheets("CIDTier").Rows(i).Columns("D")
pt.MSRP_High = ThisWorkbook.Worksheets("CIDTier").Rows(i).Columns("E")
ProductTiers.Add pt
Next
For Each pt In ProductTiers
If StrComp(pt.Category, myCategory) = 0 And pt.MSRP_Low < myMSRP And myMSRP < pt.MSRP_High Then
Get_Tier = pt.Tier()
Exit Function
Else
Get_Tier = "no match"
End If
Next
End Function
Class Module ProductTier are as follow
Private pCategory As String
Private pTier As String
Private pMSRP_Low As Double
Private pMSRP_High As Double
Public Property Get Category() As String
Category = pCategory
End Property
Public Property Let Category(Value As String)
pCategory = Value
End Property
Public Property Get Tier() As String
Tier = pTier
End Property
Public Property Let Tier(Value As String)
pTier = Value
End Property
Public Property Get MSRP_Low() As String
MSRP_Low = pMSRP_Low
End Property
Public Property Let MSRP_Low(Value As String)
pMSRP_Low = Value
End Property
Public Property Get MSRP_High() As String
MSRP_High = pMSRP_High
End Property
Public Property Let MSRP_High(Value As String)
pMSRP_High = Value
End Property
And the data on CIDTier tab
Column B: ProductA
Column C: Tier 1
Column D: 0
Column E: 300
The Culprit
CIDTierCount = ThisWorkbook.Worksheets("CIDTier").Range("A:A").CurrentRegion.Rows.Count
If you throw a debug marker on that line and print what
ThisWorkbook.Worksheets("CIDTier").Range("A:A").CurrentRegion.Rows.Count
returns, you get 1048576. This will overflow the Integer you are trying to place it in.
Possible Solution
From what you are doing, it looks like you want a count of rows, you can try changing the code to the following:
CIDTierCount = Worksheets("CIDTier").UsedRange.Rows.Count
Why this works
In Excel, each worksheet has a range that is defined as the UsedRange. This range goes from A1 to the farthest right column that is formatted or contains data and the lowest row that is formatted or contains data.

VBA Class() object as property of another class

I'm trying to create a class to hold a variable number of items (which are themselves another class object).
So, I have Class 2:
' Class 2 contain each individual quote elements (OTC and MRC)
Private pOTC As String
Private pMRC As String
Public Property Get OTC() As String
OTC = pOTC
End Property
Public Property Let OTC(Value As String)
pOTC = Value
End Property
Public Property Get MRC() As String
MRC = pMRC
End Property
Public Property Let MRC(Value As String)
pMRC = Value
End Property
Then Class 1 contains an array of Class 2:
Private pCurr As String
Private pQuote(20) As Class2
Public Property Get Curr() As String
Curr = pCurr
End Property
Public Property Let Curr(Value As String)
pCurr = Value
End Property
Public Property Set Quote(Index As Integer, cQuote As Class2)
Set pQuote(Index) = cQuote
End Property
Public Property Get Quote(Index As Integer) As Class2
Quote = pQuote(Index)
End Property
And what I would like to do is something like:
Dim myQuotes As Class1
Set myQuotes = New Class1
myQuotes.Curr = "GBP"
myQuotes.Quote(3).OTC = "1200"
The first line setting myQuotes.Curr is no problem, however when I try to set a value inside the array the next line errors with Run-time 91 Object variable or With block variable not set
Any pointers as to what I'm doing wrong and how can I set the values for the elements within the class array?
Thanks in advance!
When you myQuotes.Quote(3) you call Property Get Quote which has an issue.
Your internal array of Class2 is not instantiated so pQuote(Index) refers to an array element of Nothing, when you then myQuotes.Quote(3).OTC = you try to assign to Nothing which fails.
You need to make sure pQuote(Index) is instanced; you can do this on demand:
Public Property Get Quote(Index As Integer) As Class2
If (pQuote(Index) Is Nothing) Then Set pQuote(Index) = New Class2
Set Quote = pQuote(Index)
End Property
(Note the required Set)
Or by adding an intitialisation routine to Class1:
Private Sub Class_Initialize()
Dim Index As Long
For Index = 0 To UBound(pQuote)
Set pQuote(Index) = New Class2
Next
End Sub
You need to set them as New Class2 in Class1:
For intI = LBOUND(pQuote) to UBOUND(pQuote)
Set pQuote(intI) = New Class2
Next IntI
Just as you do with Class1 in your final script.
Maybe it should be
Public Property Let Quote(Index As Integer, cQuote As Class2)
Set pQuote(Index) = cQuote
End Property

Resources