Binding Combobox to Object Datasource - vb.net

I've got a form bound to an object datasource. It has one text box and one combo box. I set up one binding source for the main object and one binding source for the combo box. When I run the form, the text box is bound correctly, and the list of values in the combo box is bound correctly, but the ValueMember of the combo box isn't working correctly.
The combo box shows the correct list, but it's selected index is 0 instead of what it should be 2. When I change the value in the text box, it's bound object's Property.Set method is called correctly, but the same Property.Set method is not called for the combo box.
I know I can hack up the OnSelectedIndex change methods in the form, but I would like to know what I am doing wrong in just using the Bindings.
Here is the code on the form:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
Dim NameValueBindingSource1 As New BindingSource()
Dim WorkOrderBindingSource1 As New BindingSource
'Create main object to bind to
Dim wo As New WorkOrder
wo.WOIndex = "2012-0111"
wo.WorkOrderType = 3
'Create list object for combo box
Dim NameValues As BindingList(Of NameValue)
NameValues = FillNameValueList()
'Bind Text Box to Binding Source
WorkOrderBindingSource1.DataSource = wo
WOIndexTextBox1.DataBindings.Add("Text", WorkOrderBindingSource1, "WOIndex")
'Bind Combo Box to Binding Source
NameValueBindingSource1.DataSource = NameValues
WorkOrderTypeCombo.DataSource = NameValueBindingSource1
WorkOrderTypeCombo.DisplayMember = "Value"
WorkOrderTypeCombo.ValueMember = "Code"
End Sub
Function FillNameValueList() As BindingList(Of NameValue)
Dim bl As New BindingList(Of NameValue)
Dim nv As NameValue
nv = New NameValue
bl.Add(New NameValue("Short", 0))
bl.Add(New NameValue("Middle", 1))
bl.Add(New NameValue("Long", 2))
bl.Add(New NameValue("Very Long", 3))
Return bl
End Function
End Class
Here's the code for the main object - "WorkOrder"
Imports System.ComponentModel
Public Class WorkOrder
Implements IEditableObject
Implements INotifyPropertyChanged
Private mWOIndex As String
Private mWorkOrderType As Integer
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Property WOIndex As String
Get
Return mWOIndex
End Get
Set(value As String)
mWOIndex = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("WOIndex"))
End Set
End Property
Public Property WorkOrderType As Integer
Get
Return mWorkOrderType
End Get
Set(value As Integer)
mWorkOrderType = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("WorkOrderType"))
End Set
End Property
Public Sub BeginEdit() Implements System.ComponentModel.IEditableObject.BeginEdit
End Sub
Public Sub CancelEdit() Implements System.ComponentModel.IEditableObject.CancelEdit
End Sub
Public Sub EndEdit() Implements System.ComponentModel.IEditableObject.EndEdit
End Sub
End Class
Here's the code for the object used in the combo box
Imports System.ComponentModel
Public Class NameValue
Implements IEditableObject
Implements INotifyPropertyChanged
Private mValue As String
Private mCode As Integer
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Property Code As Integer
Get
Return mCode
End Get
Set(value As Integer)
mCode = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Code"))
End Set
End Property
Public Property Value As String
Get
Return mValue
End Get
Set(value As String)
mValue = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Value"))
End Set
End Property
Public Sub BeginEdit() Implements System.ComponentModel.IEditableObject.BeginEdit
End Sub
Public Sub CancelEdit() Implements System.ComponentModel.IEditableObject.CancelEdit
End Sub
Public Sub EndEdit() Implements System.ComponentModel.IEditableObject.EndEdit
End Sub
Public Sub New(InitValue As String, InitCode As Integer)
Value = InitValue
Code = InitCode
End Sub
End Class

