I have bound a ComboBox to an enum like so
Enum Foo
Bar
Baz
End Enum
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
FooComboBox.DataSource = [Enum].GetValues(GetType(Foo))
End Sub
I would like to change the selected value at a given time
Private Sub FooBar()
FooComboBox.SelectedValue = Foo.Bar
End Sub
But this throws an exception:
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
Additional information: Cannot set the SelectedValue in a ListControl with an empty ValueMember.
And inspecting the ComboBox I can indeed see that the ValueMember property is empty. But SelectedValue is equal to the value expected for the currently selected item when I read it, which would indicate that the ValueMember is set to the enum value as was expected.
A) If the ValueMember property is not set, where is the value in SelectedValue coming from?
B) If the binding is causing SelectedValue to contain the correct value, why the flip doesn't setting it work in just the same way?
C) What else do I need to do to make this bloody .Net work?
Note that there are indeed quite a few C# & WPF questions on this, none really explain this as far as I have seen and I haven't been able to work out what the correct vb syntax would be from the answers suggested them.
Particularly when the values are not sequential, you need to provide a way for the control to "map" the Name to the related value. Once you post Enum.GetValues or the Names to a CBO, they have become detached.
You can use something like a KeyValuesPair(of String, Int32) using the names as TKey and the values as TValue. The generic can make it seem more complex than it is. Since the key will always be String, and the value is usually an Int32 I tend to use a simple NameValuePair class for these:
Public Class NameValuePair
Public Property Name As String
Public Property Value As Int32
Public Sub New(n As String, v As Int32)
Name = n
Value = v
End Sub
Public Overrides Function ToString() As String
Return String.Format("{0}", Name)
End Function
End Class
That will associate any name with any value. The main thing is that you control what displays for ToString(). In this case, both the name and value come from an Enum; a simple method to create a List or Array of them:
Private Enum Stooges
Moe = 9
Larry = 99
Curly = 45
Shemp = 65
CurlyJoe = 8
End Enum
' method to convert any Enum to a collection of Named-Value pairs
Private Function EnumToPairsList(e As Type) As List(Of NameValuePair)
Dim ret As New List(Of NameValuePair)
Dim vals = [Enum].GetValues(e)
Dim names = [Enum].GetNames(e).ToArray
For n As Int32 = 0 To names.Count - 1
ret.Add(New NameValuePair(names(n), CType(vals.GetValue(n), Int32)))
Next
Return ret
End Function
EnumsToPairsList could return an array, or use KeyValuePair as desired. It can be expanded to use a Description in place of the name when present. Using it:
cbox1.DataSource = EnumToPairsList(GetType(Stooges))
cbox1.DisplayMember = "Name" ' use "Key" for a KVP
cbox1.ValueMember = "Value"
' set a value:
cbox1.SelectedValue = Convert.ToInt32(Stooges.Shemp)
Since you have "wrapped" the enum in the NVP class, that is what each SelectedItem will be (enclosed in an Object). When using a DataSource such as this, you would typically act in the SelectedValueChanged event and examine the SelectedValue. This is the main purpose of it: show the names to the user, but return the enum value to you in code.
The only "trick" is that it needs to be cast back to your enum:
Private Sub cbox1_SelectedValueChanged(...
Dim eItem As Stooges = CType(cbox1.SelectedValue, Stooges)
Console.WriteLine(eItem)
Console.WriteLine(eItem.ToString)
45
Curly
If you insist on using SelectedItem, you will have to cast from Object to NameValuePair, get the Value, then cast that to your Enum Type.
It is often useful to hold onto a copy of the data source so it resides somewhere other than only as a control datasource:
Private StoogesDS As List(Of NameValuePair)
...
StoogesDS = EnumsToPairsList(GetType(Stooges))
cbox1.DataSource = StoogesDS
This allows your code to still use the collection even when the form is not around. As noted above, each item is now a NameValuePair object.
cbox1.SelectedItem = StoogesDS.FirstOrDefault(Function(z) z.Name = Stooges.Shemp.ToString())
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With