Can't use Type in VB - vb.net

Is there anything wrong with the following code ? It failed on Form_Load() line , and complains about it.
Private Sub Form_Load()
Type Human
Name As String
End Type
Dim stu As Student
With Human:
.Name = "Someone"
End With
Debug.Print ("Name: " & stu.Name)
End Sub

You have two options:
1
Create a new class
Private Class Human
Public Name As String
End Class
(Obviously it would be better to wrap the Name in a public property, but for simplicity, exposing it as a public variable is easier.)
2
Create a new struct:
Structure Human
Dim Name As String
End Structure
Note
It should be noted that both of these options must be done outside of the function, not within Form_Load function

The keyword is no longer Type; it is Structure now. Type was used in VB6 and earlier, but not in .NET.

Related

VBA List of Custom Datastructures

One of the main problems in VBA are custom data structures and lists.
I have a loop which generates with each iteration multiple values.
So as an example:
Each loop iteration generates a string "name" an integer "price" and an integer "value".
In C# for example I'd create a class which can hold these three values and with each loop iteration I add the class object to a list.
How can I do the same thing in VBA if I want to store multiple sets of data when not knowing how many iterations the loop will have (I cant create an array with a fixed size)
Any ideas?
The approach I use very frequently is to use a class and a collection. I also tend to use an interface model to make things more flexible. An example would look something like this:
Class Module IFoo
Option Explicit
Public Sub Create(ByVal Name as String, ByVal ID as String)
End Property
Public Property Get Name() as String
End Property
Public Property Get ID() as String
End Property
This enforces the pattern I want for my Foo class.
Class Module Foo
Option Explicit
Private Type TFoo
Name as String
ID as String
End Type
Private this as TFoo
Implements IFoo
Private Sub IFoo_Create(ByVal Name as String, ByVal ID as String)
this.Name = Name
this.ID = Name
End Sub
Private Property Get IFoo_Name() as String
IFoo_Name = this.Name
End Property
Private Property Get IFoo_ID() as String
IFoo_ID = this.ID
End Property
We get intellisense from the Private Type TFoo : Private this as TFoo where the former defines the properties of our container, the latter exposes them privately. The Implements IFoo allows us to selectively expose properties. This also allows you to iterate a Collection using an IFoo instead of a Foo. Sounds pointless until you have an Employee and a Manager where IFoo_BaseRate changes depending on employee type.
Then in practice, we have something like this:
Code Module Bar
Public Sub CollectFoo()
Dim AllTheFoos as Collection
Set AllTheFoos = New Collection
While SomeCondition
Dim Foo as IFoo
Set Foo = New Foo
Foo.Create(Name, ID)
AllTheFoos.Add Foo
Loop
For each Foo in AllTheFoos
Debug.Print Foo.Name, Foo.ID
Next
End Sub
While the pattern is super simple once you learn it, you'll find that it is incredibly powerful and scalable if implemented properly. It also can dramatically reduce the amount of copypasta that exists within your code (and thus reduce debug time).
You can use classes in VBA as well as in C#: Class Module Step by Step or A Quick Guide to the VBA Class Module
And to to the problem with the array: you can create an array with dynamic size like this
'Method 1 : Using Dim
Dim arr1() 'Without Size
'somewhere later -> increase a size to 1
redim arr1(UBound(arr1) + 1)
You could create a class - but if all you want to do is hold three bits of data together, I would define a Type structure. It needs to be defines at the top of an ordinary module, after option explicit and before any subs
Type MyType
Name As String
Price As Integer
Value As Integer
End Type
And then to use it
Sub test()
Dim t As MyType
t.Name = "fred"
t.Price = 12
t.Value = 3
End Sub

Compile error: Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions

