7

I was given an answer on how to make a general class module: Class "let" stuck in infinite loop

I'm trying to apply this to dictionaries inside my classes.

My class module:

Option Explicit

Private Type categories
    Temp As scripting.Dictionary
    Humid As scripting.Dictionary
    Wind As scripting.Dictionary
End Type

Private this As categories

Public Sub Initialize()
    Set this.Temp = New scripting.Dictionary
    Set this.Humid = New scripting.Dictionary
    Set this.Wind = New scripting.Dictionary
End Sub

Public Property Get Temp(ByVal HourIndex As Long) As Double
    Temp = this.Temp(HourIndex)
End Property

Public Property Let Temp(ByVal HourIndex As Long, ByVal Value As Double)
    this.Temp(HourIndex) = Value
End Property

Public Property Get Humid(ByVal HourIndex As Long) As Double
    Humid = this.Humid(HourIndex)
End Property

Public Property Let Humid(ByVal HourIndex As Long, ByVal Value As Double)
    this.Humid(HourIndex) = Value
End Property

Public Property Get Wind(ByVal HourIndex As Long) As Double
    Wind = this.Wind(HourIndex)
End Property

Public Property Let Wind(ByVal HourIndex As Long, ByVal Value As Double)
    this.Wind(HourIndex) = Value
End Property

I tried to test this in the immediate window with set tester = new WeatherData (the name of the module) and Initialize. That did not work.

I then modified Initialize:

Public Sub Initialize(ByVal variable As categories)
    Set variable.Temp = New scripting.Dictionary
    Set variable.Humid = New scripting.Dictionary
    Set variable.Wind = New scripting.Dictionary
End Sub

and entered Initialize tester, but this did not work either ("Compile Error: Sub or Function not defined").

How do I put three dictionaries in a class module?

The following doesn't solve the problem, but it did skirt around it to the point that I don't have to acknowledge it:

Option Explicit

Private Type categories
    Temp(23) As Double
    Humid(23) As Double
    wind(23) As Double
End Type

Private this As categories

Public Property Get Temp(ByVal HourIndex As Long) As Double
    Temp = this.Temp(HourIndex)
End Property

Public Property Let Temp(ByVal HourIndex As Long, ByVal Value As Double)
    this.Temp(HourIndex) = Value
End Property

Public Property Get Humid(ByVal HourIndex As Long) As Double
    Humid = this.Humid(HourIndex)
End Property

Public Property Let Humid(ByVal HourIndex As Long, ByVal Value As Double)
    this.Humid(HourIndex) = Value
End Property

Public Property Get wind(ByVal HourIndex As Long) As Double
    wind = this.WindChill(HourIndex)
End Property

Public Property Let wind(ByVal HourIndex As Long, ByVal Value As Double)
    this.wind(HourIndex) = Value
End Property

tl;dr: make arrays instead of dictionaries, and cut out initialize entirely. Your "keys" have no choice but to be numbers, but it works. I would be interested in knowing an actual solution, but the specific issue is solved.

Community
  • 1
  • 1
kumquatwhat
  • 315
  • 1
  • 4
  • 12
  • 1
    "it does not work" isn't exactly a clear problem statement, but `Temp`, `Humid` and `Wind` are all exposed as `Double`'s - you can't assign them to a `Dictionary` reference... – Mathieu Guindon Apr 06 '17 at 18:01
  • I did that because it was going to be a dictionary of doubles, though I had no idea if it would get me anywhere or not. Would this work as an array, then? And I edited to add that the returned error is a compile error about `Initialize`. – kumquatwhat Apr 06 '17 at 18:06
  • The `Initialize` method is a member of the `WeatherData`? – Mathieu Guindon Apr 06 '17 at 18:08
  • Initialize is meant as a function to add 24 entries to each dictionary(/array/whatever ends up working) in `WeatherData`. From there, with the entries actually existing, they could be changed to whatever the data read. – kumquatwhat Apr 06 '17 at 18:11

3 Answers3

11

Seems you want to implement an indexed property.

Simplified to a bare minimum:

Option Explicit
Private values As Scripting.Dictionary

Private Sub Class_Initialize()
    Set values = New Scripting.Dictionary
End Sub

Public Property Get Something(ByVal key As String) As Double
    Something = values(key)
End Property

Public Property Let Something(ByVal key As String, ByVal value As Double)
    values(key) = value
End Property

You keep the dictionaries safely encapsulated as an implementation detail of your class (external code cannot set them to Nothing, for example), and expose an indexed Get+Let property for each encapsulated dictionary, that takes the index (/key) as a parameter.

In the case of your WeatherData class, this means you can populate the data like this:

Set data = New WeatherData
With data
    .Temp("day 1") = 76
    .Temp("day 2") = 78
    .Humid("day 1") = 0.55
    .Humid("day 2") = 0.61
    .Wind("day 1") = 0.92
    .Wind("day 2") = 1.27
End With

And then retrieve the temperature of "day 1" with data.Temp("day 1").

As for your initializer method, it needed to be called from an instance of the class - being an instance method.

