1

I want to write to custom class properties dynamically. In my use case, I have a table with column headers. The headers are properties of an Issue class. There are over 120 columns per issue. The end user chooses which columns they want included in the report. How do I set the properties of an object when the columns are not known until runtime? I couldn't find anything on Google that helped.

EDITED for clarity

Here is a snippet of my CIssue class:

Option Explicit

Private pIncidentNumber As String
Private pIncidentType As String
Private pContent As String
Private pStartDate As Date
Private pEndDate As Date


Public Property Let IncidentNumber(Value As String)
    pIncidentNumber = Value
End Property
Public Property Get IncidentNumber() As String
    IncidentNumber = pIncidentNumber
End Property
Public Property Let IncidentType(Value As String)
    pIncidentType = Value
End Property
Public Property Get IncidentType() As String
    IncidentType = pIncidentType
End Property
Public Property Let Content(Value As String)
    pContent = Value
End Property
Public Property Get Content() As String
    Content = pContent
End Property
Public Property Let StartDate(Value As Date)
    pStartDate = Value
End Property
Public Property Get StartDate() As Date
    StartDate = pStartDate
End Property
Public Property Let EndDate(Value As Date)
    pEndDate = Value
End Property
Public Property Get EndDate() As Date
    EndDate = pEndDate
End Property

It does nothing but help organize my code. I will build a collection class for this, also. If the end user chooses Incident Number and Content columns I want to set the appropriate properties. There could be up to 1,000 rows of data. So I need to set the properties for the rows that fit the criteria.

Example

I might have 72 rows that fit the criteria. Therefore, I need to add to my collection 72 objects of type CIssue with the correct properties set according to the columns the end user chose.

Thanks!

Brian
  • 2,078
  • 1
  • 15
  • 28
  • 2
    How about using a `Dictionary`? – Mathieu Guindon Oct 02 '18 at 21:20
  • @MathieuGuindon Well, I thought of something like that, but in terms of a case statement. But I don't understand how to use it for getting and setting the properties. Would you provide a practical and simple example? – Brian Oct 02 '18 at 21:24
  • 2
    Create a dictionary, key it with the column names and store a `Boolean` indicating whether the column should be included in the report or not? There are literally thousands of `Scripting.Dictionary` examples out there. – Mathieu Guindon Oct 02 '18 at 23:31
  • @MathieuGuindon I'm sorry, but I still don't understand. I know how to use a dictionary. But I don't know how to use it to add a value to the correct `Property Let` and retrieve the value from the correct `Property Get`. I don't know if the end user will choose `Column1`, in which case I would use `Public Property Let ColumnOne(Value As String)`, or `Column2`, in which case I would use `Public Property Let ColumnTwo(Value As String)`. Only rows that meet certain criteria would be added to the collection. How does a dictionary help with this? – Brian Oct 03 '18 at 10:00
  • 3
    It completely removes the need for every single one of these properties? It *is* the property name/value mapping? – Mathieu Guindon Oct 03 '18 at 10:58
  • Okay. I don't understand. – Brian Oct 03 '18 at 11:48
  • If you can [edit] your question to better explain what you're trying to do, with a code block listing what your `Issue` class looks like, I'd need to make fewer assumptions and thus would be more inclined to explain further. Can't post a useful answer with just the information that's in the question right now, it would be guesswork, at best. – Mathieu Guindon Oct 03 '18 at 11:57
  • 1
    @MathieuGuindon I edited my question. Hopefully, it is more clear now. – Brian Oct 03 '18 at 12:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/181221/discussion-between-brian-and-mathieu-guindon). – Brian Oct 03 '18 at 13:19
  • How does the end user select which properties are to be used and which aren't? How are the issues themselves filtered to get to the Example value of 72 issues? And finally - if you have 120 cols and say 1000 rows and you just want to summarize them - Isn't it something that you can do with Pivot Tables and some slicers? – Rik Sportel Oct 05 '18 at 15:19
  • @RikSportel They select which properties are to be used by picking the columns they want from a listview control. They are filtered according to the unique values I give them from the column they choose in a different listview control. – Brian Oct 08 '18 at 09:45
  • Put your Table in an array, You can Filter the rows and columns your end-user want. No need for a class despite the fact that I can really appreciate the use of classes – EvR Oct 09 '18 at 08:27

