2

I am kinda desperate, because yesterday it was working but today this is not working anymore, let me explain, what I am trying to do:

I want to set the DataSource of a ComboBox to all enum values in a specific enum. Just like that:

cmbType.DataSource = m_addViewPresenter.Types;

where Types is a BindingList and is being initialised like that:

public BindingList<MyEnum> Types
{
    get { return m_types; }
    set
    {
        m_types = value;
        OnPropertyChanged();
    }
}

[ImportingConstructor]
public AddViewPresenter()
{
    Types = new BindingList<MyEnum>(
              Enum.GetValues(typeof(MyEnum))
                         .Cast<MyEnum>().ToList());
}

Further more, I want to bind the current item selected to a property. Since ComboBox.SelectedItem doesn't fire a INotifyProperty-event, I am using ComboBox.SelectedValue.

cmbType.Bind(m_addViewPresenter, c => c.SelectedValue, m => m.SelectedValue);

public static void Bind<TComponent, T>(
    this TComponent component, T value,
    Expression<Func<TComponent, object>> controlProperty, 
    Expression<Func<T, object>> modelProperty)
    where TComponent : IBindableComponent
    where T : INotifyPropertyChanged
{
    var controlPropertyName = PropertyNameResolver.GetPropertyName(controlProperty);
    var modelPropertyName = PropertyNameResolver.GetPropertyName(modelProperty);
    component.DataBindings.Add(new Binding(controlPropertyName, value, modelPropertyName, false, DataSourceUpdateMode.OnPropertyChanged));
}

It worked fine yesterday, but something messed things up and today I am only getting an InvalidOperationException:

Cannot set the SelectedValue in a ListControl with an empty ValueMember.

I know it's digging in the dark, but can anyone brainstorm with me and find out, what's the problem? Thanks in advance!

Jannik
  • 2,310
  • 6
  • 32
  • 61
  • Looks like binding to `ComboBox.SelectedValue` requires setting 'ComboBox.ValueMember` – Ivan Stoev Oct 07 '15 at 17:08
  • You should really bind to `ComboBox.SelectedItem`, I don't know why do you think it will not fire property change notification, I think it does. – Ivan Stoev Oct 07 '15 at 17:12
  • I tried it with cmbType.Bind(m_addViewPresenter, c => c.SelectedValue, m => m.SelectedValue);, but it wasnt going into the setter. :/ – Jannik Oct 07 '15 at 17:17
  • @Jannik To use data binding to `SelectedValue`, use another class that contains a property as `DisplayMember` and a property as `ValueMember` and shape your enum values to a `List` as `DataSourec`, or simplye use your enum values as data source and bind to `SelectedItem` as mentioned by Ivan too. – Reza Aghaei Oct 07 '15 at 17:26
  • Like I told you, binding SelectedItem is not going into the setter of SelectedValue, it is not working. – Jannik Oct 07 '15 at 17:36
  • @Jannik Sorry, I was pretty sure there is `SelectedItemChanged` event, in in fact it exists internally, but they forgot to expose it! `SelectedValue` is fine, as soon as you don't need two way binding (i.e, from your class property to combobox) in which case it throws the exception you are getting. – Ivan Stoev Oct 07 '15 at 17:36
  • @IvanStoev It works for `SelectedItem` too. I bind my object property to it and checked the value after selecting new item in combobox, the new value had been set in my object property. – Reza Aghaei Oct 07 '15 at 17:40
  • @IvanStoev So, whats the fix ? :) – Jannik Oct 07 '15 at 17:41
  • @Jannik Just found it, see my answer. – Ivan Stoev Oct 07 '15 at 17:57

2 Answers2

7

Option 1

To use SelectedValue for data binding, Create a calss DataItem:

public class DataItem
{
    public MyEnum Value { get; set; }
    public string Text { get; set; }
}

Then use this code for data binding:

this.comboBox1.DataSource = Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>()
                                .Select(x => new DataItem() { Value = x, Text = x.ToString() })
                                .ToList();
this.comboBox1.ValueMember = "Value";
this.comboBox1.DisplayMember = "Text";

this.comboBox1.DataBindings.Add(
    new Binding("SelectedValue", yourObjectToBind, "PropertyOfYourObject"));

You can make generic DataItem<T> containing public T Value { get; set; } to make it more reusable in your project.

Option 2

To Use SelectedItem for data binding:

this.comboBox1.DataSource = Enum.GetValues(typeof(MyEnum));
this.comboBox1.DataBindings.Add(
    new Binding("SelectedItem", yourObjectToBind, "PropertyOfYourObject"));

Option 3

To use SelectedValue for data binding as another option suggested by Ivan Stoev, you can perform data to binding this way:

this.comboBox1.DataSource = Enum.GetValues(typeof(MyEnum));
this.comboBox1.DataBindings.Add(
    new Binding("SelectedValue", yourObjectToBind, "PropertyOfYourObject",
                true,  DataSourceUpdateMode.OnPropertyChanged));

Edit by Jannik:

The basic generic approach of option 1 would look like this:

public class ComboBoxItemWrapper<T>
{
    public T Value { get; set; }
    public string Text { get; set; }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
3

Ok, here is the case. You are correctly binding to ComboBox.SelectedValue. The problem is coming from the following line:

component.DataBindings.Add(new Binding(controlPropertyName, value, modelPropertyName, false, DataSourceUpdateMode.OnPropertyChanged));

It should be

component.DataBindings.Add(new Binding(controlPropertyName, value, modelPropertyName, true, DataSourceUpdateMode.OnPropertyChanged));

Shortly, always set Binding.FormattingEnabled to true (as in my example answering another question by you) when using WF data binding and you'll have no problems.

A modified example from my answer to Exchange UserControls on a Form with data-binding showing the case and the solution:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace Tests
{
    enum MyEnum {  Red, Green, }
    class Controller
    {
        public MyEnum SelectedValue { get; set; }
    }
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var form = new Form();
            var topPanel = new Panel { Dock = DockStyle.Top, Parent = form };
            var combo = new ComboBox { Left = 8, Top = 8, Parent = topPanel };
            topPanel.Height = combo.Height + 16;
            combo.DataSource = (MyEnum[])Enum.GetValues(typeof(MyEnum));
            var c = new Controller();
            combo.DataBindings.Add(new Binding("SelectedValue", c, "SelectedValue", true, DataSourceUpdateMode.OnPropertyChanged));
            form.BindingContextChanged += (sender, e) =>
            {
                // If you change combo binding formatting enabled parameter to false,
                // the next will throw the exception you are getting
                c.SelectedValue = MyEnum.Red;
            };
            var panel1 = new Panel { Dock = DockStyle.Fill, Parent = form, BackColor = Color.Red };
            var panel2 = new Panel { Dock = DockStyle.Fill, Parent = form, BackColor = Color.Green };
            Bind(panel1, "Visible", combo, "SelectedValue", value => (MyEnum)value == MyEnum.Red);
            Bind(panel2, "Visible", combo, "SelectedValue", value => (MyEnum)value == MyEnum.Green);
            Application.Run(form);
        }
        static void Bind(Control target, string targetProperty, object source, string sourceProperty, Func<object, object> expression)
        {
            var binding = new Binding(targetProperty, source, sourceProperty, false, DataSourceUpdateMode.Never);
            binding.Format += (sender, e) => e.Value = expression(e.Value);
            target.DataBindings.Add(binding);
        }
    }
}

You may also find useful the following thread Custom WinForms data binding with converter not working on nullable type (double?)

Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343