The default property for cells() - vba

I was under the impression VBA had default properties for all its objects. So that if I say Cells(counter, x) the default property attached will be .value. Additionally, I've always used Cells(counter, x) and Cells(counter, x).value interchangeably. However, when adding items to a collection via a for loop, I noticed if I did not include .value instead of storing the value, it stored the value as represented by the location in the worksheet. Such that if the location were deleted the reference in the collection would be lost. This brings me to two discoveries: 1) Collections can store non-static references, and 2) Cells() and other objects do not have default properties such as .value.
If anyone can clarify, confirm, and enlighten, that would be excellent.

No, not all types have a default member.
Any class module can have a default member, by specifying a special member attribute (you'd have to export and edit the code file manually to do this, the VBE doesn't expose any functionality for this):
Attribute {member name}.VB_UserMemId = 0
Only one member is allowed to be a type's default member.
You're discovering the nastiness of default members and why they should be avoided.
VBA does a lot of things to "make our lives easier" (e.g. implicit type conversions), and default members is one of these things.
Collections can store non-static references
I don't know what a "static reference" is, but when you store an object reference in a Collection, you're not storing a copy of the object but a reference to it.
Cells() and other objects do not have default properties such as .value.
Global.Cells is a parameterized property getter that returns a Range object reference; Range.Cells is also a getter that returns a Range object; there is no Cell class in the Excel object model. The default member of a Range is a hidden [_Default] member that appears to resolve to its Value. But then when you do this:
Dim c As Collection
Set c = New Collection
c.Add ActiveSheet.Cells(1, 1)
Then you're adding the Range reference that .Cells(1, 1) returns, and then this:
Debug.Print c.Item(1)
Will output that Range object's value. Yet this:
Debug.Print TypeName(c.Item(1))
Will output Range.
Confusing? Yes. That's why you should always have Option Explicit specified, work with variables declared with an explicit type as much as possible, avoid implicit type conversions, ...and avoid using default members.
By writing code that reads exactly as it should behave, you avoid a number of VBA traps, and when you eventually want to learn some VB.NET or C# you won't be lost at all about type safety and explicitness.

Related

How to handle object declaration in VBA (Error 91)

