VBA Error Code Object Required --- Have Triple checked but still not working - vba

The commented lines below continue to return to me the error Run Time Error'424': Object Required. I am working on this as part of a self learn class, and I have tripled checked to make sure I am entering the code properly and am still returning this error. I tried looking at other examples of this error on here, but none were helpful to this instance because I do not know the code well enough yet.
Thank you!
Sub FirstArray()
Dim Fruit(2) As String
Fruit(0) = "Apple"
Fruit(1) = "Banana"
Fruit(2) = "Cherry"
Range("A1").Text = "First Fruit: " & Fruit(1)
' ^ RUN TIME ERROR 424
Dim Veg(1 To 3) As String
Veg(1) = "Artichoke"
Veg(2) = "Broccoli"
Veg(3) = "Cabbage"
Range("B1").Text = "First Veg:" & Veg(1)
' ^ RUN TIME ERROR 424
Dim Flower() As String
ReDim Flower(1 To 3)
Flower(1) = "Azalea"
Flower(2) = "Buttercup"
Flower(3) = "Crocus"
Range("C1").Text = "Final Flower:" & Flower(3)
' ^ RUN TIME ERROR 424
End Sub

You just need to change .Text to .Value

Changing .Text for .Value will fix it, but in case you're wondering how "Object required" even remotely makes sense as an error message, here's why.
You know the syntax for implicit value assignment:
foo.Bar = 42
The explicit value assignment syntax is still supported, but obsolete/deprecated:
Let foo.Bar = 42
This assignment calls the Property Let accessor of the Bar property of the foo object, which could look something like this:
Public Property Let Bar(ByVal value As Long)
internalBar = value
End Property
Public Property Get Bar() as Long
Bar = internalBar
End Property
In the case of Range.Text, the property might look something like this:
Public Property Get Text() As String
Text = GetStringRepresentationOfValue
End Property
Notice there's no Property Let accessor, so this:
someRange.Text = "foo"
Isn't legal, because the Text property doesn't expose a Property Let accessor.
So what's the deal with object required? Getting to it.
But first you need to know what a default member is. You see every class module can define a "default member". For collections that member is usually the Item property, by convention.
This means whether you do this:
foo = myCollection.Item(12)
Or that:
foo = myCollection(12)
You get the exact same foo, exactly the same way - the two are exactly the same.. except the latter implicitly calls the default member, and the former explicitly does so.
A class' default member is determined by a hidden member attribute
that you can only see if you export the class module and open it in a
text editor:
Public Property Get Item(ByVal Index As Long) As Variant
Attribute Foo.VB_UserMemId = 0
Item = internalCollection(Index)
End Property
Only 1 member in a class can be the default.
So what the error message is saying, is that when it sees this:
foo.Bar = 42
And knows that Bar is a read-only property that only exposes a Property Get accessor, then the only way for this code to be legal, is if Bar returns an object that has a default member that can be assigned to take that value.
And since Range.Text returns a String and not an Object, VBA complains that an object is required.

Perhaps
Range("B1") = "First Veg:" & Veg(1)
Same for
Range("C1") = "Final Flower:" & Flower(3)

Related

Assigning Values to properties in Structures