I'm struggling with a little bit of VBa and Excel. I need to create a structure in VBa, which is a Type. The problem I have is, I get an error message when I try to execute the code! I feel I need to explain how I have arrived where I am in case I've made an error.
I have read that to create a type, it needs to be made public. As such I created a new Class (under Class Modules). In Class1, I wrote
Public Type SpiderKeyPair
IsComplete As Boolean
Key As String
End Type
And within ThisWorkbook I have the following
Public Sub Test()
Dim skp As SpiderKeyPair
skp.IsComplete = True
skp.Key = "abc"
End Sub
There is no other code. The issue I have is I get the error message
Cannot define a public user-defined type within an object module
If I make the type private I don't get that error, but of course I can't access any of the type's properties (to use .NET terminology).
If I move the code from Class1 into Module1 it works, but, I need to store this into a collection and this is where it's gone wrong and where I am stuck.
I've updated my Test to
Private m_spiderKeys As Collection
Public Sub Test()
Dim sKey As SpiderKeyPair
sKey.IsComplete = False
sKey.Key = "abc"
m_spiderKeys.Add (sKey) 'FAILS HERE
End Sub
Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions
I have looked into this but I don't understand what it is I need to do... How do I add the SpiderKeyPair to my collection?
Had the exact same problem and wasted a lot of time because the error information is misleading. I miss having List<>.
In Visual Basic you can't really treat everything as an object. You have Structures and Classes which have a difference at memory allocation: https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/data-types/structures-and-classes
A Type is a structure (so are Arrays), so you if you want a "List" of them you better use an Array and all that comes with it.
If you want to use a Collection to store a "List", you need to create a Class for the object to be handled.
Not amazing... but it is what the language has available.
You seem to be missing basics of OOP or mistaking VBA and VB.NET. Or I do not understand what are you trying to do. Anyhow, try the following:
In a module write this:
Option Explicit
Public Sub Test()
Dim skpObj As SpiderKeyPair
Dim m_spiderKeys As New Collection
Dim lngCounter As Long
For lngCounter = 1 To 4
Set skpObj = New SpiderKeyPair
skpObj.Key = "test" & lngCounter
skpObj.IsComplete = CBool(lngCounter Mod 2 = 0)
m_spiderKeys.Add skpObj
Next lngCounter
For Each skpObj In m_spiderKeys
Debug.Print "-----------------"
Debug.Print skpObj.IsComplete
Debug.Print skpObj.Key
Debug.Print "-----------------"
Next skpObj
End Sub
In a class, named SpiderKeyPair write this:
Option Explicit
Private m_bIsComplete As Boolean
Private m_sKey As String
Public Property Get IsComplete() As Boolean
IsComplete = m_bIsComplete
End Property
Public Property Get Key() As String
Key = m_sKey
End Property
Public Property Let Key(ByVal sNewValue As String)
m_sKey = sNewValue
End Property
Public Property Let IsComplete(ByVal bNewValue As Boolean)
m_bIsComplete = bNewValue
End Property
When you run the Test Sub in the module you get this:
Falsch
test1
-----------------
-----------------
Wahr
test2
Pay attention to how you initialize new objects. It happens with the word New. Collections are objects and should be initialized as well with New.

Can UDT's be used as method parameters in any way?

