List.add overwrites previus items in list VB.NET - vb.net

Hi I have a little problem with a list of custom objects i wrote. When I use the list.add(xxx) method it doesn't simply append the xxx object to my list, but it turns each items to xxx and I don't know how to fix it.
Here is the declaration of my custom class:
Public Class User
Private Shared n As String
Public Shared Property Name() As String
Get
Return n
End Get
Set(ByVal value As String)
n = value
End Set
End Property
Sub New(ByVal name As String)
User.Name = name
End Sub
End Class
And here's where I call the list.add method
Public Class Form1
Private Sub subname
Dim temp As New User
Dim data As New List(Of User)
For Each item As String In ListBox1.Items
data.Add(New User(item))
Next
End Sub
End Sub
P.S. Yes, I've already read some posts about people having the same problem but did not understand how to apply their solution to my project.

Your problem is the Shared property and private member. A Shared member is shared among all instances of the class. If you set it in one instance it will be the same for all instances.
Private Shared n As String
Public Shared Property Name() As String
...
End Property
Remove the keyword Shared and it should work as expected.
Private n As String
Public Property Name() As String
...
End Property

Related

How to access object in multidimensional array in Visual Basic

In Visual Basic, I am using a List to store an array of objects. However, I haven't figured out how to access a particular object once I have added it to my list.
I am using Microsoft Visual Studio
Private Results As New List(Of Object)
Dim Results As Object()
Dim CaseStatus as String = "Closed"
...
Results.Add(New Object() {CaseStatus, FlagsStr, OBTotal, OBPaid})
MessageBox.Show(Result(0).ToString)
Displays: System.Object[]
I have also tried MessageBox.Show(Results(0,0).ToString), but don't get the expected results.
When you can .ToString on an object you get the fully qualified name of the object unless the class overrides .ToString. Creating a class and a List (Of T) gives you easy access to any of the properties of the class.
Public Class ProjectCase
Public Property CaseStatus As String
Public Property FlagsString As String
Public Property OBTotal As Decimal
Public Property OBPaid As Decimal
Public Sub New()
'include a default constructor
End Sub
Public Sub New(cs As String, fs As String, total As Decimal, paid As Decimal)
CaseStatus = cs
FlagsString = fs
OBTotal = total
OBPaid = paid
End Sub
Public Overrides Function ToString() As String
Return $"{CaseStatus}, {FlagsString}, Total = {OBTotal}, Paid = {OBPaid}"
End Function
End Class
Private Results As New List(Of ProjectCase)
Private Sub OPCode()
Results.Add(New ProjectCase("Closed", "Some string", 74.36D, 22D))
MessageBox.Show(Results(0).ToString)
MessageBox.Show(Results(0).CaseStatus) 'or any property of your class
End Sub

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

VB.Net check for duplicate items in a collection base

