Two way databinding for WinForms ComboBox with separate source for Items and SelectedItem - vb.net

I hope my title makes sense. I want to do the following:
Bind ComboBox Items to a list
Bind the SelectedItem to a property (separate from the list)
Update the SelectedItem of the ComboBox once something in the object changes (for example, once the property that is bound to SelectedItem changes programatically, the SelectedItem of the ComboBox also changes accordingly)
I have already achieved to do the first two things but I am stuck on point 3. The databinding only works one-way. Meaning, when I select a new Item from the ComboBox, the object in code behind changes accordingly. But if I change the object in code behind or if it has an initial value, the SelectedItem of the ComboBox is not updated/preselected.
cbErledigungsArt.DataSource = _erledigungsArten.ToArray()
cbErledigungsArt.DisplayMember = "Beschreibung"
cbErledigungsArt.ValueMember = "ID"
cbErledigungsArt.DataBindings.Add("SelectedItem", _feststellung, "ErledigungsArt")
Festellung is a custom type:
Public Class FeststellungDTO
Public Property Jahr() As Integer
Public Property ErledigungsArt() As ErledigungsArtDTO
End Class
Erledigungsart is the property that is bound to the ComboBox
Public Class ErledigungsArtDTO
Public Property ID() As Integer
Public Property Beschreibung() As String
End Class
I want to be able to say, for example:
Dim _feststellung As New FeststellungDTO() With {
.Jahr = 2015,
.ErledigungsArt = New ErledigungsArtDTO() With {.ID = 0, .Beschreibung = "Bla"}
}
Dim _erledigungsArten As New List(Of ErledigungsArtDTO)(
{
New ErledigungsArtDTO() With {.ID = 0, .Beschreibung = "Bla"},
New ErledigungsArtDTO() With {.ID = 1, .Beschreibung = "Blu"}
}
)
cbErledigungsArt.DataSource = _erledigungsArten.ToArray()
cbErledigungsArt.DisplayMember = "Beschreibung"
cbErledigungsArt.ValueMember = "ID"
cbErledigungsArt.DataBindings.Add("SelectedItem", _feststellung, "ErledigungsArt")
'SelectedItem will become "Bla"
_feststellung.ErledigungsArt = New ErledigungsArtDTO() With {.ID = 1, .Beschreibung = "Blu"}
'SelectedItem will become "Blu"
'User now selects "Bla" from the ComboBox and the value of _festellung.ErledigungsArt will change accordingly
Is this possible?

Related

Bind object in datasource to datagridview combobox VB.Net

I am facing a problem in my VB.Net code.
I have an object factuurregel with the following properties:
Property Id As Integer
Property Medewerker As Medewerker
Property Datum As DateTime
Property Activiteit As Activiteit
Property Omschrijving As String
Property Tijd As Decimal
Property Tarief As Decimal
Property Specificatie As Boolean
Property Factureren As Boolean
Property NietFactureren As Boolean
Property Klant As FactKlant
Property Project As String
Another object I am using there is activiteit:
Property Id As Integer
Property Omschrijving As String
Property FactuurRegel As String
In a form, I have a datagrid, where I use following code:
Dim lActiviteiten As New List(Of Activiteit)
lActiviteiten = LoadActiveiten()
Dim dgAct As DataGridViewComboBoxColumn = DataGridUren.Columns(3)
dgAct.DataSource = lActiviteiten
AND
loadmwlist()
Dim dgMW As DataGridViewComboBoxColumn = DataGridUren.Columns(1)
With dgMW
.ValueMember = dtmw.Columns("ID").ToString
.DisplayMember = dtmw.Columns("Naam").ToString
.DataSource = dtmw.Copy
End With
where loadmwlist fills a table.
With both codes, the combobox in the datagrid is filled correct.
The problem is, when I set the object factuurregel as datasource, only in the medewerker combobox the right value is displayed.
To be complete, the object medewerker is build like this:
Property Id As Integer
Property Naam As String
Why is the datasource correct when I use a table as datasource for the combobox, but not when I use a list of objects?
jmcilhinney said:
Why are you doing this: .ValueMember = dtmw.Columns("ID").ToString
instead of this: .ValueMember = "ID"? Where are you setting the
DisplayMember and ValueMember of dgAct? I'm not sure that you know
what those properties actually do, so you probably ought to read up on
them. –
after which I added
dgAct.DisplayMember = "Omschrijving"
dgAct.ValueMember = "Id"
Code is working as it should now.

