Is there an implemented function similar to the toString() function in vba? - excel

I want to get an Object within a Collection as a String without calling a function for it.
e.G.:
In java i can
System.out.print(objVarXY)
and the compiler will automatically call the objVarXY.toString() function (if implemented)
in VBA something like this
Debug.Print parameterListe.LList.Item(1)
will cause an error.
Debug.Print parameterListe.LList.Item(1).toString
will work, if i implemented a toString Subfunction.
But what if i dont know what kind of object will be inside my LList collection?

Debug.Print will implicitly attempt coerce the given value expression into a String for output.
When you Debug.Print an object, VBA attempts to Let-coerce the object into a value - if the object doesn't have a default member that ultimately yields a value that can be implicitly converted to a String, then you get run-time error 438 "object doesn't support this property or method" if the class doesn't have a default member.
If the object is user code (i.e. your own class module), and if it makes sense to do so, you could add a default member yourself, and have the class responsible for knowing how to represent itself as a String (note that the VB attribute is hidden in the VBE code panes, and must be edited outside the VBE - unless you're using Rubberduck, in which case you can simply add a #DefaultMember annotation and synchronize annotations/attributes):
'#DefaultMember
Public Function ToString() As String
Attribute ToString.VB_UserMemId = 0
'...
End Function
But all this does is give the class the ability to be essentially treated as a String, through implicit member calls. I'd call that something like an abuse a language feature (it's through this mechanism that Debug.Print Excel.Application outputs the application's Name, or Debug.Print adoConnection outputs the connection's ConnectionString property), since as you noted, you might as well just invoke that ToString method explicitly.
If the object doesn't know how to represent itself as a String, then something, somewhere will have to. In Java (IIRC) and .NET this would essentially be the default ToString implementation:
Debug.Print TypeName(objVarXY)
...which is rather useless, but that's essentially what ToString does by default.
Whether you're writing Java, C#, or VBA, there needs to be code responsible for knowing how to represent objVarXY as a String.
Sadly VBA doesn't do fancypants pattern matching, so we can't Select Case TypeOf obj like in C# (it only recently got that ability - don't know about Java), and since Select Case TypeName(obj) wouldn't be type-safe, I'd go with If...ElseIf:
Public Function Stringify(ByVal obj As Object) As String
If TypeOf obj Is Something Then
Dim objSomething As Something
Set objSomething = obj ' cast to Something interface
Stringify = objSomething.SomeProperty
ElseIf TypeOf obj Is SomethingElse Then
Dim objSomethingElse As SomethingElse
Set objSomethingElse = obj ' cast to SomethingElse interface
Stringify = objSomethingElse.AnotherProperty & "[" & objSomethingElse.Foo & "]"
'ElseIf TypeOf obj Is ... Then
' ...
Else
' we don't know what the type is; return the type name.
Stringify = TypeName(obj)
End If
End Function
Obviously if the collection always involves classes that you own, the better solution is to have each object know how to represent itself as a String value.
But, having user classes expose a ToString method on their default interface isn't ideal: since we don't know what type we're getting from the collection, all we have is Object and a late-bound call - and no compile-time guarantee that the class implements a ToString method, and no compiler warning if we try to invoke, say, ToStrnig.
The solution is to not put ToString on the classes' default interface, and formalize the behavior with some IString class module, which might look like this:
Option Explicit
Public Function ToString() As String
End Funtion
Yup, that's the whole class.
Now the user classes that need to be representable as strings, can do this:
Option Explicit
Implements IString
Private Function IString_ToString() As String
' todo: implement the method!
End Function
And now we can have early-bound assurance that the objects have a ToString method:
Dim o As Object
For Each o In MyCollection
If TypeOf o Is IString Then
Dim s As IString
Set s = o 'cast to IString interface
Debug.Print s.ToString
Else
Debug.Print TypeName(o)
End If
Next
At the end of the day there's no magic, regardless of what language you're using.

Something like that does not exist in VBA there are only the Type conversion functions like CStr() to convert eg. an Integer into a String.
If you eg need to convert a Collection into an Array you will need to use a function for that.
But what if I don't know what kind of object will be inside my LList collection
Then you will need to determine which object it is (you would probably expect eg 5 different possible objects) and do something like a Select Case for each different object type to convert this to a String.

Related

How to obtain data type directly? [duplicate]

Is there a way to determine the Object type, when passing a reference to a function?
I'm using a security permissions function, which determines if the user has permission to view/edit the Form passed to it by reference. I'd like to expand this to include reports as well.
To keep the function generic, I'd like to pass a ref for either a Form or a Report as an Object, eg:
function gfSecurity_Permission(obj as Object)
However, I'd need to determine the type of the object within the function.
Does anyone know of a way to do that?
MTIA
Take a look at
typeOf and typeName
Generic object variables (that is, variables you declare as Object)
can hold objects from any class. When using variables of type Object,
you may need to take different actions based on the class of the
object; for example, some objects might not support a particular
property or method. Visual Basic provides two means of determining
which type of object is stored in an object variable: the TypeName
function and the TypeOf...Is operator.
TypeName and TypeOf…Is
The
TypeName function returns a string and is the best choice when you
need to store or display the class name of an object, as shown in the
following code fragment:
Dim Ctrl As Control = New TextBox
MsgBox(TypeName(Ctrl))
The TypeOf...Is operator is the best choice for testing an object's
type, because it is much faster than an equivalent string comparison
using TypeName. The following code fragment uses TypeOf...Is within an
If...Then...Else statement:
If TypeOf Ctrl Is Button Then
MsgBox("The control is a button.")
End If
Simplest way to determine the access type in access is to do an object lookup within the Access' system tables.
Here would be the lookup:
DLookup("Type","MSysObjects","NAME = '" & strObject & "'")
strObject is the name of the object within Access
The result is one of the number below OR NULL if the object does not exist in Access
1 = Access Table
4 = OBDB-Linked Table / View
5 = Access Query
6 = Attached (Linked) File (such as Excel, another Access Table or query, text file, etc.)
-32768 = Access Form
-32764 = Access Report
-32761 = Access Module
so, the dlookup would provide "-32768" for a Form or "-32764" for a Report
Hope that helps

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