I have a class that inherits from CollectionBase. I tried to use the contains method to detect whether the Key already exists before inserting a new one. Here is what I have tried.
<Serializable()> Public Class validationList
Inherits CollectionBase
Public Function Add(ByVal Item As validationItem) As Integer
Return Me.List.Add(Item)
End Function
Default Public ReadOnly Property Item(ByVal index As Integer) As validationItem
Get
Return CType(List.Item(index), validationItem)
End Get
End Property
Public Sub Remove(ByVal index As Integer)
Me.List.RemoveAt(index)
End Sub
Protected Overrides Sub OnInsert(ByVal index As Integer, ByVal value As Object)
If Me.List.Contains(value) Then MsgBox("Already exist")
MyBase.OnInsert(index, value)
End Sub
Public Function IndexOf(ByVal key As validationItem)
Return List.IndexOf(key)
End Function
Public Sub AddRange(ByVal item() As validationItem)
For counter As Integer = 0 To item.GetLength(0) - 1
List.Add(item(counter))
Next
End Sub
End Class
<Serializable()> Public Class validationItem
Implements IEquatable(Of validationItem)
Private _key As validationTypes
Private _value As String
Public Sub New()
' Empty constructor is needed for serialization
End Sub
Public Sub New(ByVal k As validationTypes, ByVal v As String)
_key = k
_value = v
End Sub
Public Enum validationTypes
Madatory = 0
[Integer] = 1
Numeric = 2
[Decimal] = 3
MaxValue = 4
MinValue = 5
MinLength = 6
Email = 7
End Enum
Public Property Value As String
Get
Return _value
End Get
Set(ByVal Value As String)
_value = Value
End Set
End Property
Public Property Key As validationTypes
Get
Return _key
End Get
Set(ByVal value As validationTypes)
_key = value
End Set
End Property
Protected Overloads Function Equals(ByVal eqItem As validationItem) As Boolean Implements IEquatable(Of Testing_Project.validationItem).Equals
If eqItem Is Nothing Then Return False
Return Me._key = eqItem.Key
End Function
Public Overrides Function Equals(ByVal eqItem As Object) As Boolean
If eqItem Is Nothing Then Return False
Dim eqItemObj As validationItem = TryCast(eqItem, validationItem)
If eqItemObj Is Nothing Then Return False
Return Equals(eqItemObj)
End Function
Public Overrides Function GetHashCode() As Integer
Return Me._key.GetHashCode()
End Function
End Class
The validationList will be exposed from a usercontrol as a property, so that items could be added from the designer. When adding items I need to detect whether they already exist. I tried overriding the OnInsert but this sometime return that duplicates exists even when their aren't and doesn't report that duplicate exist when I try to add existing keys.
This indirectly answers the question after dealing with the issue which emerged in comments about Collection(Of T):
Add a reference to System.Collections.ObjectModel if needed, then
Imports System.Collections.ObjectModel
' a ValidationItem collection class
Public Class ValidationItems
Inherits Collection(Of ValidationItem)
Public Shadows Sub Add(NewItem As ValidationItem)
' test for existence
' do not add if it is not unique
Dim dupe As Boolean = False
For n As Int32 = 0 To Items.Count - 1
If Items(n).Key = NewItem.Key Then
dupe = True
Exit For
End If
Next
If dupe = False then
items.Add(newitem)
End if
' I would actually use an IndexOfKey function which might
' be useful elsewhere and only add if the return is -1
If IndexOfKey(NewItem.Key) <> -1 Then
Items.Add(newItem)
End If
End Sub
Some NET collection types implement Add as a function and return the item added. This sounds weird since you pass it the item to add. But returning Nothing if the item cannot be added is a neat semaphore for "I cant/wont do that". I cant recall if the std NET Collection Editor recognizes that or not.
One problem with using Contains is that it will test if item passed as param is the same object as one in the list. They never will be the same object, even if they have the same values. Testing the key in a loop is simpler than calling a method which implements an interface. (That previous answer was totally valid in the context presented, but the context has changed).
Even if you stay with CollectionBase, you want to handle it in the Add. If you try to remove it in OnInsert, VS will have problems deserializing the collection.
Also, your validationitem needs a Name property or the Collection Editor will display "Myassembly+MyType" as the Name (or a ToString override).
Other issues:
I am not sure your IndexOf will work. The list contains ValidationItems (objects), but you check it for _key (string). This will not matter if you change to Collection(Of T) which implements it for you.
The simple ctor is needed by the Collection Editor, not serialization. But the important thing is that it is there.
As for the comment about all Zeroes coming back - that is because your ValidationItem is not yet decorated for designer serialization. Maybe not the Collection Property either, that isnt shown.

VB.NET CType: How do I use CType to change an object variable "obj" to my custom class that I reference using a string variable like obj.GetType.Name?