Finding combobox item

For this question I made a simple class:
Public Class ListBoxEntry
Public Property ID As Integer
Public Property Text As String
Public Overrides Function ToString() As String
Return Text
End Function
End Class
I create some instances of this class and add them into a combobox:
...
While DR.Read
LI = New ListBoxEntry
LI.ID = DR("ID") ' ID is an integer value
LI.Text = DR(Feldname) ' Feldname is a string
cmbList.Items.Add(LI)
End While
I cannot get a working code for setting the combobox to a specific value by code.
E.g. these are my three entries (ID - Feldname):
1 - One (value 1, shown text in combobox "One")
2 - Two (value 2, shown text in combobox "Two")
3 - Three (value 3, shown text in combobox "Three")
Combobox1.SelectedIndex = somehow(2) <- here I want to set the combobox to the second entry (2), so "two" is selected
Which peace of code to I need?
You should add the instances of your class to an array or collection, then bind that to your ComboBox, e.g.
With ComboBox1
.DisplayMember = "Text"
.ValueMember = "ID"
.DataSource = myList
End With
You can then assign an ID value to the SelectedValue property of the ComboBox to select the item with that ID, e.g.
ComboBox.SelectedValue = 2
That would display "Two" in the control.

How to bind the selected member of a ComboBox?

I have a ComboBox that is populated with objects of type ProfileName
Private Class ProfileName
Public Property Name As String
Public Property File As String
Public Property ProductVersion As String
End Class
These items are created added to the combo box after a deserialising a bunch of files and copying some of the values from the resulting objects:
pcb.DisplayMember = "Name"
For Each F As FileInfo In ProfileFiles
Dim Reader As StreamReader = F.OpenText()
Dim Serialize As Serializer = New Serializer()
Dim SerializedData As String = Reader.ReadToEnd()
Dim P As Profile = Serialize.DesearializeObject(Of Profile)(SerializedData)
If P.Type = Profile.ProfileType.Product Then
Dim PN As ProfileName = New ProfileName()
PN.File = F.Name
PN.ProductVersion = P.ProductVersion
PN.Name = P.ProductName & " - " & P.ProductVersion
pcb.Items.Add(PN)
End If
Reader.Close()
Next
Then if a user opens one of these files, the file will be again deserialised resulting in a Profile object with a 'ProductName' property that should match one of the items already on the ComboBox items list, so I'd like for the ComboBox to show that as the selected item.
i.e.
-On form load the ComboBox is populated with all possible product names.
-When a profile file is opened the product that the profile uses is automatically selected in the ComboBox.
I've been playing with
ProductComboBox.DataBindings.Add("SelectedValue", CurrentProfile, "ProductName")
and permutations thereof, but can't seem to get it right.
You cant mix and match - put objects into the items collection and use the data binding methods/elements. Databinding basics:
Public Class Profile
Public Property Name As String
Public Property File As String
Public Property ProductVersion As String
Public Overrides Function ToString() As String
Return String.Format("{0} ({1})", Name, ProductVersion)
End Function
End Class
The ToString() controls what will be displayed when you cant specify the property to display. Note, these should be properties becaus mere Fields will be treated differently.
Then a container for them. This will be the DataSource for the cbo.
Private Profiles As List(Of Profile)
...
' create instance of list and populate from where ever:
Profiles = New List(Of Profile)
Profiles.Add(New Profile With {.Name = "Default", .File = "foo",
.ProductVersion = "1.0"})
Profiles.Add(New Profile With {.Name = "Ziggy", .File = "bat",
.ProductVersion = "1.9.8"})
Profiles.Add(New Profile With {.Name = "Zoey", .File = "bar",
.ProductVersion = "1.4.1"})
Rather than putting the Profile objects into the Items collection, bind the control to the List:
cboP.DataSource = Profiles
cboP.DisplayMember = "Name"
If you omit the property to display, ToString() will be shown (or WindowsApp1.Profile if you did not override it). Note: When using a DataSource you no longer add or delete from the control's Items collection - it will yell at you. Instead manage the underlying source, your List(Of Profile) in this case.
To change the selection, for example to the one for "Ziggy":
Dim n As Int32 = Profiles.FindIndex(Function(f) f.Name = "Ziggy")
If n > -1 Then
cboP.SelectedIndex = n
End If
You can also set SelectedItem after you find the Profile instead, but I tend to use index. Even though the list is a new actor, serializing the entire thing is easy:
' serializing the List acts on all the profiles in it
Dim json = JsonConvert.SerializeObject(Profiles)
File.WriteAllText("C:\Temp\Profiles.json", json)
Read it back:
json = File.ReadAllText("C:\Temp\Profiles.json")
Dim newPs = JsonConvert.DeserializeObject(Of List(Of Profile))(json)
Its a bit simpler than looping thru a set of files. List(of T) has a full set of methods and extensions to remove, sort, find etc so you should gain functionality over the items collection or array.
Alternatively, you could keep the one file per structure, but add the deserialized Profile objects to a List(of Profile) rather than the Items collection.

