Proper usage of array as field member in VB 2005 - vb.net

I normally use C# and I'm attempting to convert a qbasic programmer to the joys of object oriented programming by easing him into VB 2005.
Below is a extremely simplified version of what I'm trying to accomplish. It successfully compiles, but all members in the array of card objects are set to "Nothing". The test line throws a NullReferenceException. What am I doing wrong?
Sub Main()
Dim deck1 As New Deck
Console.WriteLine("Test: " & deck1.cards(2).face)
End Sub
Class Card
Public face As String
Sub New()
face = "Blank"
End Sub
End Class
Class Deck
Public cards(51) As Card
End Class

Yes, when you create an array in .NET, every element of the array is set to the default value of the element type - which is null/Nothing for classes.
You need to populate the array before you use it (or expect it to be full of null references).
Note that this would have behaved exactly the same way in C#.
EDIT: As no-one's actually posted population code that would work yet, here it is:
Class Deck
Public cards(51) As Card
Public Sub New()
For i As Integer = 0 To cards.Length-1
cards(i) = New Card()
Next
End Sub
End Class

You need to do some sort of
For Each currentItem As String in Me.face
currentItem = "Blank"
End
Apologies if the syntax of the for-each is off, I'm a C# guy normally. But the basic issue is that you haven't initialized each element of the array.

Related

VBA Class with Collection of itself

