Create class with multiple indexed properties - excel

I'm trying to create a class with multiple properties that are indexed by the same number I pass into the property. I've tried several configurations of code, my latest failure being this:
'dataTypeClass class module
Private ap() As String
Private dt() As String
Public Property Get apos(index As Long) As Variant
Set apos = ap(index)
End Property
Public Property Get dataType(index As Long) As Variant
Set dataType = dt(index)
End Property
Public Property Let apos(index As Long, apVal As String)
ap(index) = apVal
End Property
Public Property Let dataType(index As Long, dtVal As String)
dt(index) = dtVal
End Property
I get this error code mostly:
Expecting to use below to get "INT" and "" for item 1 in sub below.
Sub classTest()
Dim d As New dataTypeClass
d(1).dataType = "INT"
d(1).apos = ""
Debug.Print d(1).dataType & d(1).apos
End Sub
What several things am I doing wrong?
EDIT:
Taking suggestions below (such as editing my question rather than commenting), I'm setting the variants to strings, and using let instead of set. Since I did indeed get a subscript out of range, I need to initialize the class, but do I need to redim index? How might I do that if the index will have a different maximum each time I run the script?

The apos property1 gets a Variant at the specified index; the implementation suggests that the encapsulated ap(index) is an Object, so the return type should probably be Object instead of Variant:
Public Property Get apos(index As Long) As Variant
Set apos = ap(index)
End Property
The setter uses Let assignment, which is hackish, but allowed given the value is Variant - oh wait no, it's a String!
Public Property Let apos(index As Long, apVal As String)
ap(index) = apVal
End Property
You're getting this error because, as the error states, the property definition is inconsistent. If ap(index) is a String, then the getter should look like this:
Public Property Get apos(index As Long) As String
apos = ap(index)
End Property
If ap(index) is an Object, then the setter should look like this:
Public Property Set apos(index As Long, apVal As Object)
Set ap(index) = apVal
End Property
...and the getter like this:
Public Property Get apos(index As Long) As Object
Set apos = ap(index)
End Property
Or like this:
Public Property Get apos(index As Long) As Variant
Set apos = ap(index)
End Property
Public Property Set apos(index As Long, apVal As Variant)
Set ap(index) = apVal
End Property
In other words:
The RHS/value parameter of Property Let/Property Set needs to be the same type as the return type of the same-name Property Get member.
If the type is an object type, use Property Set for assignments.
If the type is a value type, use Property Let for assignments.
Avoid Variant if possible.
Do not use Set to assign anything other than an object reference.
1All of this is also true for the dataType property.

You have a variant for the return type of your Get but your Let additional param is using a string. Perhaps you need dtVal As Variant? As mentioned in comments and other answer there are additional things to consider; some will be implementation specific.
Quote:
The parameters for Property Get, Property Let, and Property
Setprocedures for the sameproperty must match exactly, except that the
Property Let has one extra parameter, whose type must match the return
type of the corresponding Property Get, and the Property Set has one
more parameter than the corresponding Property Get, whose type is
either Variant, Object, a class name, or an object library type
specified in an object library. This error has the following causes
and solutions:
The number of parameters for the Property Get procedure isn't one less
than the number of parameters for the matching Property Let or
Property Set procedure. Add a parameter to Property Let or Property
Set or remove a parameter from Property Get, as appropriate.
The parameter types of Property Get must exactly match the
corresponding parameters of Property Let or Property Set, except for
the extra Property Set parameter. Modify the parameter declarations in
the corresponding procedure definitions so they are appropriately
matched.
The parameter type of the extra parameter of the Property Let must
match the return type of the corresponding Property Get procedure.
Modify either the extra parameter declaration in the Property Let or
the return type of the corresponding Property Get so they are
appropriately matched.
The parameter type of the extra parameter of the Property Set can
differ from the return type of the corresponding Property Get, but it
must be either a Variant, Object, class name, or a validobject library
type.
Make sure the extra parameter of the Property Set procedure is either
a Variant, Object, class name, or object library type.
You defined a Property procedure with an Optional or a ParamArray
parameter. ParamArray and Optional parameters aren't permitted in
Property procedures. Redefine the procedures without using
thesekeywords.

Related

VBA: Can't set a variable of a Structure within a class