I'm stuck in VBA and I couldn't find a good answer in the other questions related to error 91. I want to create an object and store variables and arrays inside that object. I tried an approach like I would do in js:
Dim block As Object
...
Set block = Nothing
block.Name = "Unbekannter Prüfblock"
block.Awf = "Unbekannter Anwendungsfall"
block.Conditions = Array()
block.Checks = Array()
I use the "Set block = Nothing" because I will use it multiple times in a loop.
But all I get is error 91 - Object variable not set
How can I set the object?
Do I really have to declare everything in vba?
Isn't there a "stop annoying me with declaration notices" toggle? ;-)
Update
Thank you all so much for the detailed answers!
As suggested I created a class for "block" and also a class for "condition" and "check". Block for example:
Option Explicit
Public name As String
Public awf As String
Public conditions As Collection
Public checks As Collection
Then I use it inside my code like this:
Dim bl As Block
Dim co As Condition
Dim ce As Check
Set bl = New Block
bl.name = ws.Range("B" & i).value
bl.awf = ws.Range("B" & i).value
Set co = New Condition
co.attr = ws.Range("B" & i).value
co.value = ws.Range("C" & i).value
bl.conditions.Add co
VBA isn't Javascript; objects and their members cannot be created inline, they need a class definition.
When you make a member call against an object, you refer to it by name, and whenever that name refers to a null reference (Nothing) you'll get error 91.
To fix it, you need to ensure every member call is made against a valid object reference. Using the Set keyword you can assign such a reference, and to create a new instance of an object you can use the New keyword followed by the name of the class that defines the type you want a new instance of:
Dim Block As Object
Block.Something = 42 ' Error 91
Set Block = New SomeClass ' set reference
Block.Something = 42 ' OK
Note that because the object is declared As Object, every member call is late-bound (resolved at run-time); if the member doesn't exist (or if there's a typo), you'll get error 438 at run-time.
You can move this error to compile-time with early binding by using a more specific data type for the declaration:
Dim Block As SomeClass
Because the members of SomeClass are known at compile-time, the IDE will now provide you with a member completion list when you type up a member call, and typos will no longer be valid at compile-time: strive to remain in the early-bound realm whenever possible! Note: As Variant (explicit or not) is also similarly late-bound.
So we add a new class module and call it SomeClass and we add a couple of public fields:
Option Explicit
Public Name As String
Public Case As String
Public Condition As Variant
Public Check As Variant
And now you can create and consume a new instance of that class, and add instances of it to a collection to process later (note: you can't do that with a UDT/Type).
The VBIDE settings have an annoying option ("automatic syntax check", IIRC) that immediately pops a message box whenever there's a compilation error on the current line; uncheck it (invalid lines will appear in red, without a message box), but do have the "require variable declaration" setting checked: it will add Option Explicit to every module, and that will spare you from a number of easily avoidable run-time errors, moving them to compile-time.
In JS, you can add properties (together with values) on the fly to an object. That's not possible in VBA (and most other languages).
Your declaration Dim block As Object is defining a variable that is supposed to point to an Object. But it isn't pointing to anything yet, per default it is initialized with Nothing, which is, literally, nothing, and has neither properties nor methods, it's just a placeholder to signal "I don't point to anything yet". Furthermore, Object cannot be instantiated.
in VBA, you assign something to an object variable with Set (this is different to most other languages). If you want to create a new object, you use the keyword New.
However, before you do that, you need to know what kind of object (which class) you need. This can be an existing class, eg a Worksheet or a Range in Excel, or it can be an own defined class (by creating a new class module in your code). In any case, you need to define the properties and the methods of that class. Considering the most simple class module Class1 (of course you should think about a more usefull name):
Option Explicit
Public name as String
Public case as String
(I will not start to talk about private members and getter and setter).
You then write
Dim block As Class1
Set block = New Class1
block.name = "Unbekannter Prüfblock"
(But block.data = "Hello world" will not be possible as data is not a member of your class.)
Big advantage of this attempt is that the compiler can show you when you for example mistyped a property name before you even start your code (Debug->Compile). In JS, you will get either a runtime error or a new property on the fly - hard to find nasty bugs.
If you later need a new (empty) object, just create a new object using New. If the old object is not referenced anywhere else, the VBA runtime will take care that the memory is freed (so no need to write Set block = Nothing).
In case you really don't know the properties in advance (eg when you read XML or JSON files or a key-value list from an Excel sheet...), you can consider to use a Collection or a Dictionary. Plenty of examples on SO and elsewhere.
One remark to block.Condition = Array(). Arrays in VBA are not really dynamic, you cannot add or remove entries easily during runtime. In VBA, you have static and dynamic arrays:
Dim a(1 to 10) as String ' Static array with 10 members (at compile time)
Dim b() as String ' Dynamic array.
However, for dynamic members you need to define how many members you need before you write something into it, you use the ReDim statement for that. Useful if you can calculate the number of members in advance:
Redim b(1 to maxNames) ' Dynamic array, now with maxNames members (whatever maxNames is)
You can change the array size of a dynamic array with Redim Preserve, but that should be an exception (bad programming style, inperformant). Without Preserve, you will get a new array, but the former data is lost.
Redim Preserve b(1 to maxNames+10) ' Now with 10 more members.
If you really don't know the number of members and it can change often during runtime, again a Collection or a Dictionary can be the better alternative. Note that for example a Dictionary can itself a Dictionary as value, which allows to define Tree structures.
Regarding your issue adding to the collection:
You need to add this code to your class module "Block" - only then you can add objects to the collections
Private Sub Class_Initialize()
Set conditions = New Collection
set checks = new Collection
End Sub

Why must Set be used to hold objects?

While working on my PowerPoint macro, I noticed the following:
To obtain the current, active slide:
Dim currSlide As Slide
Set currSlide = Application.ActiveWindow.View.Slide
To obtain a newly created Textbox:
Dim textbox As Shape
Set textbox = currSlide.Shapes.AddTextbox(...)
I'm new to VBA, having worked with Java, C++ & C#. Why must Set be used above? Why does using Slide & Shape instead generate errors? In what way is VBA different in how variable declaration work in this respect?
This is taken from Byte Comb - Values and References in VBA
In VBA, the difference between value types and reference types is made explicit by requiring the keyword Set when assigning a reference. In addition, you will often see assigning a reference referred to as “binding”.
Byte Comb goes into great detail about the inter working of the VBA. I highly recommend that any serious about programming using the VBA read through it.
In layman's terms: Set is used so that the compiler knows that the programming wants to have a variable reference an Object type. In this way, the compiler will know to throw an error when you are trying to assign a value to an object.
Another reason is that objects can have default values.
In this example I have a variable named value that is of a Variant type. Variants can be of any type in the VBA. Notice how the compiler knows that to assign the value of the Range when value = Range("A1") and to set a reference to the Range when the keyword Set is used in Set value = Range("A1").

Returning the Default Property

I fill A1 and A2 as follows:
I then run:
Sub WhatIsGoingOn()
Dim r As Range, sh As Worksheet
Set r = Range(Cells(1, 1))
Set sh = Sheets(Cells(2, 1))
End Sub
I expected that in both cases, VBA would use the default property of Cells (the Value) property to Set each variable. However I get a runtime error 13 on the last line of code!
In order to avoid errors, I must use:
Sub WhatIsGoingOn2()
Dim r As Range, sh As Worksheet
Set r = Range(Cells(1, 1))
Set sh = Sheets(Cells(2, 1).Value)
End Sub
What is going on here ??
The difference is in how the input to their default properties is handled by the implementation of the Range and Sheets objects.
The default property of both the Range and the Sheets object takes a parameter of type Variant. You can pass anything to it, so no type coercion will be necessary. In your first example you pass a Range object to both.
How the default properties handle the input is up to themselves. Apparently the property of the Range tries to retrieve the default value of the passed parameter, in your example an address as String. The Sheets object doesn't seem to be so forgiving and raises an error because you neither passed a number nor a String.
Inconsistency is one of the strengths of VBA...
Btw., passing CStr(Cells(2, 1)) would also work, because you explicitly cast to String before passing as a parameter.
Perhaps Leviathan's comment that "Inconsistency is one of the strengths of VBA..." may ring true, but there are some contextual details that his answer neglects (and is technically incorrect on some subtle points). He is correct that for the given code that all of the parameters are variants, but the statement that "no type coercion will be necessary" can be misleading and perhaps just wrong in many cases. Even if many objects and methods are programmed to handle multiple types and default values, this very question reveals that it is a mistake to avoid purposely ensuring (or coercing) the correct data type. Avoiding default properties altogether (by always typing out the full reference) can avoid many headaches.
A significant difference between the lines of code for this particular question is this: Range is a property that takes parameters, while Sheets is also a property but has no parameters. Range and Sheets are NOT objects in this context, even though they are properties which do return Range and Sheets objects, respectively. They are properties of an (automatic global) object defined for the particular module or Excel workbook instance. This detail is not trivial for understanding what the code is actually doing.
The Obect Browser in the VBA window reveals the following metadata for the two properties:
Property Range(Cell1, [Cell2]) As Range
Property Sheets As Sheets
For Range(Cells(1, 1)), the argument Cells(1,1) is passed to the parameter Cell1. Cells itself is a property of the Excel.Global hidden object and it returns a Range object, so that Cells(rowindex, colindex) is calling a hidden default property of the Range class equivalent to Cells._Default(rowindex, colindex). The return type of the property _Default() is not declared, so technically it could return any type in a variant, but inspection shows that it returns a Range object. Apparently passing a Range object to its default property will attempt to take the default value and if it is a valid range value, like a string with a range expression, then it will execute without error.
The default property for the Sheets class is the parameterized hidden _Default(Index) method. Thus, Sheets(Cells(2, 1)) is equivalent to Sheets._Default(Cells(2, 1)). More importantly, it means that Sheets._Default(Cells(2, 1)) is passing a Range object as an index value, but documentation says that it expects an integer or string value. We already mentioned that the index parameter is variant... and when passing an object to a variant, it always passes the actual object and never its default property. So we know that Sheets.Item obtains a Range object in that call. Here is were Levithan was correct in that Sheets.Item can decide what to do with it. It is likely that it could have been smart enough to get the single string value and continue without error. Other collection objects (with a default Item(index) property) in MS Office objects do not seem to exhibit this same "pickiness", so it appears that Sheets._Default() (and perhaps Sheets.Item()) is being rather strict on validating its arguments. But this is just a particular design issue with this method only... not necessarily an overall issue with VBA.
What can be difficult is determining exactly what the source objects of the properties are. Inside the ThisWorkbook module, Me.Sheets reveals that Sheets is a property of the particular Workbook for the module. But Me.Range is not valid in the Workbook module, but right-clicking on the Range property (without the Me qualifier) and choosing "Definition" results in the message "Cannot jump to Range because it is hidden". However, once in the Object Browser, righ-clicking within the browser one can choose "Show Hidden Members" which will then allow navigating to the hidden Global objects and other hidden members.
Why the inconsistency and the hidden properties? In an attempt to make the current instance of Excel and all of its components accessible in a "natural" way, Excel (and all Office applications) implement these various hidden properties to avoid the "complexity" of having to repeatedly discover and type out full references. For instance, the automatic global Application object also has both Range and Sheets properties. Actual documentation for Application.Sheets, for example, says "Using this property without an object qualifier is equivalent to using ActiveWorkbook.Sheets". Even that documentation fails to say is that ActiveWorkbook is in turn a property of the global Excel Application object'.

Why doesn't an Excel/VBA user defined default property that returns a Range behave like a Range?

A property in an Excel/VBA class I'm writing returns a Range. I made it the default property for the class using the technique described at http://www.cpearson.com/excel/DefaultMember.aspx. I expected to use all the Range class's built-in properties and methods with objects of my class without specifying the property explicitly. It doesn't work. Here are a couple of much simpler classes to illustrate. (These listings are the exported source viewed with a text editor since VBA's editor hides the Attribute statements.)
' clsDefLong: This class just verifies that default properties work as I expected.
Public Property Get DefProp() As Long
Attribute DefProp.VB_UserMemId = 0
DefProp = 125
End Property
' clsDefRange: This class is identical except the default property returns a Range.
Public Property Get DefProp() As Range
Attribute DefProp.VB_UserMemId = 0
Set DefProp = ActiveCell
End Property
Here's a Sub in a normal module to instantiate and test the classes. The comments indicate what happens when I single step through it:
Sub DefTest()
Dim DefRange As New clsDefRange, DefLong As New clsDefLong
Debug.Print DefLong.DefProp '(1) Displays 125. Verifies the class behaves as intended.
Debug.Print DefLong '(2) Same as (1). Verifies VBA uses the DefProp property as the default.
Debug.Print DefRange.DefProp.Value '(3) Displays the ActiveCell content. Verifies that this class works as intended.
Debug.Print DefRange.DefProp '(4) Same as (3). Verifies VBA knows DefProp returns a Range without further prompting.
Debug.Print DefRange '(5) Aborts with the messge "Run-time error '13': Type mismatch"
End Sub
Why doesn't DefRange in statement (5) behave just like DefRange.DefProp in statement (4)?
If I change statement (5) to:
Debug.Print DefRange.Cells(1, 1)
The compiler selects ".Cells", says "Compile error: Method or data member not found" and stops so the problem is in the object model - not just something getting messed up at run-time. Am I doing something wrong? Or isn't it possible to have a default property that returns a Range? How about other built-in classes? User defined classes?
Debug.Print DefRange
This seems like you're asking it to chain default properties and it won't do it. You can only pull the default property from the object you provide. In this case, you're returning a range object and that can't be printed. VBA won't go to the next level to see if the default property returns an object and if that object type has a default property. I suppose if it did, you could create an infinite loop - two objects each the result of the default property of the other.
Debug.Print DefRange.Cells(1, 1)
No default property will insert itself into a dot-chain. I assume this is because if DefRange did have a Cells property of its own, which would it use? I can't think of any objects in Excel's model that behave this way. You can use this
Debug.Print DefRange(1,1)
This seems to be an example of chaining default properties, which I said it wouldn't do. I guess the (1,1) is enough to jump start the chain again. DefRange returns a range object, (1,1) returns a range object, and the Value (default) property is returned.
Interesting question. I wonder if the default property feature was built this way intentionally or it's just the way it worked out.

