VB.net how do I combine lists and classses? - vb.net

Here are my definitions:
Public Class RouteDetails
Public strRoad As String
Public strToLane As String
Public strToDirection As String
End Class
Public Class Route
Public lstRouteDetails As List(Of RouteDetails)
End Class
ByVal lstRouteList As List(Of Route) (part of a function definition)
Sorry I tried to put the definitions section into a code block but, for some reason, it's not working.
Here is a section of the code, from that function, that I'm having problems with:
rlcRoute = New Route
lstRouteList.Add(rlcRoute)
rdcRouteStep.strRoad = sdrNextJunction.GetValue(3)
rdcRouteStep.strToLane = sdrNextJunction.GetValue(2)
rdcRouteStep.strToDirection = sdrNextJunction.GetValue(1)
For intloop = 0 To lstRouteList.Count - 1
lstRouteList(intloop).lstRouteDetails.Add(rdcRouteStep)
Next
The line, within the for loop, fails with:
System.NullReferenceException: 'Object reference not set to an
instance of an object.'
System.Collections.Generic.List.this[int].get.lstRouteDetails was
Nothing.
If I understand the error correctly then it's telling me that I haven't created an instance of lstRouteDetails for this instance of Route? If that's correct then I would
appreciate some advice on how to achieve this? I've tried a few combinations. The structure I'm trying to have is as follows:
RouteList is a list of objects of type route
Each Route object contains several objects of type RouteDetails
Thanks

