Issue Creating Class with Boolean - excel

I'm having an issue creating this class and it's stating that the Set parameters are invalid and error on the below code. I've triple checked this and can't seem to find the issue! Any idea why this error is occurring?
CODE:
Option Explicit
Private pIsChargeable As Boolean
Public Property Set IsChargeable(value As Boolean)
pIsChargeable = value
End Property
Public Property Get IsChargeable() As Boolean
IsChargeable = pIsChargeable
End Property
ERROR:
Compile Error:
Definitions of property procedures for the same property are inconsistent, or property procedure has an optional parameter, a ParamArray, or an invalid Set final parameter

Property Set is for Object type variables. You're looking for Property Let.
It might be helpful to read Chip Pearson's explanation of classes as well.

Related

Do I need the Me keyword in class modules?

These two subs do the same thing when inside a class.
Sub DemoMe( )
Me.AboutMe ' Calls AboutMe procedure.
End Sub
Sub DemoMe( )
AboutMe ' Does the same thing.
End Sub
What is the point? Does the Me keyword do anything? What is the preferred way of an object accessing its own members?
tldr; No, although there are situations where it can be useful.
From the VBA language specification (5.3.1.5):
Each procedure that is a method has an implicit ByVal parameter called
the current object that corresponds to the target object of an
invocation of the method. The current object acts as an anonymous
local variable with procedure extent and whose declared type is the
class name of the class module containing the method declaration. For
the duration of an activation of the method the data value of the
current object variable is target object of the procedure invocation
that created that activation. The current object is accessed using the
Me keyword within the <procedure-body> of the method but cannot be
assigned to or otherwise modified.
That's all it is, just a "free" local variable that refers to the specific instance that the method is being called on. This also happens to be the default context for the procedures during their invocation, so it can be omitted if the code is intended to operate on the current instance. Although as #HansPassant points out in the comment above, it also allows the editor to bind to the interface and provide IntelliSense.
That said, there are a couple instances where you would either want to or have to use it (this is by no means an exhaustive list):
Naming collisions:
If your class has a member that "hides" a built-in VBA function, it can be used to make the scope explicit:
Public Property Get Left() As Long
'...
End Property
Public Property Get Right() As Long
'...
End Property
Public Property Get Width() As Long
Width = Me.Right - Me.Left
End Property
Equity Checks:
Public Function Equals(other As Object) As Boolean
If other Is Me Then
Equals = True
Exit Function
End If
'...
End Function
Fluent Functions:
This can be a useful pattern for compositing objects - you perform an action, then return the instance of the class so they can be "chained". Excel's Range interface does this in a lot of cases:
Public Function Add(Value As Long) As Class1
'Do whatever.
Set Add = Me
End Function
Public Sub Foo()
Dim bar As New Class1
bar.Add(1).Add(1).Add 1
End Sub
Not any more than there are reasons to use this in Java, C#, or any other language: it's a reserved identifier that represents the current instance of the class - what you do with that is up to your imagination.
What is the preferred way of an object accessing its own members?
Indeed, an object doesn't need the Me keyword to access it own public interface. Same as this in other languages, I'd even call it redundant. However it can sometimes be a good idea to explicitly qualify member calls with Me, especially when the class has a VB_PredeclaredId attribute (e.g. any UserForm): referring to UserForm1 in the code-behind of UserForm1 yields a reference to the default instance of the class, whereas qualifying member calls with Me yields a reference to the current instance of that class.
Accessing Inherited Members
VBA user code can't do class inheritance, but a lot of VBA classes do have a base class. The members of UserForm when you're in the code-behind of UserForm1, and those of Worksheet when you're in the code-behind of Sheet1, aren't necessarily easy to find. But since the inherited members show up in IntelliSense/auto-complete, you can type Me. and browse a list of members inherited from the base class, members that you would otherwise need to know about in order to invoke.
A class creating an instance of itself inside itself? That I've never seen.
You're missing out! I do this all the time, to enable referring to the object instance held by a With block, inside a Factory Method - like this GridCoord class.
Public Function Create(ByVal xPosition As Long, ByVal yPosition As Long) As IGridCoord
With New GridCoord
.X = xPosition
.Y = yPosition
Set Create = .Self
End With
End Function
Public Property Get Self() As IGridCoord
Set Self = Me
End Property
Note that while the GridCoord class exposes a getter and a setter for both X and Y properties, the IGridCoord interface only exposes the getters. As a result, code written against the IGridCoord interface is effectively working with read-only properties.
Another use is to get the name of the class module, without needing to hard-code it. This is particularly useful when raising custom errors: just use TypeName(Me) for the Source of the error.
The Builder Pattern notoriously returns Me, which enables a "fluent API" design that makes it possible to write code that incrementally builds complex objects through chained member calls, where each member returns Me (except the final Build call, which returns the type of the class being built):
Dim thing As Something
Set builder = New ThingBuilder
Set thing = builder _
.WithFoo(42) _
.WithBar("test") _
.WithSomething _
.WithSomethingElse
.Build
#PBeezy : In addition to my comment :
Me, refers to the object it's coming from so AboutMe resides in the class. If you had another instance, say this is Class1, you'd have dim c as Class1, as soon as you create an instance of Class1 in Class1, you need to tell the compiler which class you are using, the holding class or the instance created in, where, me.class1.aboutme would be logically valid. You can also create, a class for each cell in a workbook, then you could refer to A1's class from B1's class. Also, if there is a public function/sub called AboutMe, this also helps.
Class (clsPerson)
Public c1 As clsPerson
Public strPersonName As String
Public Function NAME_THIS_PERSON(strName As String)
strPersonName = strName
End Function
Public Function ADD_NEW_CHILD(strChildName As String)
Set c1 = New clsPerson
c1.strPersonName = strChildName
End Function
Normal module
Sub test()
Dim c As New clsPerson
c.NAME_THIS_PERSON "Mother"
c.ADD_NEW_CHILD "Nathan"
Debug.Print c.strPersonName
Debug.Print c.c1.strPersonName
End Sub
Gives these results
Mother
Nathan

Create class with multiple indexed properties

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.

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.

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