I have a class, implementing two interfaces, one interface for normal use, and one interface that only reveals a function to create the class with initial parameters. The class has a field that is a Structure with two variables within, I am using the Get property of the class to get the structure, and then using dot notation to access the field within the structure, and then I am trying to set it to a number, but it never works. If I try to access the structure with the private variable within the class it works, but I want to be consistent and only use the properties to modify it within the Create function.
Public Function Create(WorksheetName As String, Optional CurrentRow As Long = 4) As ISheetInfo
With New clsSheetInfo
Set .WS = ThisWorkbook.Worksheets(WorksheetName)
Set .Cols = CreateColumnDictionary(.WS, 3)
Let .Rows.Current = CurrentRow
Let .Rows.Final = .WS.Cells(.WS.Rows.Count, 1).End(xlUp).Row
Set Create = .Self
End With
End Function
When I step through, it first goes to this property (RowData is the UDT with two fields, .Current and .Final:
Property Get Rows() As RowData
Rows = pRows
End Property
But then after the assignment, .Rows.Current is still 0, I'm not sure why.

VBA Implements / Inheritance

I'm having a hard time understanding and working with Implements and I'm failing to see why this is of any use if Inheritance isn't supported with VBA.
I'm testing the code below and I keep getting the error:
Compile Error:
Object module needs to implement '~' for interface '~'
Interface: ITransmission
Option Explicit
Public pVENDOR As String
Public Property Get VENDOR() As String
End Property
Public Property Let VENDOR(ByVal value As String)
End Property
Base Class: cASN
Option Explicit
Implements ITransmission
Private Property Let ITransmission_pVENDOR(ByVal value As String)
pVENDOR = value
End Property
Private Property Get ITransmission_pVENDOR() As String
ITransmission_pVENDOR = pVENDOR
End Property
Unit Test Method: mUnitTesting
Private Sub Test_cASN()
Dim foo As cASN
Set foo = New cASN
foo.VENDOR = "Test"
End Sub
Still very new to Implements and it is something I want to learn, and I've done a fair amount of research into it.
Question 1:
Why am I getting an error message when I try to unit test this?
Question 2:
What is the real benefit here, if inheritance isn't supported?
You implement pVENDOR but not the two VENDOR properties.
I'm assuming you want the interface to be a get/let of the VENDOR property.
Your Public pVENDOR As String looks like a backing field for this property, as an Interface cannot include an implementation then its not needed.
The Interface should look like:
Public Property Get VENDOR() As String
End Property
Public Property Let VENDOR(ByVal value As String)
End Property
Then when you implement it:
Implements ITransmission
Private pVENDOR As String '// local implementation detail
Public Property Let ITransmission_VENDOR(ByVal value As String)
pVENDOR = value
End Property
Public Property Get ITransmission_VENDOR() As String
ITransmission_VENDOR = pVENDOR
End Property
And to test:
Private Sub Test_cASN()
Dim foo As cASN
Set foo = New cASN
foo.ITransmission_VENDOR = "Test"
End Sub
What is the real benefit here
How will I know when to create an interface?
The point of an Interface

VBA Error: Definitions of property procedures for the same property are inconsistent, or property procedure has an optional parameter

I have a very simple Class definition.
Class Sheetwriter is defined as follows:
Option Explicit
'Works off of the ActiveCell
'Helps you write data into the cells
Private pCornerCell As String
Public Property Get CornerCell()
CornerCell = pCornerCell
End Property
Public Property Let CornerCell(Value As String)
pCornerCell = Value
Range(Value).Select
End Property
I get a compile error that I don't understand.
Definitions of property procedures for the same property are inconsistent, or the property procedure has an optional parameter.
What am I doing wrong?
Public Property Get CornerCell()
That's returning an implicit Variant, since no return type was specified.
Public Property Get CornerCell() As String
That's returning the String that the compiler is seeing in the Property Let member, and fixes your problem.
FWIW, that Range(Value).Select statement doesn't belong in there at all, and you don't want to work off the active cell and sprinkle Select and Activate statements everywhere.
See How to avoid using Select in Excel VBA macros for some tips about avoiding that.

How to emulate the ScriptingContext's "ASPTypeLibrary.Application" Object

I have been tasked with modifying some legacy ActiveX DLLs written in Visual Basic 6. One of the things I need to do is to emulate the "ScriptingContext" object, (so that we can support other mechanisms for running the DLLs other than IIS without having to re-write large chunks of the code).
Something that has been causing me some grief is the "ASPTypeLibrary.Application" object which has two very different ways to access its stored values, eg:
.Application("KeyName")
or
.Application.Value("KeyName")
How can I create my own VB6 class which supports both of these access mechanisms? I can do one or the other but not both?
(a simple code example would be great thanks, I'm not a VB6 programmer)
I have found a way to do this, see the code snippet below taken from two classes, "clsContext" and "clsContextApp". The latter implements the ".Value" functionality and the former has the ".Application" property...
I have now discovered an even more difficult problem. The ScriptingContext's "ASPTypeLibrary.Request" object has three different ways to access its ".Request.QueryString" property:
.Request.QueryString("KeyName")
or
.Request.QueryString.Value("KeyName")
or
.Request.QueryString
The last method returns a string comprised of all the Key/Value pairs concatenated by "&" characters. I have no idea how to implement this?
' clsContext
Public ContextApp As clsContextApp
Public Property Get Application(Optional ByRef Key As Variant = Nothing) As Variant
If (Key Is Nothing) Then
Set Application = ContextApp
Else
If (Not ContextApp.p_Application.Exists(Key)) Then
Application = ""
Else
Application = ContextApp.p_Application.Item(Key)
End If
End If
End Property
Public Property Let Application(ByRef Key As Variant, ByVal Value As Variant)
If (VarType(Key) = vbString) Then
If (VarType(Value) = vbString) Then
If (Not ContextApp.p_Application.Exists(Key)) Then
ContextApp.p_Application.Add Key, Value
Else
ContextApp.p_Application.Item(Key) = Value
End If
End If
End If
End Property
' clContextApp
Public p_Application As Scripting.Dictionary
Public Property Get Value(Key As String) As String
If (Not p_Application.Exists(Key)) Then
Value = ""
Else
Value = p_Application.Item(Key)
End If
End Property
Public Property Let Value(Key As String, Value As String)
If (Not p_Application.Exists(Key)) Then
p_Application.Add Key, Value
Else
p_Application.Item(Key) = Value
End If
End Property
Well I've managed to answer the additional question regarding ScriptingContext's "ASPTypeLibrary.Request" object which has three different ways to access its ".Request.QueryString" property.
I've included a code snippet below that is based on the code from my previous answer for the "ASPTypeLibrary.Application" object. If I add a new Property to the "clsContextApp" class and make it the default property for that class, then it will be called when the ".Application" property is called without any qualification eg:
MyString = Context.Application
Setting a particular property as the default property in VB6 is a little obscure, but I followed the directions I found here.
' clsContextApp Default Property
Property Get Values(Optional ByVal Index As Integer = -1) As String ' This is the Default Value for clsContextApp
Attribute Values.VB_UserMemId = 0
Dim KeyName As String, Value As String
Values = ""
If (Index < 0) Then
For Index = 0 To p_Application.Count - 1
KeyName = p_Application.Keys(Index)
Value = p_Application.Item(KeyName)
If (Index > 1) Then
Values = Values + "&"
End If
Values = Values + KeyName + "=" + Value
Next Index
Else
If (Index < p_Application.Count) Then
KeyName = p_Application.Keys(Index)
Value = p_Application.Item(KeyName)
Values = KeyName + "=" + Value
End If
End If
End Property
Adding a reference to Microsoft Active Server Pages Object Library, and to COM+ Services Type Library, and then using the object browser reveals some basic things you seem to be missing.
GetObjectContext is a global method in COMSVCSLib with no arguments used to retrieve the current ObjectContext as its return value.
ObjectContext is a Class. It has a read-only default property, named Item that takes a String argument and is of type Variant.
Passing "Application" as an argument to Item returns the current instance of the Application Class.
ScriptingContext is a Class. It is obsolete.
Application is another Class. It has a default property named Value that takes a String argument and is of type Variant.
Value is a property of the Application Class and provides access to a read-write key/value pair store where keys are always Strings. Since it is of type Variant you can store objects as well as simple values and arrays of various types.
None of this looks difficult to replicate in VB6. The key/value store could be a Collection or Scripting.Dictionary.

Trouble with object array as object property in VBA Excel 2010

Private oiCustoms() As CCustomClass
Public Property Get Partners() As CCustomClass()
Set Partners() = oiCustoms()
End Property
Public Property Set Partners(values() As CCustomClass)
ReDim oiPartners(values.Count)
Set oiCustoms() = values()
End Property
When I try to run I get a Compile error:
Definitions of property procedures for the same property are inconsistent, or property procudure has n optional parameter, a ParamArray, or an invalid Set final parameter.
What's wrong here? I have done some looking, and it looks like I can't use an array as a property parameter. Is this correct? Any good workarounds. I will need to have this data for the object stored in an array for use elsewhere.
You are confusing arrays with objects.
Private oiCustoms() As CCustomClass
Public Property Get Partners() As CCustomClass()
Partners = oiCustoms
End Property
Public Property Let Partners(values() As CCustomClass)
ReDim oiPartners(LBound(values) To UBound(values))
oiCustoms = values
End Property

Resources