You don't need a class for you list. You can just create one in your form.
I added a custom constructor to your class so you can set all the properties of the class in one line as demonstrated in the BuildList Sub. I had to add back the default constructor (no parameters). With the default constructor the properties are set individually. I also added an override of the .ToString method so we could use it in the Form. (See the OPCode method)
The Class
Public Class RouteDetails
Public strRoad As String
Public strToLane As String
Public strToDirection As String
Public Sub New()
End Sub
Public Sub New(Road As String, ToLane As String, Direction As String)
strRoad = Road
strToLane = ToLane
strToDirection = Direction
End Sub
Public Overrides Function ToString() As String
Return $"Road is {strRoad}, Lane is {strToLane}, Direction is {strToDirection}"
End Function
End Class
The Form
Private RouteList As New List(Of RouteDetails)
Private Sub BuildList_Click(sender As Object, e As EventArgs) Handles MyBase.Load
Dim Route As New RouteDetails()
Route.strRoad = "Main Street"
Route.strToLane = "Park Lane"
Route.strToDirection = "North East"
RouteList.Add(Route)
'or with the custom constructor
Dim Route2 As New RouteDetails("Wall Steet", "Mulberry Lane", "South West")
RouteList.Add(Route2)
End Sub
You can refer to the items in the list anywhere in the form.
Private Sub OPCode()
MessageBox.Show(RouteList(1).strToLane)
For Each item As RouteDetails In RouteList
ListBox1.Items.Add(item) 'The ListBox calls .ToString on the item for display
Next
End Sub
EDIT
Expanded Code
Public Class Route
Public RouteDetailsList As New List(Of RouteDetails)
Public Name As String
Public Sub New(RDL As List(Of RouteDetails), nme As String)
RouteDetailsList = RDL
Name = nme
End Sub
Public Sub New()
End Sub
Public Overrides Function ToString() As String
Return Name
End Function
End Class
To test the code
In the Form_Load call BuildRouteList
Private RouteList As New List(Of Route)
Private Sub BuildRouteList()
Dim Details = {New RouteDetails("MainStreet", "Park Lane", "North East"), New RouteDetails("Wall Steet", "Mulberry Lane", "South West")}
'Uses the construtor List(Of T)(IEnumerable<T>)
Dim R As New Route(New List(Of RouteDetails)(Details), "1")
RouteList.Add(R)
Dim Details2 = {New RouteDetails("Wall Street", "Strawberry Lane", "South West"), New RouteDetails("Pine Street", "Little Maid Lawnd", "North")}
Dim R2 As New Route(New List(Of RouteDetails)(Details2), "2")
RouteList.Add(R2)
End Sub
In a button click call OPCode1 (assumes a ListBox1 is present
Private Sub OPCode1()
MessageBox.Show(RouteList(0).RouteDetailsList(1).strToLane) 'shows Mulberry Lane
For Each item As Route In RouteList
ListBox1.Items.Add(item)
For Each route In item.RouteDetailsList
ListBox1.Items.Add(route) 'The ListBox calls .ToString on the item for display
Next
Next
End Sub

Related

Removing items in a collection based on listbox string

Having issues when clicking the remove button. If more of my code is needed, let me know. I get this error on the line AddressList.Remove(selectedName):
System.ArgumentException: 'Argument 'Key' is not a valid value.
I've tried many variations but can't figure out why this doesn't work. I think it has something to do with how the strings are concatenated in the listbox. I need to be able to remove entries from the collection and the listbox. Any help would be greatly appreciated.
Module EmailCollection
Public AddressList As New Collection
Public Sub AddRecord(ByVal a As cAddress)
Try
AddressList.Add(a)
Catch ex As Exception
MessageBox.Show("Error: inputs must be characters valid in string format")
End Try
End Sub
End Module
public class form1
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
Dim frmAdd As New AddNewName
frmAdd.ShowDialog()
UpdateListBox()
End Sub
Private Sub UpdateListBox()
lstAddress.Items.Clear()
Dim a As cAddress
For Each a In AddressList
lstAddress.Items.Add(String.Concat(a.strName, a.strEmail, a.strPhone, a.strComment))
Next
If lstAddress.Items.Count > 0 Then
lstAddress.SelectedIndex = 0
End If
End Sub
Private Sub btnRemove_Click(sender As Object, e As EventArgs) Handles btnRemove.Click
Dim selectedName As String
Try
' Get the selected value.
selectedName = lstAddress.SelectedItem.ToString()
' Remove the selected name from the list box and the collection.
If MessageBox.Show("Are you sure?",
"Confirm Deletion",
MessageBoxButtons.YesNo) = Windows.Forms.DialogResult.Yes Then
lstAddress.Items.Remove(selectedName)
AddressList.Remove(selectedName)
End If
Catch ex As NullReferenceException
MessageBox.Show("Select an item to remove.", "Selection Needed")
End Try
End Sub
end class
In your Module I changed AddressList from the old VB6 Collection type to the .net List(Of T). The T stands for Type.
Module EmailCollection
Public AddressList As New List(Of cAddress)
Public Sub AddRecord(ByVal a As cAddress)
AddressList.Add(a)
End Sub
End Module
I guessed that your cAddress class looks something like this. I added a custom .ToString method so the list box will display the data you wish but the item, itself, will be a cAddress object.
Public Class cAddress
Public Property strName As String
Public Property strEmail As String
Public Property strPhone As String
Public Property strComment As String
Public Overrides Function ToString() As String
Return $"{strName}, {strEmail}, {strPhone}, {strComment}"
End Function
End Class
In the Form...
Instead of adding a string to the list box I added the cAddress object. The list box calls .ToString on the object to get the display value.
Private Sub UpdateListBox()
ListBox1.Items.Clear()
For Each a As cAddress In AddressList
ListBox1.Items.Add(a)
Next
If ListBox1.Items.Count > 0 Then
ListBox1.SelectedIndex = 0
End If
End Sub
In the remove button I cast the selected item to its underlying type, cAddress. This is the item removed from the AddressList. Then simply remove the selected item from the list box.
Private Sub btnRemove_Click(sender As Object, e As EventArgs) Handles btnRemove.Click
If MessageBox.Show("Are you sure?",
"Confirm Deletion",
MessageBoxButtons.YesNo) = Windows.Forms.DialogResult.Yes Then
AddressList.Remove(DirectCast(ListBox1.SelectedItem, cAddress))
ListBox1.Items.Remove(ListBox1.SelectedItem)
End If
End Sub
I changed the name of the list box to ListBox1 to match my test project.
Here is something to try, use a BindingSource for setting up the ListBox. In the class override ToString to what is to be shown in the ListBox rather than what you are doing now without a DataSource.
My version of your class has name and property name changes.
Public Class Address
Public Property Name() As String
Public Property Email() As String
Public Property Phone() As String
Public Property Comment() As String
Public Overrides Function ToString() As String
Return $"{Name} {Email} {Phone} {Comment}"
End Function
End Class
Mocked data is used to populate the ListBox
Public Class Form1
Private ReadOnly _bsAddresses As New BindingSource
Private Sub UpdateListBox()
Dim AddressList = New List(Of Address) From
{
New Address() With {
.Name = "John",
.Email = "john#gmail.com",
.Phone = "555-444-3456",
.Comment = "AAA"},
New Address() With {
.Name = "Mary",
.Email = "mary#gmail.com",
.Phone = "888-333-2222",
.Comment = "BBB"},
New Address() With {
.Name = "Bob",
.Email = "bob#gmail.com",
.Phone = "111-555-9999",
.Comment = "CCC"}
}
_bsAddresses.DataSource = AddressList
lstAddress.DataSource = _bsAddresses
lstAddress.SelectedIndex = 0
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) _
Handles Me.Shown
UpdateListBox()
End Sub
Private Sub RemoveButton_Click(sender As Object, e As EventArgs) _
Handles RemoveButton.Click
If lstAddress.Items.Count > 0 AndAlso lstAddress.SelectedItem IsNot Nothing Then
Dim address = CType(_bsAddresses.Current, Address)
If My.Dialogs.Question($"Remove {address.Name}") Then
_bsAddresses.RemoveCurrent()
RemoveButton.Enabled = _bsAddresses.Count > 0
End If
End If
End Sub
End Class
Code module for asking a question
Namespace My
<ComponentModel.EditorBrowsable(ComponentModel.EditorBrowsableState.Never)>
Partial Friend Class _Dialogs
Public Function Question(text As String) As Boolean
Return (MessageBox.Show(
text,
My.Application.Info.Title,
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2) = MsgBoxResult.Yes)
End Function
End Class
<HideModuleName()>
Friend Module WinFormsDialogs
Private instance As New ThreadSafeObjectProvider(Of _Dialogs)
ReadOnly Property Dialogs() As _Dialogs
Get
Return instance.GetInstance()
End Get
End Property
End Module
End Namespace
Karen's post seems quite comprehensive. My response is an attempt to focus on your direct question.
I don't see all of the type definitions shown in your code, but in answer to your question, which I believe is "Why am I getting System.ArgumentException: 'Argument 'Key' is not a valid value":
In the offending line of code:
AddressList.Remove(selectedName)
AddressList is a collection. To use .Remove, you must pass in an object of the AddressList collection to remove it. You are passing a simple string, and that is not an AddressList object. You need to create an object based on your string selectedName to pass into .Remove and that line should work. The rest of what you are doing seems more complex.

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