So instead of Initialize tester you should have done tester.Initialize.

Whether you make the internal encapsulated storage an array, a Collection or a Dictionary makes no difference to the calling code - it's an encapsulated implementation detail: your class could just as well store the data in .csv files or into a database if it wanted.

Mathieu Guindon
  • 69,817
  • 8
  • 107
  • 235
  • I had this working for a moment, and I got all excited, but then it stopped. I put in your code, then in the immediate window, I have `set tester = new weatherdata` and then `debug.print tester.values.count`, to which I get a message that the object does not support that property/method. – kumquatwhat Apr 06 '17 at 19:03
  • 1
    `values` is `Private`, you can't access it from the outside (it's the whole entire point!) - if your external code needs to know how many values there are, then you need to expose `TempItemCount`, `HumidItemCount` and `WindItemCount` properties to return the `.Count` of the respective internal collections. – Mathieu Guindon Apr 06 '17 at 19:04
  • 1
    Okay, so I think the thing I was missing is that I refer to the functions in the class, not the dictionary object? I don't need to access `.count`, I was just using it as a testing device to see if the code worked. But so instead of `debug.print tester.values`, the proper form would be `tester.something(key)`? And if i wanted to put in multiple dictionaries, I would have `values` and `values2` both declared, then have `something2` exactly as `something` is except acting on `values2`? – kumquatwhat Apr 06 '17 at 19:12
  • Yes. And you decide what you want the calling code do look like by exposing properties and methods as you see fit. – Mathieu Guindon Apr 06 '17 at 19:13
1

I've found Mathieu Guindon example very instructive but quite minimalist for beginners.

All credits for Mathieu Guindon, but let me share an extended version of his code, using late binding just to change little details.

Class code module named WeatherData:

'Mathieu Guindon,Feb 6 '17
'https://stackoverflow.com/a/43263480
Option Explicit
Private dTemp As Object
Private dHumid As Object
Private dWind As Object
Private Sub Class_Initialize()
    Set dTemp = CreateObject("Scripting.Dictionary")
    Set dHumid = CreateObject("Scripting.Dictionary")
    Set dWind = CreateObject("Scripting.Dictionary")
End Sub
Public Property Get Temp(ByVal key As String) As Double
    Temp = dTemp(key)
End Property
Public Property Let Temp(ByVal key As String, ByVal value As Double)
    dTemp(key) = value
End Property
Public Property Get TempItemCount() As Long
    TempItemCount = dTemp.Count
End Property
Public Property Get Humid(ByVal key As String) As Double
    Humid = dHumid(key)
End Property
Public Property Let Humid(ByVal key As String, ByVal value As Double)
    dHumid(key) = value
End Property
Public Property Get HumidItemCount() As Long
    HumidItemCount = dHumid.Count
End Property
Public Property Get Wind(ByVal key As String) As Double
    Wind = dWind(key)
End Property
Public Property Let Wind(ByVal key As String, ByVal value As Double)
    dWind(key) = value
End Property
Public Property Get WindItemCount() As Long
    WindItemCount = dWind.Count
End Property

Standar code module:

Sub test()
Set Data = New WeatherData
With Data
    .Temp("day 1") = 76
    .Temp("day 2") = 78
    .Humid("day 1") = 0.55
    .Humid("day 2") = 0.61
    .Wind("day 1") = 0.92
    .Wind("day 2") = 1.27

    Debug.Print .Temp("day 2")
    Debug.Print .Humid("day 2")
    Debug.Print .Wind("day 2")
    Debug.Print .Wind("day 2")
    Debug.Print .TempItemCount
End With
End Sub
robertocm
  • 124
  • 6
-1

In this case you should use late binding as follows:

Private Type categories
    Temp As Object
    Humid As Object
    Wind As Object
End Type

Private this As categories

Public Sub Initialize()
    Set this.Temp = CreateObject("Scripting.Dictionary")
    Set this.Humid = CreateObject("Scripting.Dictionary")
    Set this.Wind = CreateObject("Scripting.Dictionary")
End Sub

Furthermore you can't use Let with multiple arguments. You should use a function to do that:

Public Function SetTemp(ByVal HourIndex As Long, ByVal Value As Double)
    this.Temp(HourIndex) = Value
End Function

To run this I used:

Sub test()
    Dim multi As Dictionaries
    Set multi = New Dictionaries

    multi.Initialize

    multi.SetTemp 13, 25.522
    Debug.Print multi.Temp(13)
End Sub

Where my class module is named Dictionaries. So basically use late binding and change all your multi argument let functions to simple functions.

Marius Katinas
  • 470
  • 1
  • 5
  • 13
  • 1
    There's no reason he should have to late bind as long as the reference is set. There is the chance though that the reference just isn't set properly. – Brandon Barney Apr 06 '17 at 18:44
  • 1
    Multiple arguments in Property Let are valid: see Mathieu Guindon example. The need for using functions in your example seems to be related with the use of the Private Type. – robertocm Sep 19 '21 at 17:50