In the below code "f" is an instance of the Class FORM which has a property "s" of type SIZE, a structure which has been defined in the code. My question is: When I try to assign values to the attributes of property "s" of the instance "t" directly it does not work: That is the statement f.s.height = 15 does not work. My confusion is arising from the fact that when I print the values of the property "s" of the instance "f", I am able to print the individual attributes of the structure SIZE but the same cannot be done while assignment of value. Assignment of values require me to call the constructor. Why is it so? What is preventing the assignment of the value to the attributes of "s": i.e. f.s.height & f.s.width?
Module Module1
Sub Main()
Dim f As New MyForm()
f.s = New Size(2, 5) 'Works Fine
f.colour = "Red" 'Assignment works just fine
'Below: Individual elements cannot be acceessed for assignment. WHY?
f.s.height = 15 'Doesn't Work
f.s.height = +2 'Doesn't work
'Individual elements can be accessed while printing
Console.WriteLine("Widht = {0}", f.s.width)
Console.WriteLine("Height = {0}", f.s.height)
Console.ReadLine()
End Sub
End Module
Class MyForm
Public Property s As Size
Public Property colour As String
End Class
Public Structure Size
Dim height As Integer
Dim width As Integer
Public Sub New(ByVal w As Integer, ByVal h As Integer)
width = w
height = h
End Sub
End Structure
Pls help.
The compiler should be indicating "Expression is a value and therefore cannot be the target of an assignment".
Change Size from a Structure to a Class (and Dim to Property) to fix the problem:
Public Class Size
Property height As Integer
Property width As Integer
Public Sub New(w As Integer, h As Integer)
width = w
height = h
End Sub
End Class
By the way, you'll also see this behaviour with the standard System.Drawing.Size which is defined as a Structure rather than a Class. (So is Point and probably others.)
This behavior is fundamental to value types (Structures). Conceptually, an instance of a value type is supposed to represent a single immutable value, and any instances with the same value are all supposed to be equivalent. As you have observed, you can get very surprising behavior if you try to change parts of an existing value type. It's really not intended for you to be able to alter them piecewise.
For this reason, I will always recommend that the members of a value type should be marked as ReadOnly so that you get blocked from trying to change them after construction.
If you want to be able to treat something like a mutable object instance instead of an immutable value, it needs to be a reference type (a Class). That's what they're designed to do.
After a lot of searching I came across the following article which probably explains the reason why we are not able to directly access the height/width attribute of the SIZE structure through an instance of the FORM class. Requesting people to go through this as the author has given a lot of details:
http://www.albahari.com/valuevsreftypes.aspx
PLs feel free to share any difference in opinion.

VBA: Class Module: Get and Let

I have no experience with custom classes and a really simple question, but I found it difficult to google this:
I've come across an example (source) for using custom classes.
Module 1
Sub clsRectAreaRun()
'This procedure instantiates an instance of a class, sets and calls class properties.
Dim a As Double
Dim b As Double
Dim areaRect As New clsRectArea
a = InputBox("Enter Length of rectangle")
b = InputBox("Enter Width of rectangle")
areaRect.Length = a
areaRect.Width = b
MsgBox areaRect.rArea
End Sub
class module 'clsRectArea'
'Example - Create Read-Only Class Property with only the PropertyGet_EndProperty block.
Private rectL As Double
Private rectW As Double
Public Property Let Length(l As Double)
rectL = l
End Property
Public Property Get Length() As Double
Length = rectL
End Property
Public Property Let Width(w As Double)
rectW = w
End Property
Public Property Get Width() As Double
Width = rectW
End Property
Public Property Get rArea() As Double
'Read-Only property with only the PropertyGet_EndProperty block and no PropertyLet_EndProperty (or PropertySet_EndProperty) block.
rArea = Length * Width
End Property
My question is regarding this part of the code:
areaRect.Length = a
areaRect.Width = b
MsgBox areaRect.rArea 'rArea = Length * Width
From what I've read, that Get and Let properties have the same name is kind of the point. But I have to ask, how does the code know if it's supposed to call Get or Let? Is it simply down to if, in this case, Length and Width are to the left or to the right of the equal sign? As in, when you want to assign a value to the property, it automatically recognizes it's Let and if it's on the right, like for rArea here, the code is supposed to retrieve the value, so it's Get?
I know, extremely basic, but I'm not 100% sure and I simply want to know if I'm not messing up the something basic.
You can convince yourself which property method is being called by adding MsgBox's to the code in the class module.
For example:
Public Property Let Length(l As Double)
rectL = l
MsgBox "Let Length called."
End Property

Type mismatch trying to set data in an object in a collection