(De)Serializing in VB.Net

I have written a class (containing only properties). The code is shortened, here is only the part of the code what I want to do:
Public Class test
Public Overrides Function ToString() As String
'Das Objekt lebensmittel serialisieren
Dim ser As New Xml.Serialization.XmlSerializer(Me.GetType)
Dim sw As New IO.StringWriter
ser.Serialize(sw, Me)
Return sw.tostring
End Function
Public Sub New()
End Sub
Public Sub New(ByVal t As String)
Dim deser As New Xml.Serialization.XmlSerializer(Me.GetType)
Dim ms As New IO.MemoryStream(System.Text.Encoding.Unicode.GetBytes(t))
Me = CType(deser.Deserialize(ms), test) 'This throws an error
End Sub
End Class
What I want to do is to overload the New() operator and deserialize the string to this class, like:
Dim x As New test(string)
How can I do this? The marked line throws an error in Sub New.

how to get the Index of object in collection

I'm trying to make a application, in this application I have a List(of T) collection that holds an object.
When processing the object I need to know it's Index from the list.
Example:
Public Class
Public oList as New List(of TestObject)
Private Sub Test()
Dim NewObject As New TestObject
oList.add(NewObject)
Index(NewObject)
End Sub
Private Sub Index(Byval TestObject As TestObject)
debug.print(Testobject.index)
End Sub
End Class
Is something like this possible? Ive seen it available in a reference file I used some time ago, but now I would like to make this available within my own class.
Can someone provide a sample?
PS: I know I can get the index using the List(Of T).IndexOf Method (T) but for future possibilities I would like to make the call from the object itself.
What usually happen is that they have a custom list, they don't directly used List(Of T) and store the list inside the object when they add that item to the list.
Module Module1
Sub Main()
Dim someList As New CustomList
someList.Add(New CustomItem())
someList.Add(New CustomItem())
someList.Add(New CustomItem())
Console.WriteLine(someList(1).Index)
Console.ReadLine()
End Sub
End Module
Class CustomItem
' Friend since we don't want anyone else to see/change it.
Friend IncludedInList As CustomList
Public ReadOnly Property Index
Get
If IncludedInList Is Nothing Then
Return -1
End If
Return IncludedInList.IndexOf(Me)
End Get
End Property
End Class
Class CustomList
Inherits System.Collections.ObjectModel.Collection(Of CustomItem)
Protected Overrides Sub InsertItem(index As Integer, item As CustomItem)
If item.IncludedInList IsNot Nothing Then
Throw New ArgumentException("Item already in a list")
End If
item.IncludedInList = Me
MyBase.InsertItem(index, item)
End Sub
Protected Overrides Sub RemoveItem(index As Integer)
Me(index).IncludedInList = Nothing
MyBase.RemoveItem(index)
End Sub
End Class
It looks like this
Public oList As New List(Of TestObject)
Private Sub Test()
Dim NewObject As New TestObject(oList.Count)
oList.add(NewObject)
End Sub
Public Class TestObject
Public index As Integer
Public Sub New(IndxOfObj As Integer)
Me.index = IndxOfObj
End Sub
End Class
If you necessarily need to have it as a property on the object I would suggest the following:
Public Class Main
Public oList As New List(Of TestObject)
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Dim NewObject As New TestObject(Me)
oList.Add(NewObject)
Dim NewObject2 As New TestObject(Me)
oList.Add(NewObject2)
MsgBox(NewObject2.Index)
End Sub
Public Function Index(ByVal TestObject As TestObject) As Integer
Return oList.IndexOf(TestObject)
End Function
End Class
Public Class TestObject
Private _main As Main
Public ReadOnly Property Index() As Integer
Get
Return _main.Index(Me)
End Get
End Property
Public Sub New(RootClass As Main)
_main = RootClass
End Sub
End Class
If you happen to have the Main class as a Singleton you can skip the whole sending 'Me' into the constructor business. Then you can just call Main.Index without storing it as a property on all TestObjects.

Removing an object from List (Of T)

I'm not able to remove an object from my List (of contact)
Here are my fields:
Public Class Contact
'Things to remember
Private m_firstName As String = String.Empty
Private m_lastName As String = String.Empty
Private m_address As Address
My list:
Public Class ContactManager
Private m_contactRegistry As List(Of Contact)
Public Sub New()
m_contactRegistry = New List(Of Contact)()
End Sub
My method in ContactManger Class. Here I'm getting error "Value of type 'Integer' cannot be converted to Assignment.Contact" on the index
Public Function DeleteContact(index As Integer) As Boolean
m_contactRegistry.Remove(index)
Return True
End Function
My delete button method on my Main class:
Private Sub btnRemove_Click(sender As Object, e As EventArgs) Handles btnRemove.Click
'listResults is my listbox
Dim list = listResults.SelectedIndex
'm_contact is an object of the Contact class
m_contacts.DeleteContact(list)
UpdateGUI()
End Sub
The problem is that I don't know how to do the method DeleteContact(index As Integer) without getting an error. Do you guys have a suggestion?
When using an index, you need RemoveAt() rather than Remove()