Invalid cast exception - vb.net

I have a simple application to store address details and edit them. I have been away from VB for a few years now and need to refreash my knowledge while working to a tight deadline. I have a general Sub responsible for displaying a form where user can add contact details (by pressing button add) and edit them (by pressing button edit). This sub is stored in a class Contact. The way it is supposed to work is that there is a list with all the contacts and when new contact is added a new entry is displayed. If user wants to edit given entry he or she selects it and presses edit button
Public Sub Display()
Dim C As New Contact
C.Cont = InputBox("Enter a title for this contact.")
C.Fname = frmAddCont.txtFName.Text
C.Surname = frmAddCont.txtSName.Text
C.Address = frmAddCont.txtAddress.Text
frmStart.lstContact.Items.Add(C.Cont.ToString)
End Sub
I call it from the form responsible for adding new contacts by
Dim C As New Contact
C.Display()
and it works just fine. However when I try to do something similar using the edit button I get errors - "Unable to cast object of type 'System.String' to type 'AddressBook.Contact'."
Dim C As Contact
If lstContact.SelectedItem IsNot Nothing Then
C = lstContact.SelectedItem()
C.Display()
End If
I think it may be something simple however I wasn't able to fix it and given short time I have I decided to ask for help here.
I have updated my class with advice from other members and here is the final version (there are some problems however). When I click on the edit button it displays only the input box for the title of the contact and actually adds another entry in the list with previous data for first name, second name etc.
Public Class Contact
Public Contact As String
Public Fname As String
Public Surname As String
Public Address As String
Private myCont As String
Public Property Cont()
Get
Return myCont
End Get
Set(ByVal value)
myCont = Value
End Set
End Property
Public Overrides Function ToString() As String
Return Me.Cont
End Function
Sub NewContact()
FName = frmAddCont.txtFName.ToString
frmStart.lstContact.Items.Add(FName)
frmAddCont.Hide()
End Sub
Public Sub Display()
Dim C As New Contact
C.Cont = InputBox("Enter a title for this contact.")
C.Fname = frmAddCont.txtFName.Text
C.Surname = frmAddCont.txtSName.Text
C.Address = frmAddCont.txtAddress.Text
'frmStart.lstContact.Items.Add(C.Cont.ToString)
frmStart.lstContact.Items.Add(C)
End Sub
End Class

You're doing
frmStart.lstContact.Items.Add(C.Cont.ToString)
That is, you are adding strings to lstContact. Of course lstContact.SelectedItem() is a string!

Since you didn't post all of your code, I'll give you my best guess which is:
lstContact seems like it is bound to an IEnumerable of strings instead of IEnumerable of Contacts.
Therefore, when you get lstContact.SelectedItem() it is returning a string instead of a Contact (and throwing an error when trying to cast string as Contact).
It'd be more helpful if you could post the code bit that binds lstContact to a data source.

Related

Why can't I bind a list of custom objects to datagridview?