1 Answers1

3

The core problem: Create only properties in CIssue objects that are selected according to a listview.

For this first issue, I created a Sheet ("Sheet1") to which I added an ActiveX ListView (MicroSoft ListView Control, version 6.0) that I populated with the Column headers (or property names) as follows in a regular module:

Option Explicit
Sub PopulateListView()
Dim i As Integer
i = 1
With Worksheets("Sheet1")
    .TestListView.ListItems.Clear
    Do While Not IsEmpty(.Cells(1, i))
        .TestListView.ListItems.Add i, , .Cells(1, i).Value
        i = i + 1
    Loop
End With
End Sub

I set the following properties:

  • Checkboxes to True
  • MultiSelect to True

This will allow us to loop over selected items and create properties in our CIssue class accordingly.

Next, I added a reference to MicroSoft Scripting Runtime, so the Dictionary class is available. This is needed, because with the Collection class there's no easy way to retrieve the "property" by "key" (or property name, as below).

I created the CIssue class as follows:

Option Explicit
Private p_Properties As Dictionary
Private Sub Class_Initialize()
    Set p_Properties = New Dictionary
End Sub
Public Sub AddProperty(propertyname As String, value As Variant)
    p_Properties.Add propertyname, value
End Sub
Public Function GetProperty(propertyname As Variant) As Variant
    On Error Resume Next
        GetProperty = p_Properties.Item(propertyname)
    On Error GoTo 0
    If IsEmpty(GetProperty) Then
        GetProperty = False
    End If
End Function
Public Property Get Properties() As Dictionary
    Set Properties = p_Properties 'Return the entire collection of properties
End Property

This way, you can do the following in a regular module:

Option Explicit
Public Issue As CIssue
Public Issues As Collection
Public lv As ListView
Sub TestCreateIssues()
Dim i As Integer
Dim Item As ListItem

Set lv = Worksheets("Sheet1").TestListView
Set Issues = New Collection

For i = 2 To 10 'Or however many rows you filtered, for example those 72.
    Set Issue = New CIssue
    For Each Item In lv.ListItems 'Loop over ListItems
        If Item.Checked = True Then ' If the property is selected
            Issue.AddProperty Item.Text, Worksheets("Sheet1").Cells(i, Item.Index).value 'Get the property name and value, and add it.
        End If
    Next Item
    Issues.Add Issue
Next i
End Sub

Thereby ending up with a Collection of CIssue objects, that only have the required properties populated. You can retrieve each property by using CIssue.GetProperty( propertyname ). It will return "False" if the property doesn't exist, otherwise the value of the property. Since it returns Variant it will cater for Dates, Strings, etc. Note that if you want to loop over filtered rows, you can amend the loop above accordingly. Note that the propertyname parameter for the GetProperty method is also a Variant - This allows you to pass in strings as well as the actual Key objects.

To populate another sheet, with whatever you captured this way, you can do something like the following (in either the same or a different module; note that the Sub above needs to be run first, otherwise your Collection of CIssues will not exist.

Sub TestWriteIssues()
Dim i As Integer
Dim j As Integer
Dim Item As ListItem
Dim p As Variant
Dim k As Variant

i = 1
j = 0
'To write all the properties from all issues:
For Each Issue In Issues
    i = i + 1
    For Each p In Issue.Properties.Items
        j = j + 1
        Worksheets("Sheet2").Cells(i, j).value = p
    Next p
    j = 0
Next Issue

'And add the column headers:
i = 0
For Each k In Issues.Item(1).Properties.Keys
    i = i + 1
    Worksheets("Sheet2").Cells(1, i).value = k
    'And to access the single property in one of the Issue objects:
    MsgBox Issues.Item(1).GetProperty(k)
Next k
End Sub

Hope this is more or less what you were after.

N.b. more background on why the choice for Dictionary instead of Collection in this question

Rik Sportel
  • 2,661
  • 1
  • 14
  • 24
  • This is definitely "more or less" what I was looking for. Just this morning I began implementing the `dictionary` in my class. A friend of mine recommended a variation of the above using an array with column names as the dictionary keys and zero-based integers as the value. Then looping through the filtered array values. This is what I couldn't understand. Thank you for taking the time to do this. Marking as correct and +1. – Brian Oct 09 '18 at 10:08