workaround to VB.Net 2003 System.Collections.Generic? - vb.net

I'm working in an old web application (vb.net 2003) and I'm trying to use a generic list of a custom class.
I realized that System.Collections.Generic was introduced in .Net 2 according to link
Is there any alternative to the list? For instance an array of class?
Let's say I have the following class definition:
Public Class Box
Public x As Integer
Public y As Integer
End Class
And an array of Class Box:
Dim BoxList() As Box
BoxList(0).x = 1
BoxList(0).y = 1
BoxList(1).x = 2
BoxList(2).y = 2
But I'm getting an error when BoxList(0).x = 1 error: Object reference not set to an instance of an object
I'm just guessing here.

Use ArrayList, like this:
Dim BoxList As New ArrayList
Dim box = New Box()
box.x = 1
box.y = 2
BoxList.Add(box)
Note: It is recommended that you add a constructor to the Box class that will accept the x and y values, like this:
Public Class Box
Public x As Integer
Public y As Integer
Public Sub New(ByVal _x As Integer, ByVal _y As Integer)
x = _x
y = _y
End Sub
End Class
Now you can shorten your ArrayList code to this:
Dim BoxList As New ArrayList
BoxList.Add(New Box(1, 2))
To use the values in the ArrayList you will need to un-box (pun not intended) the value out of the ArrayList, like this:
For Each box In BoxList
' Use x value, like this
CType(box, Box).x
Next
OR (as Meta-Knight suggested)
For Each box As Box In BoxList
' Now box is typed as Box and not object, so just use it
box.x
Next