I am getting Runtime Error 13 when trying to update an object stored in a collection. Here is a minimal example.
The class (Class2) of the objects to be stored in the collection.
Option Explicit
Private pHasA As Boolean
Private pHasB As Boolean
Private pSomeRandomID As String
Property Get HasA() As Boolean
HasA = pHasA
End Property
Property Get HasB() As Boolean
HasB = pHasB
End Property
Property Let HasA(propValue As Boolean)
pHasA = propValue
End Property
Property Let HasB(propValue As Boolean)
pHasB = propValue
End Property
Property Let RandomID(propValue As String)
pSomeRandomID = propValue
End Property
Sub SetHasValues(key As String)
Select Case key
Case "A"
pHasA = True
Case "B"
pHasB = True
End Select
End Sub
Minimal code that reproduces the error:
Option Explicit
Private Sub TestCollectionError()
Dim classArray As Variant
Dim classCollection As Collection
Dim singleClass2Item As Class2
Dim iterator As Long
classArray = Array("A", "B", "C")
Set classCollection = New Collection
For iterator = LBound(classArray) To UBound(classArray)
Set singleClass2Item = New Class2
singleClass2Item.RandomID = classArray(iterator)
classCollection.Add singleClass2Item, classArray(iterator)
Next iterator
Debug.Print "Count: " & classCollection.Count
singleClass2Item.SetHasValues "A" ' <-- This code works fine.
Debug.Print "New Truth values: " & singleClass2Item.HasA, singleClass2Item.HasB
For iterator = LBound(classArray) To UBound(classArray)
classCollection(classArray(iterator)).RandomID = classArray(iterator)
classCollection(classArray(iterator)).SetHasValues classArray(iterator) '<-- Type mismatch on this line.
Next iterator
'***** outputs
'''Count: 3
'''New Truth values: True False
' Error dialog as noted in the comment above
End Sub
While the code above appears a little contrived, it is based on some real code that I am using to automate Excel.
I have searched for answers here (including the following posts), but they do not address the simple and non-ambiguous example that I have here. The answers that I have found have addressed true type mismatches, wrong use of indexing or similar clear answers.
Retrieve items in collection (Excel, VBA)
Can't access object from collection
Nested collections, access elements type mismatch
This is caused by the fact, that the parameter of your procedure SetHasValues is implicitely defined ByRef.
Defining it ByVal will fix your problem.
#ADJ That's annoying, but perhaps the example below will allow you to start making a case for allowing RubberDuck.
I've upgraded your code using ideas and concepts I've gained from the rubberduck blogs. The code now compiles cleanly and is (imho) is less cluttered due to fewer lookups.
Key points to note are
Not relying on implicit type conversions
Assigning objects retrieved from collections to a variable of the type you are retrieving to get access to intellisense for the object
VBA objects with true constructors (the Create and Self functions in class2)
Encapsulation of the backing variables for class properties to give consistent (and simple) naming coupled with intellisense.
The code below does contain Rubberduck Annotations (comments starting '#)
Updated Class 2
Option Explicit
'#Folder("StackOverflowExamples")
'#PredeclaredId
Private Type Properties
HasA As Boolean
HasB As Boolean
SomeRandomID As String
End Type
Private p As Properties
Property Get HasA() As Boolean
HasA = p.HasA
End Property
Property Get HasB() As Boolean
HasB = p.HasB
End Property
Property Let HasA(propValue As Boolean)
p.HasA = propValue
End Property
Property Let HasB(propValue As Boolean)
p.HasB = propValue
End Property
Property Let RandomID(propValue As String)
p.SomeRandomID = propValue
End Property
Sub SetHasValues(key As String)
Select Case key
Case "A"
p.HasA = True
Case "B"
p.HasB = True
End Select
End Sub
Public Function Create(ByVal arg As String) As Class2
With New Class2
Set Create = .Self(arg)
End With
End Function
Public Function Self(ByVal arg As String) As Class2
p.SomeRandomID = arg
Set Self = Me
End Function
Updated test code
Private Sub TestCollectionError()
Dim classArray As Variant
Dim classCollection As Collection
Dim singleClass2Item As Class2
Dim my_item As Variant
Dim my_retrieved_item As Class2
classArray = Array("A", "B", "C")
Set classCollection = New Collection
For Each my_item In classArray
classCollection.Add Item:=Class2.Create(my_item), key:=my_item
Next
Debug.Print "Count: " & classCollection.Count
Set singleClass2Item = classCollection.Item(classCollection.Count)
Debug.Print "Initial Truth values: " & singleClass2Item.HasA, singleClass2Item.HasB
singleClass2Item.SetHasValues "A" ' <-- This code works fine.
Debug.Print "New Truth values: " & singleClass2Item.HasA, singleClass2Item.HasB
For Each my_item In classArray
Set my_retrieved_item = classCollection.Item(my_item)
my_retrieved_item.RandomID = CStr(my_item)
my_retrieved_item.SetHasValues CStr(my_item)
Next
End Sub
The 'Private Type Properties' idea comes from a Rubberduck article encapsulating class variable in a 'This' type. My take on this idea is to use two type variable p and s (Properties and State) where p holds the backing variables to properties and s hold variables which represent the internal state of the class. Its not been necessary to use the 'Private Type State' definition in the code above.
VBA classes with constructors relies on the PredeclaredID attribute being set to True. You can do this manually by removing and saving the code, using a text editor to set the attributer to 'True' and then reimporting. The RUbberDuck attribute '#PredeclaredId' allows this to be done automatically by the RubberDuck addin. IN my own code the initialiser for class2 would detect report an error as New should not be used when Classes are their own factories.
BY assigning and intermediate variable when retrieving an object from a class (or even a variant) you give Option Explicit the best change for letting you n=know of any errors.
An finally the Rubberduck Code Inspection shows there are still some issues which need attention

VBA dictionary, class 424 object required error

This code is has an 424 object error.
Dim singleKey As Variant
For Each singleKey In thecollection.Keys()
Debug.Print thecollection.Item(singleKey).Country
Next singleKey
The error is caused by the debug.print line.
The country is part of a custom class.
I am at a lost.
If I remove the "Country", the code run and it returns the index number.
Then you are populating your dictionary with numbers, possibly due to inverting the Key and Item arguments to the Dictionary.Add method, but that's hard to tell without seeing that code.
"Object Required" means you're making a member call against something that isn't an object... like a number, or a string.
In order for thecollection.Item(singleKey).Country to succeed, you need to be populating thecollection (a very misleading name for a Dictionary BTW) with objects that have a Country property.
For example you'd have a Thing class module with this code:
Option Explicit
Private mCountry As String
Public Property Get Country() As String
Country = mCountry
End Property
Public Property Let Country(ByVal value As String)
mCountry = value
End Property
Then you'd be creating instances of that class, and populating the dictionary with them:
Dim thing1 As Thing
Set thing1 = New Thing
thing1.Country = "Canada"
thecollection.Add "foo", thing1 ' note: key first, then the item
Then your code would work. That said I realize this is examplified code, but iterating dictionary keys doesn't strike me as very efficient.

How to assign a value to a variable of type Double, that has been passed as Object?

I am trying to assign a value to global variable, which has a Property of type Double. This Property is passed as Object and the assignment fails.
In the example code below, the value is never assigned to the actual object, but only locally:
Public Class Form1
Friend Home As New Building
Private Sub AssignValues() Handles Me.Load
'Objects of different types are added to a list
Dim listObjects As New List(Of Object)
listObjects.Add(Home.Surface)
'All the Objects in listObjects are assigned a value that
'is stored as String
For Each o As Object In listObjects
SetProperty(o, "45.6")
Debug.Print("Surface = " & Home.Surface.ToString)
Next
End Sub
Private Sub SetProperty(ByRef Variable As Object, ByVal Value As String)
Select Case Variable.GetType
Case GetType(Double)
Variable = CDbl(Value)
Case Else
'...
End Select
End Sub
End Class
Public Class Building
Dim _surface As Double = 0
Public Property Surface As Double
Get
Return _surface
End Get
Set(ByVal value As Double)
_surface = value
End Set
End Property
End Class
The program invariably outputs Surface = 0 instead of 45.6. What am I doing wrong?
I tried to pass the Variable as reference, as suggested here, but without success. I also read about using Reflection, but there ought to be something simpler than that...
When your adding home.surface to the list, your adding a copy of the double to the list and then adjusting that copy. Stick a watch on "o" and see how it changes whilst home.surface remains the same.
If you want to use reflection, try something along these lines.
Dim prop As Reflection.PropertyInfo = o.GetType().GetProperty("Surface")
prop.SetValue(o, 45.6)
With Variable.GetType you will get always Object, because this is the type of Variable. What you can do with an Object is converting/casting it into a different type (like Double).
The best way to determine the "original type" from where the Object comes would be including an additional variable telling it. Another option might be converting the given Object into the target Type and see if it is not nothing/does not trigger an error. But this second option is not too accurate, mainly when dealing with "equivalent types" like Doubles/Integers.