Removing an object from List (Of T) - vb.net

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()

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 do you pass values from a textbox to a public property in a class?

I need to pass code that user enters into a Textbox to a Public Property within a class. Here is my code.
Form2.vb Code
Public Class Form2
Dim class2A As part2Class = New part2Class()
Dim class2B As part2BClass = New part2BClass()
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim a As Integer = CType(TextBox1.Text, Integer)
Dim b As Integer = CType(TextBox1.Text, Integer)
part2Class._Num1 = a
part2Class._Num2 = b
End Sub
Here is my code in part2Class.vb
Public Class part2Class
Public Property _Num1
Public Property _Num2
Public Overridable Function Calculate() As Integer
Return _Num1 + _Num2
End Function
End Class
I get an error saying "Reference to a non-shared member requires an object reference." How do I pass the values from the textboxes to the public property values?
Thanks!
You need to create an instance of a class first before accessing it's members:
Dim objpart2Class as part2Class = new part2Class()
objpart2Class._Num1 = a
objpart2Class._Num2 = b

Pass-through methods vs. accessing nested objects directly

What I have is an object that contains a list of objects that each contain another list of objects that have properties and such.
Currently I use pass-through methods to be able to add to those nested objects, like in this extremely simplified example:
Public Class clsA
Private objB As List(Of clsB) = New List(Of clsB)
Public Sub New()
objB.Add(New clsB)
End Sub
Public Sub AddInt(ByVal BIndex As Int32, ByVal CIndex As Int32, ByVal Number As Int32)
objB(BIndex).AddInt(CIndex, Number)
End Sub
End Class
Public Class clsB
Private objC As List(Of clsC) = New List(Of clsC)
Public Sub New()
objC.Add(New clsC)
End Sub
Public Sub AddInt(ByVal CIndex As Int32, ByVal Number As Int32)
objC(CIndex).AddInt(Number)
End Sub
End Class
Public Class clsC
Private lstNum As List(Of Int32) = New List(Of Int32)
Public Sub AddInt(ByVal Number As Int32)
lstNum.Add(Number)
End Sub
End Class
It seems like proper coding standards would tell me this is correct compared to:
Public Class clsD
Public objE As List(Of clsE) = New List(Of clsE)
Public Sub New()
objE.Add(New clsE)
End Sub
End Class
Public Class clsE
Public objF As List(Of clsF) = New List(Of clsF)
Public Sub New()
objF.Add(New clsF)
End Sub
End Class
Public Class clsF
Public lstNum As List(Of Int32) = New List(Of Int32)
End Class
Are there some instances where either method would be acceptable? Or would the pass-through setup always be preferred?
Public Class Form1
Dim oA As clsA = New clsA
Dim oD As clsD = New clsD
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
oA.AddInt(0, 0, 3)
oD.objE(0).objF(0).lstNum.Add(3)
End Sub
End Class
Think about how it's done throughout the .NET Framework. The collection should be assigned to a private field and exposed via a public read-only property.
Public Class Thing
Private _stuff As New List(Of Thing)
Public ReadOnly Property Stuff() As List(Of Thing)
Get
Return _stuff
End Get
End Property
End Class
The caller can then access the collection directly to call its Add method, etc, but cannot assign a whole new collection. There are examples everywhere: Control.Controls, ListBox.Items, ComboBox.Items, ListView.Items, DataSet.Tables, DataSet.Relations, DataTable.Rows, Datatable.Columns, etc, etc, etc.

Showing classes to user and instantiating selected type

