Igor Cheglakov
Igor Cheglakov

Reputation: 555

Refer to object properties dynamically

I have an exemplar of the class ClsFruit With following member variables:

enter image description here

I also have an excel sheet with data, like this:

enter image description here

I don't feel like Populating the object using direct references like

Fruit.Name = FruitSheet.Cells(1,2).Value
Fruit.Color = FruitSheet.Cells(2,2).Value
Fruit.Price = FruitSheet.Cells(3,2).Value

is the way to go because it's tons of repetitive code and positions of items on the worksheet might change in the future. So I wanted to loop through the first column in excel Name-Color-Priceand populate the object dynamically something like this:

Dim rg As Excel.Range
Set rg = FruitSheet.Range("A1", "A3")
Dim Cell As Variant

For Each Cell In rg
    Fruit(Cell.Value) = Cell.Offset(0, 1).Value
Next Cell

But this Fruit(Cell.Value) construct doesn't work, I get "Object doesn't support this property or method" error. Is there a way around it?

Upvotes: 0

Views: 121

Answers (2)

SmileyFtW
SmileyFtW

Reputation: 336

Using a worksheet proxy will do what you want, I think. This will return a collection of your objects:

WorksheetProxy class
Option Explicit

Private Property Get Table() As ListObject
    Set Table = Sheet1.ListObjects(1)
End Property

Private Property Get NameColumnIndex() As Long
    NameColumnIndex= Table.ListColumns("Name").Index
End Property

Private Property Get ColorColumnIndex() As Long
    ColorColumnIndex= Table.ListColumns("Color").Index
End Property

Private Property Get PriceColumnIndex() As Long
    PriceColumnIndex= Table.ListColumns("Price").Index
End Property

Private Property Get Data() As Collection

    Dim result As Collection
    Set result = New Collection

    Dim currentRow As ListRow
    For Each currentRow In Table.ListRows

        Dim currentItem As ClsFruit
        Set currentItem = New ClsFruit

        currentItem.Name= currentRow.Range(ColumnIndex:=NameColumnIndex).value
        currentItem.Color= currentRow.Range(ColumnIndex:=ColorColumnIndex).value
        currentItem.Price= currentRow.Range(ColumnIndex:=PriceColumnIndex).value

        result.Add currentItem

    Next

    Set Data = result

End Property

Mathieu Guindon has a discussion of this approach here: https://rubberduckvba.wordpress.com/2017/12/08/there-is-no-worksheet/

In the comments section is a link to his example workbook.

Upvotes: 1

Storax
Storax

Reputation: 12167

You probably need to do something like that

For Each Cell In rg
    Select Case Cell.Value
        Case "Name"
            fruit.Name = Cell.Offset(0, 1).Value
        Case "Color"
            fruit.Color = Cell.Offset(0, 1).Value
        Case "Price"
            fruit.Price = Cell.Offset(0, 1).Value
    End Select
Next Cell

Another way would be to have corresponding propertiers in your class. Then you could use CallByName

For Each Cell In rg
    CallByName fruit, cell.value, VbLet, Cell.Offset(0, 1).Value
Next Cell

Update: The class has to be changed like that

    Option Explicit

    Public mName As String
    Public mColor As String
    Public mPrice As Long

    Property Let name(nValue As String)
        mName = nValue
    End Property
    Property Get name() As String
        name = mName
    End Property

   ' continue with similar properties for the other member variables

Update 2:As pointed out in the comments it is not neccessary to have Let/Get etc. One can stick to public member variables and CallByName will just work fine. It is just IMHO the cleaner approach on the long run, see here

Upvotes: 2

Related Questions