VB.net DataBindingSource: bind an Integer into a Combobox

I have a populated combobox that I would like to bind to an Integer.
The wanted behaviour is that when the datasource changes to a new object, the combobox selected value changes accordingly to the Integer binded to it.
The problem is that the default binding(I simply dragged and dropped my Integer from the datasource pannel over the combobox) binds the text property of the combobox, not the SelectedIndex.
Right now I kinda make it work this way:
BindingSourceName.DataSource = Object
cmbType.SelectedIndex = Object.Type
But I have to manually manage it also while saving the binded object upon User modifications.
Is there a proper way to change the default bindig bahaviour to make it automatically bind the Integer to the SelectedIndex of my combobox?
The closest I can get to what you want is if I use a ValueMember for the index. For example:
I fill my Combobox with a couple of Items like this:
Public Class NameValue
Property Name as String
Property Type as Integer
Public Sub New(ByVal pName as String, ByVal pVal as Integer)
Name = pName
Type = pValue
End Sub
End Class
Dim cmbList As New List(Of NameValue)
cmbList.Add(New NameValue("Name",1)
cmbList.Add(New NameValue("Name2",2)
cmbList.Add(New NameValue("Name3",3)
cmbType.Items = cmbList
cmbType.ValueMember = "Value"
cmbType.DisplayMember = "Type"
Now the first stage is complete. The Combobox contains three items with a name and a value bound together. Next step is to setup what you are asking for: Bind the ComboboxValue to the Object class.
cmbType.DataBindings.Add(New Binding("SelectedValue", BindingSourceName, "Type", False, DataSourceUpdateMode.OnPropertyChanged))
As soon as the BindingSource "BindingSourceName.DataSource" changes, the combobox should be updated. And if you change the combobox, the Object.Type will change to the selected Value.

Add object to listbox and use different string representations depending on the listbox?

I have a coupple of listboxes with the same object type in all of them, the problem i have is that i dont want the object displayed with the same ToString() method in all of the listboxes. Is there a way to solve this?
At the moment im adding strings to the listboxes and then i use the selected string to search a list of objects for the correct one but i dont like that solution at all.
Suppose to have a class for Employees like this one:
Public Class Employee
Public Property ID As Integer
Public Property FirstText As String
Public Property SecondText As String
' and go on with other properties
....
End Class
Now when you populate your listboxes, you set the DisplayMember and ValueMember of your listboxes to two different property of Employee
Dim myList As ArrayList = New ArrayList()
myList.Add(New Employee() With {.ID = 1, .FirstText = "John Doe", .SecondText = "Doe John"})
myList.Add(New Employee() With {.ID = 2, .FirstText = "Mark Ross", .SecondText = "Ross Mark"})
ListBox1.DataSource = myList
ListBox2.DataSource = myList
ListBox1.ValueMember = "ID"
ListBox1.DisplayMember = "FirstText"
ListBox2.ValueMember = "ID"
ListBox2.DisplayMember = "SecondText"