I have looked through your questions as well as elsewhere on the internet for the past two hours and cannot find a solution for my problem anywhere, or at least I didn't understand it if I did. I apologize in advance if this appears redundant or inane. Let me be clear: the issue is that I am somehow NOT implementing the approach correctly, but I understand (or think I do) how it is supposed to be done.
I have a gridview on a form in which I want to display custom objects representing appointments. I want to bind to my appointment objects not a datatable (which was successful). However, the below approach will not display my appointment objects in the grid although it appears correct. Furthermore, adding objects directly to the bindingsource's internal list also fails to show them in the grid, as does setting the datasource of the gridview to the bindinglist directly. I have no idea what I am doing wrong! Please help, this seems to make no sense at all and is driving me crazy.
Public Sub DisplayItems()
Dim bindingsource As BindingSource
Dim appointment As ClsAppointment
Dim appointments As System.ComponentModel.BindingList(Of ClsAppointment)
Dim iterator As IEnumerator
appointments = New System.ComponentModel.BindingList(Of ClsAppointment)
bindingsource = New BindingSource
iterator = Items
While iterator.MoveNext '
appointment = iterator.Current
appointments.Add(appointment)
End While
bindingsource.DataSource = appointments
gridview.DataSource = bindingsource
Debug.Print("")
Debug.Print("DisplayItems()...")
Debug.Print("GridView has " & gridview.Rows.Count & " rows")
End Sub
Public Class ClsAppointment
Public FirstName As String
Public LastName As String
Public Day As String
Public [Date] As Date
Public Time As Date
Public Address As String
Public City As String
Public State As String
Public Zip As String
Public Description As String
End Class
========================================================================================
Note: DisplayItems() is a method of an adapter (ItemEditor) which I chose not to show for simplicity's sake. Another method (Items) returns the adapter's collection of items (appointments) via an enumerator. I have tested this and know that the enumerator is returning the items so the problem is not this.
You can not bind to public fields of an object. As Microsoft states "You can bind to public properties, sub-properties, as well as indexers, of any common language runtime (CLR) object." Msdn- Binding Sources Overview.
Change your ClsAppointment class to this :
Public Class ClsAppointment
Property FirstName As String
Property LastName As String
Property Day As String
Property [Date] As Date
Property Time As Date
Property Address As String
Property City As String
Property State As String
Property Zip As String
Property Description As String
End Class
Allow me to simplify your code:
Public Sub DisplayItems()
gridview.DataSource = Me.Items()
Debug.Print("")
Debug.Print("DisplayItems()...")
Debug.Print("GridView has " & gridview.Rows.Count & " rows")
End Sub
Try this, and let us know what errors you get. I know you might eventually need the BindingSource, but for the moment let's cut that out of the picture and see how things work.

create a message box on application start requiring users name then display it in the program?

I would like a message box to pop-up at the start of the program asking what the customers name is. I then want this name to be displayed in a label that i have already created.
I went to application events and entered the code below after finding a few helpful tips around the internet. I get a message box allowing me to enter the customers name, so that bit works fine. The problem comes when I try to change the label. It says that 'label4' is not declared. I'm guessing that the application events section is a completely different section from the other forms.
If anyone could explain how I would go about doing this correctly and also explain the difference between so I can learn from this that would be great.
Dim message, title, defaultValue, myvalue As String
' Set prompt.
message = "Please Enter The Customers Name"
' Set title.
title = "New Customer"
' Set default value.
defaultValue = ""
' Display message, title, and default value.
myValue = InputBox(message, title, defaultValue)
label4.text = myvalue
The controls of a form a defined locally to that form, i.e. they are private members of that form and cannot be accessed from outside. Now it depends how your code is organized. If you are calling the InputBox before opening the main form then you need a way to pass the customer name to the main form. You can do that in the constructor the main form.
' In the main form
Public Sub New(ByVal customerName As String)
InitializeComponent() ' This call is required by the Windows Form Designer.
label4.Text = customerName
End Sub
Then you can create the main form like this (see also How to find the main() entry point in a VB.Net winforms app?)
Public Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
...
myValue = InputBox(message, title, defaultValue)
Dim frm As Form1 = New Form1(myValue)
' Starts the application.
Application.Run(frm)
End Sub

Linking Object to UI