I'm starting to learn about Reflection in VB.NET, and I have a little example problem I'm working on to understand some concepts.
So I have one interface implemented by three classes:
Public Interface IVehicle
Sub SayType()
End Interface
Public Class Bike
Implements IVehicle
Public Sub SayType() Implements IVehicle.SayType
MsgBox("I'm a bike")
End Sub
End Class
Public Class Car
Implements IVehicle
Public Sub SayType() Implements IVehicle.SayType
MsgBox("I'm a car")
End Sub
End Class
Public Class Plane
Implements IVehicle
Public Sub SayType() Implements IVehicle.SayType
MsgBox("I'm a plane")
End Sub
End Class
I would like the user to select one type of vehicle of all the vehicles available, instantiate one object of this type and call its method "SayType".
So, with this situation, I have 2 questions
The 1st one: I have thought about filling one ComboBox control with all the classes which implement the interface IVehicle. I have searched how to do so with reflection, and I've came up with this solution:
Private Function ObtainVehicleTypes() As IEnumerable(Of Type)
Dim types As IEnumerable(Of Type) = _
Reflection.Assembly.GetExecutingAssembly.GetTypes.Where(Function(t) _
t.GetInterface("IVehicle") IsNot Nothing)
Return types
End Function
With those types, I fill the ComboBox like this, which also works fine:
Private Sub AddTypesOfVehicles()
Dim types As IEnumerable(Of Type) = ObtainVehicleTypes()
For Each t As Type In types
ComboBox1.Items.Add(t.Name)
Next
End Sub
The problem is that, when I try to retrieve the item selected by the user and obtain the type asociated like shown below, I get Nothing, since the String doesn't contain the AssemblyName, only the Class name:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim type As Type = TryCast(ComboBox1.SelectedItem, Type) 'Here I get Nothing
Dim v As IVehicle = TryCast(Activator.CreateInstance(type), IVehicle)
v.SayType()
End Sub
I have also tried to to add this to the combobox:
For Each t As Type In types
ComboBox1.Items.Add(t) 'Adding the type, not only its name.
Next
But then it displays the AssemblyName to the user, which I want to avoid.
So, the question is... how would you do to show the classes to the user and the retrieve them correctly to instantiate an object of the chosen class?
The 2nd question: Do you consider this as a good approach? Would you suggest something simpler?
Thanks!
I do not understand the need of the method SayType on the interface. All types implements the GetType method which will return the info you'll need.
Dim vehicles As IVehicle() = New IVehicle() {New Bike(), New Car(), New Plane()}
For Each vehicle As IVehicle In vehicles
MsgBox(String.Format("I'm a {0}", vehicle.GetType().Name.ToLower()))
Next
'This will produce:
'------------------
'I'm a bike
'I'm a car
'I'm a plane
'------------------
This is how you could populate the combobox:
Dim t = GetType(IVehicle)
Dim list As List(Of Type) = Assembly.GetExecutingAssembly().GetTypes().Where(Function(x As Type) ((x <> t) AndAlso t.IsAssignableFrom(x))).ToList()
Me.ComboBox1.DataSource = list
Me.ComboBox1.DisplayMember = "Name"
And to retrieve the type:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles saveButton.Click
If (Me.ComboBox1.SelectedIndex <> -1) Then
Dim t As Type = TryCast(Me.ComboBox1.SelectedItem, Type)
If (Not t Is Nothing) Then
MsgBox(t.FullName)
End If
End If
End Sub
Edit
A more real-world example of an IVehicle interface would be something like:
Public Interface IVehicle
ReadOnly Property Manufacturer() As String
ReadOnly Property Model() As String
Property Price() As Decimal
End Interface
combobx problem its Excellent answered by #Bjørn-Roger Kringsjå.
Here are additional improvements:
ObtainVehicleTypes:
Private Function ObtainVehicleTypes() As IEnumerable(Of Type)
Dim IVehicleType = GetType(IVehicle)
Dim types As IEnumerable(Of Type) = _
Reflection.Assembly.GetExecutingAssembly.GetTypes.Where(
Function(t) IVehicleType.IsAssignableFrom(t) AndAlso t.IsClass = True)
Return types
End Function
Private Sub AddTypesOfVehicles()
Dim types As IEnumerable(Of Type) = ObtainVehicleTypes().ToArray()
ComboBox1.DisplayMember = "Name"
ComboBox1.DataSource = types
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim type As Type = TryCast(ComboBox1.SelectedItem, Type)
Dim v As IVehicle = TryCast(Activator.CreateInstance(type), IVehicle)
v.SayType()
End Sub

VB.NET CheckedListBox Tag?

Is there a Tag for an Item in the CheckedListBox? Or something similar? I would like to be able to store and ID associated with the item that I'm displaying.
You don't need a Tag property. The control accepts any object, that means you don't have to put just strings in it. Make a class that has a string (and overrridden ToString()) and any other data members you need.
Public Class Form1
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
MyBase.OnLoad(e)
CheckedListBox1.Items.Add(New MyListBoxItem() With {.Name = "One", .ExtraData = "extra 1"})
CheckedListBox1.Items.Add(New MyListBoxItem() With {.Name = "Two", .ExtraData = "extra 2"})
End Sub
Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
For Each obj As Object In CheckedListBox1.CheckedItems
Dim item As MyListBoxItem = CType(obj, MyListBoxItem)
MessageBox.Show(String.Format("{0}/{1} is checked.", item.Name, item.ExtraData))
Next
End Sub
End Class
Public Class MyListBoxItem
Private _name As String
Private _extraData As String
Public Property Name As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Property ExtraData As String
Get
Return _extraData
End Get
Set(ByVal value As String)
_extraData = value
End Set
End Property
Public Overrides Function ToString() As String
Return Name
End Function
End Class
(The overridden ToString() dictates what will be displayed in the box.)
You can inherit your own control from CheckedListBox and create a property, in C# it would be like this, the rest of the functionality remains the same as it is inherited so no further additional code required:
public class MyCheckedListbox : System.Windows.Forms.CheckedListBox{
private object thisObj;
public object Tag{
get{ return this.thisObj; }
set{ this.thisObj = value; }
}
}
Edit: Decided to include the VB.NET version for everyone's benefit also...
Public Class MyCheckedListBox Inherits System.Windows.Forms.CheckedListBox
Private thisObj As Object
Public Property Tag As Object
Get
Tag = thisObj
End Get
Set (objParam As Object)
thisObj = objParam
End Set
End Property
End Class
Of course, this is plain and uses boxing but works nicely...
Hope this helps
Translation of tommieb75 answer to VB.NET:
Public Class MyCheckedListbox
Inherits System.Windows.Forms.CheckedListBox
Private thisObj As Object
Public Property Tag() As Object
Get
Return Me.thisObj
End Get
Set(ByVal value As Object)
Me.thisObj = value
End Set
End Property
End Class
I use the translator at www.developerfusion.com/tools