VB.NET, Is Object Returned by Reference from Function

This should be a fairly common question, but I haven't found a straightforward answer anywhere.
If I instantiate an object within a function in VB.NET and return it, does it return it by reference or by value. IE - should I be worried about performance if I write something like this:
Public Function ret_obj_func() As big_object
Dim ret_obj As New big_obj(<lots of stuff>)
Return ret_obj
End Function
If I call this function from somewhere else, will it instantiate the object in the ret_obj and then create a deep copy to pass back a copy to the caller, Or will it just pass back a reference?
There are two dichotomous issues here with similar vocabulary involved: value versus reference types, and passing variables by value versus by reference.
Value v. Reference Types
The first issue is value versus reference types. Value types are passed around through copying - usually. The value types are:
Date
Char
U/Int(16/32/64)
Decimal
Single and Double
Boolean
Structs
Enums
All but the above-listed types are reference types. When an object gets passed around, what is actually being passed is its memory address, which is often thought of as an int on 32 bit platforms and a long on 64 bit platforms.
Passing by Value v. By Reference
The second issue is passing a variable by value versus reference.
A variable is a slot at a certain position in memory that can hold stuff. For value types, it holds the actual value. For reference types, it holds the memory address of the object on the heap (or is Nothing).
By Value
When you pass a variable by value, whatever is at that variable's memory location is what gets copied. For value types, that means the value itself is copied. For reference types, what gets copied is the memory address of the object refered to by the variable.
By Reference
Remember that a variable is just a slot in memory for holding stuff. When you pass a variable by reference, you are passing the address of that slot (as opposed to the data in that slot).
If that variable is a value type, that slot holds the value itself, so the thing being passed is a pointer to the value.
If that variable is a reference type, the slot is holding a pointer to the object's location in memory, so the thing being passed is a pointer to your variable (just like with value types), which itself contains another pointer (not like value types) which leads to the memory location that holds the object referred to by the variable.
This allows a function to modify a variable in another function, like this:
Sub IPassByReference
Dim myVariable As Boolean = False
IReceiveByReference myVariable
Debug.Print(myVariable.ToString()) 'Always prints True
End Function
Sub IReceiveByReference(ByRef flag As Boolean)
flag = True 'the memory address of myVariable was passed.
End Function
Let's compare to the situation where you pass by value:
Sub IPassByValue
Dim myVariable As Boolean = False
IReceiveByValue myVariable
Debug.Print(myVariable.ToString()) 'Always prints False
End Function
Sub IReceiveByValue(ByVal flag As Boolean)
flag = True 'the value of myVariable was passed.
End Function
In the above example, Boolean is a value type. If it were an object, IReceiveByReference would have the power to point myVariable to an entirely new object, because it received the address of myVariable - not the address of the object to which myVariable points. By contrast, IReceiveByValue was only passed the contents of myVariable, so it cannot change myVariable to point to a new object. It could still change the object by setting its fields and properties and calling its methods, though.
Return By-Reference?
Although functions can pass variables by reference, they cannot return them that way. When a function returns, its local variables do not exist anymore (or are pending cleanup if they are heap-allocated). Functions therefore always return by value; because the local variables don't have valid memory addresses anymore, there aren't any variable references to return.
Putting it all together, when you return an object from a function, the only thing that is copied is the address of the object. When you return a value type from a function, the value itself is copied.
This means reference types are essentially value types, wherein the value is the memory address of an object on the heap, (or Nothing).
It just passes back a reference (assuming big_obj is a class). I wouldn't use the term "by reference" here, as that has a subtly different meaning when it comes to parameter passing - but assuming big_obj is a class - a reference type - the value of ret_obj is a reference, and that reference will be what's returned.
I don't have any articles on this from a VB perspective, but if you're happy to look at C#, you may find these articles useful:
Reference and value types
Parameter passing in C#
VB.NET does not have the ability to return by reference. Neither does C# for that matter, but it has been proposed. What you actually get back is just a reference to the object. So to precisely define this it returns a reference to the object. It does not return by reference like what you might compare to the ByRef keyword.