For years I've been avoiding the use of Public Type UDT's in VBA, because they're hard to pass around and I never really bothered trying to understand why.. until now - it was simply easier to just create a class module and work with actual objects instead.
But recently I gave it a shot, and once I figured they had to be passed ByRef (as an array would), things started to look like I could start using them.
So I defined a Public Type in a standard module, got this compile error:
So I moved the Public Type into a class module, made the class PublicNotCreatable, and then got this compile error:
Here's some code to reproduce the compile error.
Class module "Something":
Option Explicit
' cannot define a public user-defined type within an object module
Public Type TSomething
Foo As Integer
End Type
Public Function Create(ByRef info As TSomething) As Something
End Function
If you move the definition of TSomething to a standard module, you'll get the other compiler error, telling you that the public UDT must be defined in a public object module (i.e. a class module)... which takes you back to square one.
So if you cannot define a Public Type in a class module, why would the compiler throw a fit and even mention "public user defined types defined in public object modules" if such a thing can't legally exist?
Did it work in VB6 and the compiler message is a remnant of that version? Or is the reason somewhere in how COM works? Is it just me or the two error messages are contradicting each other? Or there's something I'm not understanding?
Obviously I'm misusing/abusing UDT's here. So what are they supposed to be used for, if not for passing a "record" to some method?
From standard module it works without any error. Following code threw no error.
Public Type TEST_TYPE
Prop1 As String
End Type
Public Function fTest(ByRef param1 As TEST_TYPE) As String
param1.Prop1 = "Hello from function"
End Function
Public Sub sTest(ByRef param1 As TEST_TYPE)
param1.Prop1 = "Hello from Sub"
End Sub
Public Sub caller()
Dim p As TEST_TYPE
'/Call Sub
Call sTest(p)
MsgBox p.Prop1
'/Call Function
Call fTest(p)
MsgBox p.Prop1
End Sub
One issue with UDT is about Forward referencing. So this will not compile, apart from that It works perfectly fine with standard modules.
Public Type TEST_TYPE
Prop1 As String
Prop2 As TEST_TYPE2 '/ Fails due to Forward referencing. TEST_TYPE2 should be declared before this UDT.
End Type
Public Type TEST_TYPE2
Prop3 As String
End Type
Edit:
However, the work around to use the UDT in class is Friend
VBA Code for Class
'/ Using UDT in VBA-Class
Private Type TEST_TYPE3
Prop3 As String
End Type
Public Sub caller()
Dim p As TEST_TYPE3
p.Prop3 = "Hello from Class"
Call testClassUDT(p)
End Sub
Friend Sub testClassUDT(p As TEST_TYPE3)
MsgBox p.Prop3
End Sub
Here's a Type being passed as a parameter to a class method, and being returned by a class method.
First the class SomeClass (doesn't need to be PublicNotCreatable)
Option Explicit
Sub test(foo As TFooBar)
Dim s As String
s = foo.foo
End Sub
Function ReturnTFoo() As TFooBar
ReturnTFoo.bar = "bar"
ReturnTFoo.foo = " bar"
End Function
And the Module:
Option Explicit
Public Type TFooBar
foo As String
bar As String
End Type
Sub test()
Dim c As SomeClass
Set c = New SomeClass
Dim t1 As TFooBar
Dim t2 As TFooBar
t1.bar = "bar"
t1.foo = "Foo"
c.test t1
t2 = c.ReturnTFoo
End Sub

User-defined constant types in VBA

I would like to define a custom data type in VBA and use it to create some user-defined constants. For example:
Public Type Person
Name as String
Age as Integer
End Type
Public Sub CreatePerson
Dim singer as Person
singer.Name = "John"
singer.Age = 37
End Sub
I would like to make singer to be constant which will be visible in other subroutines/functions in the same module. Is it possible? If not, how is the best way to store constant values which are connected to each other (like in the example above where Name is connected to Age)?
Here is how I would do it.
Code in a module:
Option Explicit
Public Type Person
Name As String
Age As Integer
End Type
Private cvsinger As Person
Public Function singer() As Person
Static ConstantHasBeenSet As Boolean
If Not ConstantHasBeenSet Then
cvsinger.Name = "John"
cvsinger.Age = 37
End If
singer = cvsinger
End Function
Public Sub CreatePerson()
Debug.Print singer.Name, singer.Age
End Sub
This is a fairly trivial case, but when the code to find the default values takes longer to process the use of ConstantHasBeenSet becomes worthwhile
(Note that I would also use a similar technique for setting default values for properties in a class)

How do I determine if a class member exists?

I have a public subroutine that is called by many classes. However, I now need to do something in that subroutine that only pertains to a small number of the classes that call it. So instead of going back and adding the property to all of the existing classes, I would like to simply check to see see if that class has that property and if so, then do something with it. But I can't seem to figure out how to simply check for the existence of the member without getting an error.
For example:
Public Class_1
Public a1 as string = ""
Public Sub New()
' when a button is clicked call subroutine "check()"
End Sub
End Class
Public Class_2
Public a1 as string = ""
Public a2 as integer = 0
Public Sub New()
' when a button is clicked call subroutine "check()"
End Sub
End Class
Public Class whatever
Public Sub check(sender as object)
If sender.a2 = 0 then
' do something
End if
End Sub
End Class
I have tried such things as
If not(sender.a2 is nothing) then
If isnothing(sender.a2) then
But I can't get past the fact that I get an error simply by using "sender.a2" since a2 is not always a member of the sender.
How can I check to see if a2 is a member of the sending class without using "sender.a2"?
If you want to see that a field exists you need this:
Dim fi As FieldInfo = sender.GetType().GetField("nameOfFieldHere")
If fi IsNot Nothing
'field exists now get the value
Dim o As Object = fi.GetValue(sender)
End If
Take a look at the documentation Type.GetField Method (String)
There are also overloads available too.
You can test that the Object you are referencing is of a certain type before attempting to use it. Once you've determined it's the right type, you can safely cast to it and then use the right properties like so:
If (TypeOf sender Is Class_2) Then
Dim castObj As Class_2 = CType(sender, Class_2)
'We can now access castObj.a2
End If
If there are multiple classes with the property, it would be sensible to create an Interface which states they have the a2 property and have them all implement it. You can then test their type against the new Interface instead of Class_2.
An alternative option is to use class inheritance to implement a default public method for all classes, and override it in your special Class_2 case for your subclasses.