Excel VBA - How to convert an Object into a String

I like to convert an Object into a string. I found this topic but I can not get it to work in Excel 2010 ("Cast.xxx", "CArray()" and "Array()" seems to be unknown to Excel?)
I know how to convert a variant e.g. "toString = CStr(x)" but not how to do it the best way for an object
Function toString(ByVal x As Variant) As String
If TypeOf x Is Object Then
toString = ???
Else
toString = CStr(x)
End If
End Function
any suggestions?
Are you referring to the IFormattable interface. For example in VB.NET?
Excel VBA isn't a language that will provide you with a ToString method since it doesn't support inheritance.
In your example, you are simply converting whatever the variant is to a string, but an object in VBA wont support this directly.
If you have an external COM object, this would need to be exposed in the interface to access this method to call it from VBA.
For your own classes, you could implement your own IFormattable interface for your objects, but you would have assign to that interface before being able to call the objects ToString method - possible with a helper method. You could of course just supply a ToString method for every class which will through the class interface, but if you want to include this functionality for all objects, then an interface is probably the way to go.
Hope that helps.

Use ObjPtr(Me) to return the Name of a Custom Class Instance?

I understand that ObjPtr will return the address of an object in memory and that it points to a structure called IUNKNOWN and that there is some kind of Interface definition encoded in that to expose the Object structure, but I couldn't figure out how to determine the interfaces for a VBA Custom Class Object and how to use that to return the Name property of an Object.
It's more "nice to have" than essential, but I just want to know the name of an object instance at run time so that I can include it in my trace messages.
Can anyone explain how to do this or, better yet direct me to a reference so I can figure it out?
EDIT
To re-state my aim:
To make a custom class objects that is able to figure out the name of its particular instance.
For example
Dim oObject1 as Class1, oObject2 as Class1
Set oObject1 = New Class1
Set oObject2 = New Class1
Debug.Print oObject1.instanceName & " " & oObject2.instanceName
In the immediate window:
oObject1 oObject2
Is this possible in VBA?
If VBA runtime has a Symbol Table - since it is interpretive I think maybe it does - and I had a way of exposing it, then I could make a Property Get procedure to access the symbol Table and search on the Address - ObjPtr(Me) - to return the semantic name of the instance of the class.
I'm pretty sure this is a dumb question but, hopefully, the process of realising its a dumb question is helpful to my understanding.
Example of a Symbol Table
Address Type Name
00000020 a T_BIT
00000040 a F_BIT
00000080 a I_BIT
20000004 t irqvec
20000008 t fiqvec
2000000c t InitReset
20000018 T _main
20000024 t End
Take NO for an answer. It's not possible to return an instance name as a String literal in VBA.
I still don't understand the reason you may want to do that... Anyway
The easiest way to know each instance code name would be to create a property for a class that stores the actual name. This would only expose the name as a String property and not an actual reference to the object - it already has a reference - itself!
So create a class module
Class1
Option Explicit
Public MyName as String
and in Module1 all it takes is
Option Explicit
Sub Main()
Dim c As Class1
Set c = New Class1
c.MyName = "c"
Debug.Print c.MyName
End Sub
And there you go :)
Another way would be to create a Dictionary to store both KEY/VALUE pairs.
Sub Main()
Dim c As Class1
Set c = New Class1
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
dict.Add "c", c
Debug.Print dict.Exists("c")
End Sub
Now, it's possible to actually do what you want but it would be a really ugly way. Here's how as I am not going to demonstrate.
You would create an instance of a custom class. Using ObjPtr you can get it's reference in memory. Then you would need a mechanism that scans your module code line by line and finds names of all variables you've dimensioned. Once you retrieve a list of all variables you would need a mechanism which tries to create an instance of the same type (class). Once you get past that point you could try to myNewObj = c ("c" would be the obj instance) programmatically. If that succeed then you would do ObjPt for both and match their addresses in memory - you get a match you know the variable name. Grree please do not do it that way :P
TypeName(obj) will return the Type of any variable in VBA:
Dim c as Class1
set c = new Class1
Debug.print TypeName(c) '==> "Class1"
FYI, I've also historically wanted to access the symbol table also. The idea was to get local variables from the previous scope by name. In that way you could make string interpolation:
a = "World"
Debug.Print StringInterp("Hello ${a}")
https://github.com/sancarn/VBA-STD-Library/blob/master/docs/VBAMemoryAnalysis.txt
https://github.com/sancarn/VBA-STD-Library/blob/master/docs/VBAMemoryAnalysis2.txt
No luck making a general function yet.

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.

Resources