In your code, you are merely assigning the DataSource to the ComboBox, but you're not establishing any DataBinding for it.
You need a line like this (using C# here):
WorkOrderTypeCombo.DataBindings.Add(new System.Windows.Forms.Binding("SelectedValue", WorkOrderBindingSource1, "WorkOrderType", true));
Hope this helps

Related

DataBind a Simple String Property to Textbox

I have a Simple Property called Customer as string
I want to bind this property to a Textbox.Text Databinding
I use the INotifyPropertyChanged Interface.
If I want to add the Databindings with
TextBox1.DataBindings.Add("Text", Customer, "Text")
I get an Error with:
You cannot bind text to the property or column for the DataSource.
Parameter name: dataMember
Public Class Form1
Implements INotifyPropertyChanged
Private _Customer As String = "DEFAULT"
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TextBox1.DataBindings.Add("Text", Customer, "Text")
End Sub
Public Property Customer As String
Get
Return _Customer
End Get
Set
_Customer = Value
NotifyPropertyChanged()
End Set
End Property
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
The reason is that you can't bind to a property directly, you need to bind to an object which contains the property. Also you can't use a property that is inside the Form1 class. You need to set up an instance of an object.
I've created a sample that uses a class named Customer with a single property called Name. I've also created a base class. This is not required, but is useful if you create multiple classes.
Public Class BaseNotify
Implements INotifyPropertyChanged
Friend Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Friend Sub NotifyPropertyChanged(<CallerMemberName()> Optional propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Public Class Customer
Inherits BaseNotify
Private _name As String = "DEFAULT"
Public Property Name As String
Get
Return _name
End Get
Set
If (_name = Value) Then Return
_name = Value
NotifyPropertyChanged()
End Set
End Property
End Class
Finally set up the form (I also renamed the textbox to something more meaningful.
Public Class Form1
Private _customer As Customer
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
BindProperties()
End Sub
Private Sub BindProperties()
_customer = New Customer()
Me.tbName.DataBindings.Add("Text", _customer, NameOf(Customer.Name))
End Sub
End Class
Using NameOf is recommended, as it won't break the code if you decide to change the property name at a later stage.

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.

Add rows to class bound datagridview

Hi I'm trying to bind a list of objects to a datagridview
Binding a existing list(Of is working but I'm trying to add or remove a object from, my dgv isn't updating.
Public Class Form1
Dim lt As New List(Of Test)
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
lt.Add(New Test("Mac", 2200))
lt.Add(New Test("PC", 1100))
dgv.DataSource = lt
lt.Add(New Test("Android", 3300)) 'This line won't appear in the dgv
End Sub
End Class
Public Class Test
Public Sub New(ByVal name As String, ByVal cost As String)
_name = name
_cost = cost
End Sub
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Private _cost As String
Public Property Cost() As String
Get
Return _cost
End Get
Set(ByVal value As String)
_cost = value
End Set
End Property
End Class
How can I add or remove or change a value from the dgv to the list and inverse?
NoiseBe
Change this line:
Dim lt As New List(Of Test)
To:
Imports System.ComponentModel
...
Private lt As New BindingList(Of Test)
When the collection contents will change, you should use a BindingList(Of T). This collection has events associated with it which make it aware of changes to the list contents.
If in addition to the list contents, the list items will change (like Test.Name), you should also implement INotifyPropertyChanged on the class itself.
Another way to do it:
dgv.DataSource = Nothing
lt.Add(New Test("Android", 3300))
dgv.DataSource = lt
This "resets" the DataSource so that the new contents will show up. However, it means that several other things get reset like selected items; if you are binding to a List/Combo control, you will also have to reset the ValueMember and DisplayMember properties as well.

sharing property between forms in vb

I've to pass the property between two forms.in Form2:i've created property IsFilterEnabled and accessing this in formmain
Public Property IsFilterEnabled() As Boolean
Get
Return mIsEnabled
End Get
Set(ByVal Value As Boolean)
mIsEnabled = Value
End Set
End Property
Public Sub FilterButton_Click() Handles FilterButton.Click
Dim currentRow As Data.DataRow
If vessel_NameComboBox.SelectedIndex > -1 Then
mIsEnabled = True
formMain.LoadData()
End If
End Sub
Formmain
Dim frm2 As New Form2
If frm2.IsFilterEnabled = True Then End
Data is lost IsFilterEnabled property as im creating new instance of form2.How to get the same instance of form2 in formmain to access the propery
Give your property a global scope, shareable between other members, by using the Shared keyword when declaring it.
Public Class Form2 : Inherits Form
Public Shared Property MyProperty As Object
End Class
Then you can access the shared value anytime:
Public Class Form1 : Inherits Form
Form2.MyProperty = "1st Hello World!"
Dim f As New Form2
f.MyProperty = "2nd Hello World!"
f.Show()
f.Dispose()
MsgBox(Form2.MyProperty.ToString)
End Class
Pass the property in the constructor of Form2.
Public Sub New(enabled As Boolean)
InitializeComponent()
'Process using enabled arg
End Sub

Databinding Not updating

I have been trying to get a label to databind to a readonly property. I have a much more complex project which I am implementing this in and it isn't working. I have been unsuccessful in getting help with this so I have created a much simpler version of the project and my databinding still isn't updating.
To replicate my issue you will need a form with a textbox, label and button, and then a class.
The code for the class is as follows
Imports System.ComponentModel
Public Class databinding
Implements INotifyPropertyChanged
Public Sub New()
numbers = New List(Of number)
End Sub
Public Property numbers As List(Of number)
Get
Return m_number
End Get
Set(value As List(Of number))
m_number = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("hnumber"))
End Set
End Property
Private m_number As List(Of number)
Public ReadOnly Property hnumber As Integer
Get
Dim list As IList(Of number) = (From t As number In numbers Select t Order By t.value Descending).ToList()
If (list.Count > 0) Then
If (IsNothing(list(0).value)) Then
Return "0"
Else
Return list(0).value
End If
End If
Return "0"
End Get
End Property
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
End Class
Public Class number
Public Property value As Integer
Get
Return t_number
End Get
Set(value As Integer)
t_number = value
End Set
End Property
Private t_number As Integer
End Class
The code for the form is as follows:
Public Class Form1
Public numberlist As New databinding
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Label1.DataBindings.Add(New Binding("text", numberlist, "hnumber"))
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim newnum As New number
newnum.value = TextBox1.Text
numberlist.numbers.Add(newnum)
End Sub
End Class
Now based on my understanding when you click the button a number from the textbox is added to this list, which happens, and the hnumber value updates, which using a breakpoint and a watch I can see also happens. From reading I need to implement inotifypropertychanged when I set the new number to get the label to re check the databind (which has been done).
However the label will stay at 0. If I run watch through Label1 I can see that under DataBindings > List > arrayList > (0) > System.Windows.Forms.Binding>DataSource>Databinding_test.databinding the details of the class (including the correct value for hnumber) is listed, so to me that shows that the Label does in fact know about the value it should be binding to.
Could someone please fill me in on what I am missing to make this all work, as it is almost causing me to pull out all of my hair.
Thanks,
mtg
I've tried to explain this to you before, and I will again.
The reason why your binding isn't updated is because you're adding the value to a list.
numberlist.numbers.Add(newnum)
However, if you "change" the list, this will trigger the propertychanged event.
numberlist.numbers.Add(newnum)
numberlist.numbers = numberlist.numbers '<--
Instead of using an IList<T> you should use the ObservableCollection<T> which allows you to track the changes made.
Public Class databinding
Implements INotifyPropertyChanged
Public Sub New()
Me.numbers = New ObservableCollection(Of number)
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property numbers As ObservableCollection(Of number)
Get
Return m_number
End Get
Set(value As ObservableCollection(Of number))
If (Not m_number Is value) Then
Unhook(m_number)
Hook(value)
m_number = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("hnumber"))
End If
End Set
End Property
Public ReadOnly Property hnumber As Integer
Get
If (Not numbers Is Nothing) Then
Dim list As IList(Of number) = (From t As number In numbers Select t Order By t.value Descending).ToList()
If (list.Count > 0) Then
If (IsNothing(list(0).value)) Then
Return 0
Else
Return list(0).value
End If
End If
End If
Return 0
End Get
End Property
Private Sub Hook(collection As ObservableCollection(Of number))
If (Not collection Is Nothing) Then
AddHandler collection.CollectionChanged, AddressOf Me.OnNumbersChanged
End If
End Sub
Private Sub OnNumbersChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("hnumber"))
End Sub
Private Sub Unhook(collection As ObservableCollection(Of number))
If (Not collection Is Nothing) Then
RemoveHandler collection.CollectionChanged, AddressOf Me.OnNumbersChanged
End If
End Sub
Private m_number As ObservableCollection(Of number)
End Class