The code below works for the class that I hard coded "XCCustomers" in my RetrieveIDandName method where I use CType. However, I would like to be able to pass in various classes and property names to get the integer and string LIST returned. For example, in my code below, I would like to also pass in "XCEmployees" to my RetrieveIDandName method. I feel so close... I was hoping someone knew how to use CType where I can pass in the class name as a string variable.
Note, all the other examples I have seen and tried fail because we are using Option Strict On which disallows late binding. That is why I need to use CType.
I also studied the "Activator.CreateInstance" code examples to try to get the class reference instance by string name but I was unable to get CType to work with that.
When I use obj.GetType.Name or obj.GetType.FullName in place of the "XCCustomers" in CType(obj, XCCustomers)(i)
I get the error "Type 'obj.GetType.Name' is not defined" or "Type 'obj.GetType.FullName' is not defined"
Thanks for your help.
Rick
'+++++++++++++++++++++++++++++++
Imports DataLaasXC.Business
Imports DataLaasXC.Utilities
Public Class ucCustomerList
'Here is the calling method:
Public Sub CallingSub()
Dim customerList As New XCCustomers()
Dim customerIdAndName As New List(Of XCCustomer) = RetrieveIDandName(customerList, "CustomerId", " CustomerName")
'This code below fails because I had to hard code “XCCustomer” in the “Dim item...” section of my RetrieveEmployeesIDandName method.
Dim employeeList As New XCEmployees()
Dim employeeIdAndName As New List(Of XCEmployee) = RetrieveIDandName(employeeList, "EmployeeId", " EmployeeName")
'doing stuff here...
End Sub
'Here is the method where I would like to use the class name string when I use CType:
Private Function RetrieveIDandName(ByVal obj As Object, ByVal idPropName As String, ByVal namePropName As String) As List(Of IntStringPair)
Dim selectedItems As List(Of IntStringPair) = New List(Of IntStringPair)
Dim fullyQualifiedClassName As String = obj.GetType.FullName
Dim count As Integer = CInt(obj.GetType().GetProperty("Count").GetValue(obj, Nothing))
If (count > 0) Then
For i As Integer = 0 To count - 1
'Rather than hard coding “XCCustomer” below, I want to use something like “obj.GetType.Name”???
Dim Item As IntStringPair = New IntStringPair(CInt(CType(obj, XCCustomers)(i).GetType().GetProperty("CustomerId").GetValue(CType(obj, XCCustomers)(i), Nothing)), _
CStr(CType(obj, XCCustomers)(i).GetType().GetProperty("CustomerName").GetValue(CType(obj, XCCustomers)(i), Nothing)))
selectedItems.Add(Item)
Next
End If
Return selectedItems
End Function
End Class
'+++++++++++++++++++++++++++++++
' Below are the supporting classes if you need to see what else is happening:
Namespace DataLaasXC.Utilities
Public Class IntStringPair
Public Sub New(ByVal _Key As Integer, ByVal _Value As String)
Value = _Value
Key = _Key
End Sub
Public Property Value As String
Public Property Key As Integer
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCCustomer
Public Property CustomerId As Integer
Public Property CustomerName As String
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCCustomers
Inherits List(Of XCCustomer)
Public Sub New()
PopulateCustomersFromDatabase()
End Sub
Public Sub New(ByVal GetEmpty As Boolean)
End Sub
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCEmployee
Public Property EmployeeId As Integer
Public Property EmployeeName As String
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCEmployees
Inherits List(Of XCEmployee)
Public Sub New()
PopulateEmployeesFromDatabase()
End Sub
Public Sub New(ByVal GetEmpty As Boolean)
End Sub
End Class
End Namespace
From MSDN
CType(expression, typename)
. . .
typename : Any expression that is legal
within an As clause in a Dim
statement, that is, the name of any
data type, object, structure, class,
or interface.
This is basically saying you can't use CType dynamically, just statically. i.e. At the point where the code is compiled the compiler needs to know what typename is going to be.
You can't change this at runtime.
Hope this helps.
Since List(Of T) implements the non-generic IList interface, you could change your function declaration to:
Private Function RetrieveIDandName(ByVal obj As System.Collections.IList, ByVal idPropName As String, ByVal namePropName As String) As List(Of IntStringPair)
And then your troublesome line would become (with also using the property name parameters):
Dim Item As IntStringPair = New IntStringPair(CInt(obj(i).GetType().GetProperty(idPropName).GetValue(obj(i), Nothing)), _
CStr(obj(i).GetType().GetProperty(namePropName).GetValue(obj(i), Nothing)))
Of course, you could still have the first parameter by Object, and then attempt to cast to IList, but that's up to you.
ctype is used to convert in object type.

Generic Collections