I'm trying to create a class with a Collection in it that will hold other CASN (kind of like a linked list), I'm not sure if my instantiation of the class is correct. But every time I try to run my code below, I get the error
Object variable or With block not set
CODE BEING RUN:
If (Numbers.count > 0) Then
Dim num As CASN
For Each num In Numbers
If (num.DuplicateOf.count > 0) Then 'ERROR HERE
Debug.Print "Added " & num.REF_PO & " to list"
ListBox1.AddItem num.REF_PO
End If
Next num
End If
CLASS - CASN:
Private pWeek As String
Private pVendorName As String
Private pVendorID As String
Private pError_NUM As String
Private pREF_PO As Variant
Private pASN_INV_NUM As Variant
Private pDOC_TYPE As String
Private pERROR_TEXT As String
Private pAddressxl As Range
Private pDuplicateOf As Collection
'''''''''''''''' Instantiation of String, Long, Range etc.
'''''''''''''''' Which I know is working fine
''''''''''''''''''''''
' DuplicateOf Property
''''''''''''''''''''''
Public Property Get DuplicateOf() As Collection
Set DuplicateOf = pDuplicateOf
End Property
Public Property Let DuplicateOf(value As Collection)
Set pDuplicateOf = value
End Property
''''' What I believe may be the cause
Basically what I've done is created two Collections of class CASN and I'm trying to compare the two and see if there are any matching values related to the variable .REF_PO and if there is a match I want to add it to the cthisWeek's collection of class CASN in the DuplicateOf collection of that class.
Hopefully this make sense... I know all my code is working great up to this point of comparing the two CASN Collection's. I've thoroughly tested everything and tried a few different approaches and can't seem to find the solution
EDIT:
I found the error to my first issue but now a new issue has appeared...
This would be a relatively simple fix to your Get method:
Public Property Get DuplicateOf() As Collection
If pDuplicateOf Is Nothing Then Set pDuplicateOf = New Collection
Set DuplicateOf = pDuplicateOf
End Property
EDIT: To address your question - "So when creating a class, do I want to initialize all values to either Nothing or Null? Should I have a Class_Terminate as well?"
The answer would be "it depends" - typically there's no need to set all your class properties to some specific value: most of the non-object ones will already have the default value for their specific variable type. You just have to be aware of the impact of having unset variables - mostly when these are object-types.
Whether you need a Class_Terminate would depend on whether your class instances need to perform any "cleanup" (eg. close any open file handles or DB connections) before they get destroyed.

VBA - Class property with reserved keyword as the name

In VBA, is there any known mechanism to fool the compiler into allowing the use of reserved keywords as names for class properties? For example, I would like to create a property called Select in one of my class modules. However, the compiler flags my declaration as an error. Below is the syntax I used:
Public Property Get Select() As Database.SQLStatements
End Property
Database is my VBA project name and SQLStatements is one of the class modules I created. Also, I'm running the code in MS Access 2010.
You can do that and use any keyword/reserved word in your VBA. But then it will make your code a lot messy and very hard to read/debug/maintain.
If you have a bool property named If in your class you will end up with something like this If .If  Then, well, good luck reading that. Also code maintenance like Find/Replace/rename etc. will need extra caution and more work.
Anyhow, if you are willing to go through all this pain, here is how you do it.
After the keywords/reserved words add a invisible blank space using ALT+0160 and that's it. VBA will consider it a perfectly legal name. e.g. If .
Also, you will have to either use intellisense for using these propertynames or manually type the altcode everywhere. That's extra typing.
clsTest
Option Explicit
Private m_sSelect As String
Private m_bIF As Boolean
Public Property Get Select () As String '~~> Select () is actually typed as SelectALT+0160()
Select  = m_sSelect
End Property
Public Property Let Select (ByVal sNewValue As String)
m_sSelect = sNewValue
End Property
Public Property Get If () As Boolean
If  = m_bIF
End Property
Public Property Let If (ByVal bNewValue As Boolean)
m_bIF = bNewValue
End Property
Test Module
Option Explicit
Sub demo()
Dim objTestClass As clsTest
Set objTestClass = New clsTest
With objTestClass
.Select  = "It works. But it will, for sure, create readibility/maintenance issues."
.If  = False
End With
MsgBox objTestClass.Select 
'/ See how hard it will to read/debug this sort of code
With objTestClass
If .If  Then '~~> This line here :)
MsgBox "If prop value is TRUE"
Else
MsgBox "If prop value is FALSE"
End If
End With
End Sub
ALT+0160 <> Space

Getting 'SubScript Out of Range' Error in VBA when calling class Method

I'm going to try to lay out this issue in as much detail as possible. Apparently I can't post images, as I am a New Member...So I will try to describe my situation as good as I possibly can.
So, I am working with a custom class called "Shifts". I have this class declared and set up in a Class Module in VBA. I declare an instance of the "Shifts" class inside a normal Module and call it "Shift".
Dim Shift As New Shifts
My "Shifts" class has 4 variables (String Arrays):
Private ShiftMembers() As String
Private ShiftCallSigns() As String
Private ShiftAssignments() As String
Private ShiftStatuses() As String
I have written a Sub within the class called "Clear" to clear the data in these variables (via ReDim):
Public Sub Clear()
ReDim ShiftMembers(-1) As String
ReDim ShiftCallSigns(-1) As String
ReDim ShiftAssignments(-1) As String
ReDim ShiftStatuses(-1) As String
End Sub
Now, when I call the Clear Sub of the "Shifts" class (declared as "Shift"):
Shift.Clear 'This is called from within the Module.
i get Subscript out of range (Error 9). My class is declared at the very top of the module, outside of any methods or functions. The Clear() sub within my class is declared Public. I don't understand why I can't seem to access my Clear Sub properly.
Any help would be greatly appreciated!
-Rob
I don't know if you can use ReDim that way. From the documentation, the subscripts argument is bound by the Option Base statement:
subscripts
Required
Dimensions of an array variable; up to 60 multiple dimensions may be declared. The subscripts argument uses the following syntax: [lower To] upper [,[lower To] upper] . . . When not explicitly stated in lower, the lower bound of an array is controlled by the Option Base statement. The lower bound is zero if no Option Base statement is present.
The Option Base statement must be either 1 or 0, and -1 is out of bounds, so the error makes sense.
In order to clear your arrays use the Erase command instead of ReDim.
I put this in my class module (I use the _Initialize procedure just to make one of the array's non-empty, in order to verify that Erase did its job):
Private ShiftMembers() As String
Private ShiftCallSigns() As String
Private ShiftAssignments() As String
Private ShiftStatuses() As String
Sub Class_Initialize()
ReDim ShiftMembers(1 To 2)
End Sub
Public Sub Clear()
Erase ShiftMembers
Erase ShiftCallSigns
Erase ShiftAssignments
Erase ShiftStatuses
End Sub
I use the following to test:
Sub t()
Dim s As New Shift
s.Clear
End Sub

Restrict type in a Collection inside a class module

I have a collection inside a class module. I'd like to restrict the object type that is "addable" to this collection, i.e. collection should only ever accept objects of one given type and nothing else.
Is there any way to enforce the type of objects added to a collection?
From what I can tell, there is no built-in way to do this. Is the solution then to make this collection private, and build wrapper functions for the methods usually accessible for Collections, i.e. Add, Remove, Item, and Count?
I kinda hate having to write 3 wrapper functions that add no functionality, just to be able to add some type enforcement to the Add method. But if that's the only way, then that's the only way.
There is no way to avoid wrapper functions. That's just inherent in the "specialization through containment/delegation" model that VBA uses.
You can build a "custom collection class", though. You can even make it iterable with For...Each, but that requires leaving the VBA IDE and editing source files directly.
First, see the "Creating Your Own Collection Classes" section of the old Visual Basic 6.0 Programmer's Guide:
http://msdn.microsoft.com/en-us/library/aa262340(v=VS.60).aspx
There is also an answer here on stackoverflow that describes the same thing:
vb6 equivalent to list<someclass>
However, those are written for VB6, not VBA. In VBA you can't do the "procedure attributes" part in the IDE. You have to export the class module as text and add it in with a text editor. Dick Kusleika's website Daily Dose of Excel (Dick is a regular stackoverflow contributer as you probably know) has a post from Rob van Gelder showing how to do this:
http://www.dailydoseofexcel.com/archives/2010/07/04/custom-collection-class/
In your case, going to all that trouble - each "custom collection" class needs its own module - might not be worth it. (If you only have one use for this and it is buried in another class, you might find that you don't want to expose all of the functionality of Collection anyway.)
This is what I did. I liked Rob van Gelder's example, as pointed to by #jtolle, but why should I be content with making a "custom collection class" that will only accept one specific object type (e.g. People), forever? As #jtolle points out, this is super annoying.
Instead, I generalized the idea and made a new class called UniformCollection that can contain any data type -- as long as all items are of the same type in any given instance of UniformCollection.
I added a private Variant that is a placeholder for the data type that a given instance of UniformCollection can contain.
Private mvarPrototype As Variant
After making an instance of UniformCollection and before using it, it must be initialized by specifying which data type it will contain.
Public Sub Initialize(Prototype As Variant)
If VarType(Prototype) = vbEmpty Or VarType(Prototype) = vbNull Then
Err.Raise Number:=ERR__CANT_INITIALIZE, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__CANT_INITIALIZE) & _
TypeName(Prototype)
End If
' Clear anything already in collection.
Set mUniformCollection = New Collection
If VarType(Prototype) = vbObject Or VarType(Prototype) = vbDataObject Then
' It's an object. Need Set.
Set mvarPrototype = Prototype
Else
' It's not an object.
mvarPrototype = Prototype
End If
' Collection will now accept only items of same type as Prototype.
End Sub
The Add method will then only accept new items that are of the same type as Prototype (be it an object or a primitive variable... haven't tested with UDTs yet).
Public Sub Add(NewItem As Variant)
If VarType(mvarPrototype) = vbEmpty Then
Err.Raise Number:=ERR__NOT_INITIALIZED, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__NOT_INITIALIZED)
ElseIf Not TypeName(NewItem) = TypeName(mvarPrototype) Then
Err.Raise Number:=ERR__INVALID_TYPE, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__INVALID_TYPE) & _
TypeName(mvarPrototype) & "."
Else
' Object is of correct type. Accept it.
' Do nothing.
End If
mUniformCollection.Add NewItem
End Sub
The rest is pretty much the same as in the example (plus some error handling). Too bad RvG didn't go the whole way! Even more too bad that Microsoft didn't include this kind of thing as a built-in feature...
I did almost the same code of Jean-François Corbett, but I adapted because for some reason wasn't working.
Option Explicit
Public pParametro As String
Private pColecao As New Collection
Public Sub Inicializar(ByVal parametro As String)
pParametro = parametro
End Sub
Public Sub Add(NewItem As Object)
If TypeName(NewItem) <> pParametro Then
MsgBox "Classe do objeto não é compatível à coleção"
Else
pColecao.Add NewItem
End If
End Sub
Public Property Get Count() As Long
Count = pColecao.Count
End Property
Public Property Get Item(NameOrNumber As Variant) As Variant
Set Item = pColecao(NameOrNumber)
End Property
Sub Remove(NameOrNumber As Variant)
pColecao.Remove NameOrNumber
End Sub
Then, when i want to create an instance from CCollection I make like the code bellow:
Set pFornecedores = New CCollection
pFornecedores.Inicializar ("CEmpresa")
Where CEmpresa is the class type from the object I want
Yes. The solution is to make your collection private and then make public wrapper functions to add, remove, getitem and count etc.
It may seem like hassle to write the additional code but it is a more robust solution to encapsulate the collection like this.

Dynamic properties for classes in visual basic

I am a vb.net newbie, so please bear with me. Is it possible to create properties (or attributes) for a class in visual basic (I am using Visual Basic 2005) ? All web searches for metaprogramming led me nowhere. Here is an example to clarify what I mean.
public class GenericProps
public sub new()
' ???
end sub
public sub addProp(byval propname as string)
' ???
end sub
end class
sub main()
dim gp as GenericProps = New GenericProps()
gp.addProp("foo")
gp.foo = "Bar" ' we can assume the type of the property as string for now
console.writeln("New property = " & gp.foo)
end sub
So is it possible to define the function addProp ?
Thanks!
Amit
It's not possible to modify a class at runtime with new properties1. VB.Net is a static language in the sense that it cannot modify it's defined classes at runtime. You can simulate what you're looking for though with a property bag.
Class Foo
Private _map as New Dictionary(Of String, Object)
Public Sub AddProperty(name as String, value as Object)
_map(name) = value
End Sub
Public Function GetProperty(name as String) as Object
return _map(name)
End Function
End Class
It doesn't allow direct access in the form of myFoo.Bar but you can call myFoo.GetProperty("Bar").
1 I believe it may be possible with the profiling APIs but it's likely not what you're looking for.
Came across this wondering the same thing for Visual Basic 2008.
The property bag will do me for now until I can migrate to Visual Basic 2010:
http://blogs.msdn.com/vbteam/archive/2010/01/20/fun-with-dynamic-objects-doug-rothaus.aspx
No - that's not possible. You'd need a Ruby like "method_missing" to handle the unknown .Foo call. I believe C# 4 promises to offer something along these lines.