What is the best method to use to link a class to a user form?
Sorry to be hypothetical, but using actual code will lead to a question that is pages long.
Let's say I have a class that holds data about a person.
<<class Person>>
Public FirstName as String
Public LastName as String
Public PhoneNumber as String
<<end class>>
I put that data into a VBA UserForm listview.
Now, let's say I want to change the phone number to 555-555-1234 if the user clicks on that record in the listview.
I can read the interaction with the listview with the item click event.
Private Sub lvExample_ItemClick(ByVal Item As MSComctlLib.ListItem)
' Change the phone number
End Sub
What is the best way to get from Item in the above code to my actual object? Should I add an GUID to each object and put that in the tag of the listitem, then look it up? Should I add the listitem from the listview into the class so I can loop through all my people and then see if the Item from _ItemClick equals the Item from the object?
The easiest way is to use either the Index property, if you don't have any unique identifier, or the Key property if you do, of the ListItem.
If you choose to use the Index property, then you can't (or at least it will greatly complicate it) add any functionality to rearrange the order of list items.
You would have populated the ListView based on the objects in a collection/recordset via the ListView.ListItems.Add method. You can use the Index property to get back to that original object based on the order of items in ListItems corresponding to the order of items in your original collection of objects.
Alternatively, if you prefer the greater flexibility of using a unique key but don't wish to modify the underlying object, then you can trivially construct a unique key (the simplest being CStr(some incrementing number)) as you add each object to ListItems, storing the keys alongside the objects.
You can then use the .Key property of the ListItem. The benefit here is the user can be allowed to modify what items are in, delete stuff etc without you having to invalidate your control and re-add all objects in order to keep the linkage between Index in source and index in the list.
E.g.:
Private persons As Collection
Private Sub lvExample_ItemClick(ByVal Item As MSComctlLib.ListItem)
' Change the phone number:
'Method 1, using the index of listitem within listitems
'to tie back to index in persons collection
'Don't allow rearranging/sorting of items with this method for simplicity
With persons.Item(Item.Index)
.PhoneNumber = "555-555-1234"
'...some stuff
End With
'Method 2, using a unique key
'that you generate, as the underlying person object doesn't have a necessarily unique one
'Items would have been added in a method similar to AddItemsExample1() below
With persons.Item(Item.Key)
.PhoneNumber = "555-555-1234"
'...some stuff
End With
End Sub
Sub AddItemsExample1()
'Storage requirements vs. your existing recordset or whatever
'are minimal as all it is storing is the reference to the underlying
'person object
'Adapt per how you have your existing objects
'But basically get them into a keyed collection/dictionary
Dim i As Long
Set persons = New Collection
For Each p In MyPersonsSource
persons.Add p, CStr(i)
i = i + 1
Next p
'By keying them up, you can allow sorting / rearranging
'of the list items as you won't be working off of Index position
End Sub
Finally, another way if you have them in a recordset returned by a DB is to add a new field (I imagine you have an existing object field) and do run an UPDATE query on your records populating it with an incrementing number (this should only effect the local recordset (check the recordset settings first of course!)). Use this as the key.
You mention in a comment to your question that you get the data from SQL. For all normal purposes with a list box it is still probably easiest to just run them through a Collection object as detailed above, but if you have e.g. 4 fields from SQL for a record in a recordset then you don't hav an 'object' in the sense of being able to call properties on it. Please specify in your question or in a comment to this if you do so, as there may be a better treatment to answer your question or the actual update operation will likely require different syntax or semantics (particularly if you need to propagate any update back to the source) :)
I would use a event driven approach.
Person will have properties instead of variables and assigning those properties will raise a event.
User form will have WithEvents Person, so that changes in related person will trigger user form code.
Person class:
Public Event Changed()
Private pFirstName As String
Private pLastName As String
Private pPhoneNumber As String
Public Property Get FirstName() As String
FirstName = pFirstName
End Property
Public Property Let FirstName(ByVal v As String)
pFirstName = v
RaiseEvent Changed
End Property
Public Property Get LastName() As String
LastName = pLastName
End Property
Public Property Let LastName(ByVal v As String)
pLastName = v
RaiseEvent Changed
End Property
Public Property Get PhoneNumber() As String
PhoneNumber = pPhoneNumber
End Property
Public Property Let PhoneNumber(ByVal v As String)
pPhoneNumber = v
RaiseEvent Changed
End Property
Event catching in user form:
Public WithEvents ThisPerson As Person
Private Sub ThisPerson_Changed()
'Update user form
End Sub
So whenever you assign YourForm.RelatedObject = SomePerson, any changes done to SomePerson will trigger code in YourForm.
Since you can't change returned ListItem, I have another solution:
Add a related ListItem to your Person class:
Public FirstName as String
Public LastName as String
Public PhoneNumber as String
Public RelatedObject As Object
When populating TreeView, assign it:
Set SomeListItem = SomeTreeView.ListItems.Add(...)
Set SomePerson.RelatedObject = SomeListItems
So whenever you have a change in a ListItem:
Private Sub SomeListView_ItemClick(ByVal Item As MSComctlLib.ListItem)
Dim p As Person
For Each p In (...)
If p.RelatedObject Is Item Then
'Found, p is your person!
p.PhoneNumber = ...
End If
Next
End Sub
Alternatively, if you don't want to change your Person class, you can encapsulate it in another class, eg, PersonToObject:
Public Person As Person
Public RelatedObject As Object
And use PersonToObject as link objects.