In VB6, there used to be a Collection data type that would allow retrieval of an item in the collection by either its key or its ordinal. However, it wasn't strongly typed.
Now, with VB.Net, I am looking for a suitable replacement that is strongly type and could be used with a generic collection.
This is a simple example of what I want to do. The only problem is that the underlying collection class, BindingList, does not support efficient retrieval of an item by an alpha key, so I have to loop through the elements to get the item I am looking for. For large collections, this is not efficient.
I have looked though the various Collection type classes and have found no suitable replacement.
This is what I want to do, except for the looping that is done with the Item property.
Rather than just saying "Use Hash tables" or something like that, if you could, please include the detailed out as I have done for the short example below.
Public Class Car
Public Sub New(ByVal keyName As String, ByVal property1 As String)
_KeyName = keyName
_Property1 = property1
End Sub
Dim _KeyName As String
Public Property KeyName() As String
Get
Return _KeyName
End Get
Set(ByVal value As String)
_KeyName = value
End Set
End Property
Public _Property1 As String
Public Property Property1() As String
Get
Return _Property1
End Get
Set(ByVal value As String)
_Property1 = value
End Set
End Property
End Class
Public Class Cars
Inherits System.ComponentModel.BindingList(Of Car)
Public Overloads ReadOnly Property Item(ByVal key As String) As Car
Get
For Each CurrentCar As Car In Me.Items
If CurrentCar.KeyName = key Then
Return CurrentCar
End If
Next
Return Nothing
End Get
End Property
End Class
I believe you're looking for Dictionary<TKey, TValue>. In fact, if you do want your own collection class that's strongly typed and isn't (itself) generic, if you change your parent class to Dictionary<string, Car>, you should be all set. This all does, of course, assume that you add the cars to the collection with an explicit string key. If you want the lookup to be based on the value of a property in the collection, you'd do better either using or inheriting from List<Car> and using LINQ to query the list. You could then have...
Public Overloads ReadOnly Property Item(ByVal key As String) As Car
Get
Return (from c in Me where c.KeyName = key select c).SingleOrDefault()
End Get
End Property
Do you really need both access by key AND index?
If you do not, then use a Dictionary(Of String, Car), and use
- MyCol.Items("XXX") to retrieve an item by key (or the shorthand MyCol("XXX"))
- MyCol.ContainsKey("XXX") to test if a key exists in the collection
- For Each Entry as KeyValuePair(Of String, Car) in MyCol if you want to enumerate all objects AND their key
- For Each Entry as Car in MyCol.Values if you want to enumerate the entries without consideration for the key
If you need both access by index and key, I'm afraid your best bet is to use a List(of Car) and a Dictionary(of Car) rolled into one custom collection class, because I believe they went away from that kind of collection which is not really all that useful for most problems.
This is what I am thinking is my best solution. I welcome comments for a better way!
Imports Microsoft.VisualBasic
Public Class Car
Implements Xs(Of Car).IKeyName
Private _KeyName As String
Public Sub New(ByVal keyName As String, ByVal property1 As String)
_KeyName = keyName
_Property1 = property1
End Sub
Public Property KeyName() As String Implements Xs(Of Car).IKeyName.KeyName
Get
Return _KeyName
End Get
Set(ByVal value As String)
_KeyName = value
End Set
End Property
Public _Property1 As String
Public Property Property1() As String
Get
Return _Property1
End Get
Set(ByVal value As String)
_Property1 = value
End Set
End Property
End Class
Public Class Cars
Inherits System.ComponentModel.BindingList(Of Car)
Public Overloads ReadOnly Property Item(ByVal key As String) As Car
Get
For Each CurrentCar As Car In Me.Items
If CurrentCar.KeyName = key Then
Return CurrentCar
End If
Next
Return Nothing
End Get
End Property
End Class
Public Class X
Private _KeyName As String
Public Property KeyName() As String
Get
Return _Keyname
End Get
Set(ByVal value As String)
_Keyname = value
End Set
End Property
End Class
Public Class Xs(Of X)
Inherits Hashtable
Interface IKeyName
Property KeyName() As String
End Interface
Public Shadows Sub Add(ByVal item As IKeyName)
MyBase.Add(item.KeyName, item)
End Sub
Public Shadows ReadOnly Property Item(ByVal key As String) As x
Get
If Me.ContainsKey(key) Then
Return MyBase.Item(key)
Else
'If I mispell a key, I don't want to end up creating a new mispelled version, I want an error
Throw New Exception("Element with key " & key & " is not found")
End If
End Get
End Property
End Class
Public Class Cars2
Inherits Xs(Of Car)
End Class
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim MyCar As New Car("key1", "prop1")
'First approach
Dim MyCars As New Cars
MyCars.Add(MyCar)
Dim RetrievedCar As Car = MyCars.Item("key1") 'Inefficient retrieval by key (uses looping)
'Second approach
Dim Cars2 As New Cars2
Cars2.Add(MyCar)
Dim RetrievedCar2 As Car = Cars2.Item("key1") 'Can now efficiently retreive an item by its key
End Sub
The OrderedDictionary in the System.Collections.Specialized namespace can be accessed by index and by key, if you ever need that. But looking at your solution, it looks like a standard Dictionary, but less efficient because it forces a string type for keys.
Is there any reason you can't use the Dictionary .NET provides you, or another collection type that's already in .NET like OrderedDictionary?