You can create your own custom collection class - this is what we had to do back before generics. This article from MSDN gives you the specifics:
''' Code copied directly from article
Public Class WidgetCollection
Inherits System.Collections.CollectionBase
Public Sub Add(ByVal awidget As Widget)
List.Add(aWidget)
End Sub
Public Sub Remove(ByVal index as Integer)
If index > Count - 1 Or index < 0 Then
System.Windows.Forms.MessageBox.Show("Index not valid!")
Else
List.RemoveAt(index)
End If
End Sub
Public ReadOnly Property Item(ByVal index as Integer) As Widget
Get
Return CType(List.Item(index), Widget)
End Get
End Property
End Class

Related

VB.NET Object with custom name to store property?

I'm not familiar with the type of structure or whatever I need to use to achieve this, but I know that there is one.
I'm trying to make it so that I can reference things something like this:
racerlist(x).compatibilityArr.john.CleatScore
instead of what I have to do now:
racerlist(x).compatibilityArr.CleatScoreArr(y).name/.score
So essentially, I want to add items to the compatibilityarr (will probably have to change to a list which is fine) and be able to reference the racer as their own name, instead of by using an index.
This is one way to build a solution that fits your needs as described above. It requires an embedded class that is built as a List(Of T) where we overload the property to accept a string rather than the integer.
Public Class Foo
Public Property compatibilityArr As New Members
End Class
Public Class Members : Inherits List(Of Member)
Public Overloads ReadOnly Property Item(name As String) As Member
Get
Return Me.Where(Function(i) i.Name = name).FirstOrDefault
End Get
End Property
End Class
Public Class Member
Public Property Name As String
Public Property CleatScore As Integer
End Class
Then to use it:
Public Class Form1
Dim f As New Foo
Private Sub loads() Handles Me.Load
Dim member As New Member With {.Name = "John", .CleatScore = 10}
f.compatibilityArr.Add(member)
MessageBox.Show(f.compatibilityArr.Item("John").CleatScore)
End Sub
End Class
There are other ways to do this, but the simplest is to write a function to search the array by name:
Sub Main1()
Dim racerlist(2) As Racer
racerlist(0) = New Racer With {.Name = "Adam", .CleatScore = "1"}
racerlist(1) = New Racer With {.Name = "Bill", .CleatScore = "2"}
racerlist(2) = New Racer With {.Name = "Charlie", .CleatScore = "3"}
For i As Integer = 0 To racerlist.GetUpperBound(0)
For j As Integer = 0 To racerlist.GetUpperBound(0)
If racerlist(j).Name <> racerlist(i).Name Then
ReDim Preserve racerlist(i).CompatibilityArr(racerlist(i).CompatibilityArr.GetUpperBound(0) + 1)
racerlist(i).CompatibilityArr(racerlist(i).CompatibilityArr.GetUpperBound(0)) = racerlist(j)
End If
Next j
Next i
Dim racerBill As Racer = Racer.FindRacer(racerlist, "Bill")
MsgBox(racerBill.FindCompatibility("Charlie").CleatScore)
End Sub
Class Racer
Property Name As String
Property CleatScore As String
Property CompatibilityArr As Racer()
Sub New()
ReDim CompatibilityArr(-1) 'initialise the array
End Sub
Function FindCompatibility(name As String) As Racer
Return FindRacer(CompatibilityArr, name)
End Function
Shared Function FindRacer(racerlist() As Racer, name As String) As Racer
For i As Integer = 0 To racerlist.GetUpperBound(0)
If racerlist(i).Name = name Then
Return racerlist(i)
End If
Next i
Return Nothing
End Function
End Class
As #Codexer mentioned, I used a dictionary to achieve this.
In my list of Racers (RacerList), I have RacerCompatibility, which I created similar to below:
Public RacerCompatibility As New Dictionary(Of String, Compatibility)
Compatibility is created like:
Public Class Compatibility
Public Cleat As Boolean
Public Skill As Integer
Public Height As Integer
End Class
So now I can access the compatibility of a racer inside the list like:
RacerList(x).RacerCompatibility.Item("John")

Collection class of specific type containing basic features

Every time i use some class e.g Artikel as follows:
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
End Class
For such classes i would like to have collection class. The features i would like to have is like:
--> Add (passing Artikel object)
--> Remove (passing Artikel object)
--> Sort entire collection (based on Position property desc/asc)
--> Compare two Artikels (pass by Artikels and tell by which property has to be compared)
--> Check whether two artikels equals
--> Every added artikel has to be marked by Key (so maybe dictionary)? <key><Artikel>
--> Remove Artikel (passing by Key index)
Could somone from you there tell me or even better provide example of collection class pass those requirments?
EDIT: Startup:
Artikel's collection:
Option Strict On
Public Class Articles
Public Property collection As Dictionary(Of Integer, Artikel)
Sub New()
'Initiate new collection
collection = New Dictionary(Of Integer, Artikel)
End Sub
'Add new Artikel to collection
Public Function AddToCollection(ByVal artikel As Artikel) As Boolean
collection.Add(artikel)
Return True
End Function
'Remove specific Artikel
Public Sub RemoveFromCollectionByArtikel(artikel As Artikel)
If Not IsNothing(collection) Then
collection.Remove(artikel)
End If
End Sub
'Get collection
Public Function GetCollection() As Dictionary(Of Integer, Artikel)
Return collection
End Function
'Sort collection by property position
Public Sub SortByPosition()
collection.Sort()
End Sub
'Remove specific sending keys and then reorder them
Public Sub RemoveAllMarkedAsDeleted(keys As List(Of Integer))
'-- Check whther anything has been marked as deleted
If keys.Count > 0 Then
For Each row In keys
collection.Remove(row)
Next
ReorderKeys()
End If
'Reorder all Artikels in collection
Private Sub ReorderKeys()
Dim newCollection As New Dictionary(Of Integer, Artikel)
Dim index As Integer = 0
For Each collitem In collection
newCollection.Add(index, collitem.Value)
index += 1
Next
collection.Clear()
collection = newCollection
End Sub
End Class
Artikel class (additionally i implemented IComparable to be able to sort)
Option Strict On
Public Class Artikel
Implements IComparable(Of Artikel)
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
Public Function CompareTo(pother As Artikel) As Integer Implements IComparable(Of Artikel).CompareTo 'we can sort because of this
Return String.Compare(Me.Position, pother.Position)
End Function
Public Shared Function FindPredicate(ByVal partikel As Artikel) As Predicate(Of Artikel)
Return Function(partikel2 As Artikel) partikel.ID = partikel2.ID
End Function
Public Shared Function FindPredicateByUserId(ByVal partikel As String) As Predicate(Of Artikel)
Return Function(partikel2 As Artikel) partikel = partikel2.ID
End Function
End Class
Parts of it look good, but I would ultimately do it a bit differently. First, consider overloads on the item class to make them easier to create and default initialization:
Public Class Article
Property ID As Integer = -1
Property Key As String = ""
Property Name As String = ""
Property Position As Integer = -1
Property PubDate As DateTime = DateTime.Minimum
Public Sub New()
End Sub
' whatever minimum data a new item requires
Public Sub New(k As String, n As String)
Key = k
Name = n
End Sub
' full initialization:
Public Sub New(k As String, n As String, pos As Int32,
pubDt As DateTime)
...
End Sub
End Class
I added some properties for variety, and I suspect "Nummer" might be the "Key" mentioned in the OP, but whatever it is, I would add it to the Article class as that name, if it has some importance.
You might need a simple ctor for serialization (???). Some of these will find and use a Private parameterless constructor, but your code will be forced to use one of the overloads in order to provide some minimum level of data when a new one is created.
You probably do not need IComparable. That is typically for more complex comparisons, such as multiple or complex properties. An example is a carton or box:
If (width = Other.Width) AndAlso (height = Other.Height) Then
Return 0
ElseIf (width = Other.Height) AndAlso (height = Other.Width) Then
Return 0
End If
Plus more gyrations to work out which is "less" than the other. One reason you dont need it, is because If Art1.Postion > Art2.Postion is trivial. The other reason in your case, is because a Dictionary cannot be sorted.
Rather than a Dictionary, an internal List would work better for some of the things you describe but still allow you to have it act like a Dictionary to the extent you need it to. For this, I might build it using ICollection<T>:
Public Class ArticleCollection
Implements ICollection(Of Article)
Pressing Enter after that line will add all the required methods including:
Public Sub Add(item As Article) Implements ICollection(Of Article).Add
Public Sub Clear() Implements ICollection(Of Article).Clear
Public Function Contains(item As Article) As Boolean Implements ICollection(Of Article).Contains
Public ReadOnly Property Count As Integer Implements ICollection(Of Article).Count
Public Function Remove(item As Article) As Boolean Implements ICollection(Of Article).Remove
It remains completely up to you how these are implemented. It also doesn't rule out adding methods such as RemoveAt(int32) or RemoveByKey(string) depending on what you need/how it will be used. One of the benefits to ICollection(Of T) is that it includes IEnumerable which will allow use for each loops (once you write the Enumerator): For Each art In Articles
To emulate a dictionary to allow only one item with a specific property value:
Public Class ArticleCollection
Implements ICollection(Of Article)
Private mcol As List(Of Article)
...
Public Sub Add(item As Article) Implements ICollection(Of Article).Add
' check for existing key
If KeyExists(item.Key) = False Then
mcol.Add(item)
End If
End Sub
You can also overload them:
' overload to match Article ctor overload
Public Sub Add(key As String, name As String)
If KeyExists(key) = False Then
' let collection create the new item
' with the minimum required info
mcol.Add(New Article(key, name))
End If
End Sub
If you add an Item Property, you can index the collection ( Articles(3) ):
Property Item(ndx As Int32) As Article
Get
If ndx > 0 AndAlso ndx < mcol.Count Then
Return mcol(ndx)
Else
Return Nothing
End If
End Get
Set(value As Article)
If ndx > 0 AndAlso ndx < mcol.Count Then
mcol(ndx) = value
End If
End Set
End Property
' overload for item by key:
Public Property Item(key As String) As Article
An Add method and an Item Property will be important if the collection will display in the standard NET CollectionEditor.
There are several ways to implement sorting. The easiest is to use linq in the code which uses your collection:
Articles = New ArticleCollection
' add Article items
Dim ArticlesByDate = Articles.OrderBy(Function(s) s.PubDate).ToList()
Where PubDate is one of the Article properties I added. The other way to handle sorting is by the collection class returning a new collection (but it is so simple to do, there is little need for it):
Friend Function GetSortedList(bSortAsc As Boolean) As List(Of Article)
If bSortAsc Then
Return mcol.OrderBy(Function(q) q.PubDate).
ThenBy(Function(j) j.Position).ToList()
Else
Return mcol.OrderByDescending(Function(q) q.PubDate).
ThenByDescending(Function(j) j.Position).ToList()
End If
End Function
Whether it implements ICollection(Of T), inherits from ICollection(Of T) or does work off a Dictionary depends entirely on what this is, how it is used and whatever rules and restrictions there are (including if it will be serialized and how). These are not things we know.
MSDN has an article on Guidelines for Collections which is excellent.
Create your class
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
sub new (_ID as integer, _Nummer as string, _Name as string, _Position as integer)
ID = _ID
Nummer = _Nummer
Name = _Name
Position = _Position
End Sub
End Class
Create another class which holds a private list and add sub routines to it
Public Class ArtikelList
Private _List as new list (of Artikel)
Public sub remove(Key as integer)
Dim obj as Artikel = nothing
for each x as Artikel in _List
if x.ID = Key then
obj = x
exit for
end if
Next
if not isnothing(obj) then
_List.remove(obj)
end if
End sub
Sub Add(obj as Artikel)
Dim alreadyDeclared as boolean = falsse
for each x as Artikel in _List
if x.ID = obj.id then
alreadyDeclared = true
exit for
end if
Next
if not AlreadyDeclared then
_List.add(obj)
Else
'Somehow inform the user of the duplication if need be.
end if
End sub
End Class
Then use your list class.
dim L as new ArtikelList
L.add(new Artikel(1280, "AFKforever!", "Prof.FluffyButton", 96))
L.remove(1280)
I only added one sub routine as an example. I hope it helps but feel free to ask for more example routines.
This can also be done by creating a class which inherits from the list class, exposing all list class functionality but by using this method you are forced to create every subroutine that will be used. This way you only use routines that you created exclusively for the purpose Artikel objects handling.
Check if two Artikels are equal
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
sub new (_ID as integer, _Nummer as string, _Name as string, _Position as integer)
ID = _ID
Nummer = _Nummer
Name = _Name
Position = _Position
End Sub
End Class
Public Overrides Overloads Function Equals(obj As Object) As Boolean
If obj Is Nothing OrElse Not Me.GetType() Is obj.GetType() Then
Return False
else
dim _obj as artikel = obj
if Me.ID = _obj.ID then
Return true
else Return False
End If
End Function
End Class
Use it like:
If x.equals(y) then
'they have the same ID
end if

VBA OOP How can I make subproperties

How can I program my class module so that I can call properties on properties?
I'm not sure I'm using the right terminology, so I will try to clarify. In MsAccess whenever I want to manipulate elements on a form I can reference them using a period to separate each objects. For example, if I wanted to change the value of a text box I can call:
form("formname").txtboxname.value = "new value"
So it's like I have a form object that has a textbox object that has a value object.
How could I achieve this in my own class module.
My specific example is that I have stored an array in a private variable in the class however I cannot simply use a Property GET to return an array. (And I don't want to make it public because the array is programmatically populated) But if I want to iterate I need to know the Ubound and Lbound value of that array.
I would rather avoid having to store the Ubound and Lbound values in their own variable as that seems a waste.
How could I somehow program the class in order to get a ?subclass?
so that if I want the ubound or lbound I could call something like
set x = mycls
debug.? x.pArrayVariable.getLBound
Even the right terminology for what I'm trying to do could get me closer to an answer, I've tried searching for properties and sub properties but I'm not sure that's getting me somewhere.
Example of my class: mycls
Private pArrayVariable() as string
public property get pArrayVariable() as string
'Run Code to Populate array here
Array() = pArray()
end property
Is something called "Collections" what I'm asking about?
So a property can return an object (like a user class) which has its own properties. Example below:
Here is the code for a class called MinMax
Private m_min As Integer
Private m_max As Integer
Public Property Get MinValue() As Integer
MinValue = m_min
End Property
Public Property Let MinValue(ByVal x As Integer)
m_min = x
End Property
Public Property Get MaxValue() As Integer
MaxValue = m_max
End Property
Public Property Let MaxValue(ByVal x As Integer)
m_max = x
End Property
Public Sub SetMinMax(ByVal min_value As Integer, ByVal max_value As Integer)
m_min = min_value
m_max = max_value
End Sub
Private Sub Class_Initialize()
m_min = 0
m_max = 1
End Sub
and here is the code for a class named MyClass. Notice how it exposes a property of type MinMax
Private m_target As MinMax
Private m_name As String
Public Property Get Target() As MinMax
Target = m_target
End Property
Public Property Get Name() As String
Name = m_name
End Property
Private Sub Class_Initialize()
Set m_target = New MinMax
m_name = vbNullString
End Sub
Public Sub SetValues(ByVal a_name As String, ByVal min_value As Integer, ByVal max_value As Integer)
m_name = a_name
m_target.SetMinMax min_value, max_value
End Sub
Now the main code can have a statement like
Public Sub Test()
Dim t As New MyClass
t.SetValues "Mary", 1, 100
Debug.Print t.Target.MinValue, t.Target.MaxValue
End Sub
I still am curious about my original question above, however it came out of a problem of not being able to access the array. Seems I am incorrect.
You can use
Public Property Get ArrayVariable() As String()
Call 'code to populate array
ArrayVariable= pArrayVariable() 'Notice the paren here
End Property
And then to reference the array
debug.? ubound(clsvar.ArrayVariable()) 'Notice paren here too
or
debug.? clsvar.ArrayVariable()(1) 'Notice the parens here too

A class can only be a template for a single object not a template for a collection

I have a simple class List.vb which is the following:
Public Class List
Public fList As List(Of Integer)
Public Sub New()
fList = New List(Of Integer)
fList.Add(1)
fList.Add(2)
fList.Add(3)
fList.Add(4)
fList.Add(5)
End Sub
End Class
The Console application is using this class like the following:
Module Module1
Sub Main()
Dim fObject As List = New List
Dim cnt As Integer = 0
For Each x As Integer In fObject.fList
Console.WriteLine("hello; {0}", fObject.fList.Item(cnt).ToString())
cnt = cnt + 1
Next
Console.WriteLine("press [enter] to exit")
Console.Read()
End Sub
End Module
Can I change the class code so that List.vb is a list(of integer) type?
This would mean that in the Console code I could replace In fObject.fList with just In fObject?
Or am I barking up the wrong tree - should classes be single objects and lists should be collections of classes ?
Yes, you can do that. In order for an object to be compatible with For Each, it must have a GetEnumerator function:
Public Function GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumerator
Return New IntListEnum(fList)
End Function
The IntListEnum class must, in turn, implement IEnumerator, like this:
Public Class IntListEnum Implements IEnumerator
Private listInt As List(Of Integer)
Dim position As Integer = -1
Public Sub New(ByVal fList As List(Of Integer))
listInt = fList
End Sub
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
position = position + 1
Return (position < listInt.Count)
End Function
Public Sub Reset() Implements IEnumerator.Reset
position = -1
End Sub
Public ReadOnly Property Current() As Object Implements IEnumerator.Current
Get
Try
Return listInt(position)
Catch ex As IndexOutOfRangeException
Throw New InvalidOperationException()
End Try
End Get
End Property
End Class
Now you can make fList private, and iterate your List as follows:
For Each x As Integer In fObject
You can see a complete example here.
The answer that dasblinkenlight has provided is excellent, but if all you need is a list that of integers that is pre-populated, you can just inherit from List(Of Integer) and then have the class populate itself in the constructor:
Public Class List
Inherits List(Of Integer)
Public Sub New()
Add(1)
Add(2)
Add(3)
Add(4)
Add(5)
End Sub
End Class
When you inherit from List(Of Integer), your class automatically gets all of the functionality implemented by that type, so your class also becomes a list class that works the same way. Then, you can just use it like this:
Dim fObject As New List()
For Each x As Integer In fObject
Console.WriteLine("hello; {0}", x)
Next

Implementing my own interface in VBA - Error: Object module needs to implement 'x' for interface 'y'

How do I implement my class ClsInterface, which has this code:
Public Function add(x As Integer, y As Integer) As Integer
End Function
in my class Class2, which has this code:
Implements ClsInterface
Public Function add(x As Integer, y As Integer) As Integer
add = x + y
End Function
My test code is
Public Sub test()
Dim obj As New Class2
MsgBox obj.add(5, 2)
End Sub
This always comes up with the following error:
Microsoft Visual Basic
Compile error:
Object module needs to implement 'add' for interface 'ClsInterface'
OK/Help
but there is no help on Microsoft help (when I press on Help button).
Any Ideas?
Your Class2 must look like:
Implements ClsInterface
Private Function ClsInterface_add(x As Integer, y As Integer) As Integer
ClsInterface_add = x + y
End Function
Check out the drop-down boxes at the top of Class2's code window, you can see what base object you can refer to; Class or ClsInterface.
In your test code you want:
Dim obj As New ClsInterface
If you want to call across the interface.
I would also recommend naming interfaces in the form ISomeDescription and using Dim then Set rather than Dim As New.