how to put the data with combobox to another form?

I am doing a payment system and what I really want to happen is that when I select the payment type(full or installment) the data that I inputted will show on another form.
For example, after I filled up my information and selected the payment method, and after I saved it, if my payment method is "full", the information will appear on the "full_pay" form or if I selected "installment_pay" the information will appear on the "installment" form.
if paytype.text="Full" Then
'the info will go to the full_pay form
you can write propertys in your new form and pass them from your old form
'Code on Main form to open form and Set parameters
Dim frmFull_pay As New full_pay
frmFull_pay.paytype = paytype.text
frmFull_pay.show()
'Code on full_pay
Private _paytype As String
Public Property paytype As String
Get
Return _paytype
End Get
Set(ByVal Value As String)
_paytype = Value
End Set
End Property
Now you can access all the data you set on Porpertys

VB.NET How To Tell If New Class Instance Was Ended Early?

I'm trying to detect if the Sub New() in my class has ended early due to missing fields. Below is a sample of my code:
Class Account
Public Sub New(ByVal Firstname As String, ByVal LastName As String, ByVal Username As String, ByVal Email As String, ByVal Password As String)
' Check For Blank Fields
If Firstname = "" Or LastName = "" Or Username = "" Or Email = "" Or Password = "" Then
MessageBox.Show("Please Enter All Information Requested")
Exit Sub
End If
' Set Public Variables Of Class
Firstname = Firstname
LastName = LastName
Username = Username
Email = Email
Password = Password
End Sub
Public Shared Sub OtherUse()
End Sub
End Class
' Create New Instance
Dim Process As New Account(txtFirstName.Text, txtLastName.Text, txtUsername.Text, txtEmail.Text, txtPassword.Text)
' HERE - How Can I Catch The Early Exit From The Instance Due To Potential Missing Fields?
' Use Instance For Other Use
Process.OtherUse()
How would I catch the Exit Sub from the class in the parent form to prevent the further processing of Process.OtherUse()?
You're approaching this problem the wrong way. Validate the input first, and then once the input is valid, create a new Account using New.
Another option would be to initialize data in New without checking if it's valid or not, then have an IsValid method in that class that you would call from another class to know whether or not the messagebox should be shown.
One way or another, the Account class shouldn't be responsible for a UI concern like showing a MessageBox on the screen. And the constructor should only be responsible for constructing the object, not validating the input, because you can't "abort" a constructor. You have a reference to a new object even though you call Exit Sub.
A fourth alternative, in addition to the three mentioned by Meta-Knight:
Have the constructor throw an exception when the parameters aren’t valid.
As an aside, your use of Or, while working, is a logical error: And and Or are bitwise arithmetic operations. You want a logical operation, OrElse (there’s also AndAlso). These may seem similar to Or and And and in this particular case, both happen to work. But they are actually quite different semantically, bitwise operations are just wrong here (it’s a pity that this code even compiles. The compiler shouldn’t allow this).