3

If you run the following code without debugging (or with "Enable Just My Code" active), it will seem to work. But if you start it with debugger and with "Enable Just My Code" turned off, you will get this exception (which is then swallowed by library code):

System.ArgumentException occurred
  HResult=-2147024809
  Message=Complex DataBinding accepts as a data source either an IList or an IListSource.
  Source=System.Windows.Forms
  StackTrace:
       at System.Windows.Forms.ListControl.set_DataSource(Object value)
  InnerException: 

Here's a minimal version of my code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Forms;

static class Program {
    [STAThread]
    static void Main() {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm(new MainModel()));
    }
}

public class MainModel {
    public IList Options { get; } = new List<string> { "Foo", "Bar" };
}

class MainForm : Form {
    private System.ComponentModel.IContainer components;
    private ComboBox comboBox;
    private BindingSource bindingSource;
    private ErrorProvider errorProvider;

    public MainForm(MainModel mainModel) {
        InitializeComponent();
        bindingSource.DataSource = mainModel;
    }

    private void InitializeComponent() {
        components = new System.ComponentModel.Container();
        bindingSource = new BindingSource(components);
        errorProvider = new ErrorProvider(components);
        ((System.ComponentModel.ISupportInitialize) bindingSource).BeginInit();
        ((System.ComponentModel.ISupportInitialize) errorProvider).BeginInit();
        SuspendLayout();

        bindingSource.DataSource = typeof(MainModel);

        comboBox = new ComboBox();
        comboBox.DataBindings.Add(new Binding("DataSource", bindingSource, "Options", true));
        comboBox.DropDownStyle = ComboBoxStyle.DropDownList;
        Controls.Add(comboBox);

        errorProvider.ContainerControl = this;
        errorProvider.DataSource = bindingSource;

        ((System.ComponentModel.ISupportInitialize) bindingSource).EndInit();
        ((System.ComponentModel.ISupportInitialize) errorProvider).EndInit();
        ResumeLayout(false);
    }

}

The problem appears to be with the data binding. If I comment out the line comboBox.DataBindings.Add(new Binding("DataSource", bindingSource, "Options", true));, the exception does not occur.

I found many references to this exception online, but it seems that in all those cases, the problem was that the data source was not an IList (as stated by the exception message). In this case, however, Options is an IList. So I'm at a loss to explain the exception.

I noticed that the exception does not occur if I remove the ErrorProvider. But I can't figure out why that is; and I need the error provider in my actual program.

I'm working in Visual Studio 2015, targeting .NET 4.6.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Daniel Wolf
  • 12,855
  • 13
  • 54
  • 80

2 Answers2

3

It looks like you are declaring your DataBinding while your bindingSource is still inside a BeginInit - EndInit block. Try moving that line to after the EndInit line, or in the OnLoad override instead:

protected override void OnLoad(EventArgs e) {
  base.OnLoad(e);
  comboBox.DataBindings.Add(new Binding("DataSource", bindingSource, "Options", true));
}
LarsTech
  • 80,625
  • 14
  • 153
  • 225
1

Adding a Binding for DataSource property is wrong idea.

To set the DataSource you should assign something to DataSource rather than adding a Binding. Adding a Binding makes sense for SelectedValue for example, but not for DataSource.

Your code should be:

bindingSource.DataSource = typeof(MainModel);
bindingSource.DataMember = "Options";

comboBox = new ComboBox();
comboBox.DataSource = bindingSource;
comboBox.DropDownStyle = ComboBoxStyle.DropDownList;
Controls.Add(comboBox);

Then you will not receive any error.

Note: If for any reason you are just curious about how to avoid the error in your example, just set this.Visible = true or call this.Show() exactly after InitializeComponent to force control handles created and make the data-binding start working. This fix is not required for my code.

Why data-binding to DataSource property is not a good idea?

If you are going to bind DataSource property to Optins property of MainModel it means you are going to update Options property using the combo box! Also it means the MainModel class should implement INotifyPropertyChanged to notify the combo boxes about changes in Options property!(Keep in mind the options property is an IList) That's totally wrong idea!

So how can I inform the ComboBox about changes in the data source?

If you want to notify the ComboBox about change in the data-source, the data-source list should implement IBindingList. BindingList<T> is an example of the implementation. So it's enough to set comboBox.DataSource = someDataSource; rather than data-binding to DataSource.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Not agree about binding to `DataSource`. Binding can be used for DataSource too - especially for `ComboBox` in cases when you want change items based on some condition in the viewmodel. – Fabio Nov 08 '17 at 06:48
  • @Fabio It's enough for the data source to support `IBindingList` then the combo box will be notified of data source changes. It's the way that complex data-binding works, you provide the data-source and use data-binding for some other properties. – Reza Aghaei Nov 08 '17 at 06:51
  • @Fabio If you are going to bind `DataSource` property to `Optins` property of `MainModel` it means you are going to update `Options` property using the combo box! Also it means the `MainModel` class should implement `INotifyPropertyChanged` to notify the combo boxes about changes in `Options` property!(Keep in mind the options property is an `IList`) That's totally wrong idea! Could you get the point? – Reza Aghaei Nov 08 '17 at 07:48
  • Agree that with `comboBox.DataSource = someDataSource;` you get job done with minimum effort. You don't update collection through combobox, you select item from combobox. If `MainModel` implements `INotifyPropertyChanged` you can bind any collection(not only `IBindingList` or `ObservableCollection`) to the combobox. My point was that I don't see some technical obstacles why `DataSource` cannot be bounded to the collection. – Fabio Nov 08 '17 at 08:17
  • @Fabio *Technically* it's possible and in the *Note* part I showed how the error can be avoided. But it's a bad idea and is a wrong way of doing things while there are better, correct and tested solutions. – Reza Aghaei Nov 08 '17 at 08:36
  • By using `BindingList` as datasource combobox will not be notified about changes in case when you set new instance of `BindingList` to the property – Fabio Nov 08 '17 at 08:42
  • I just want to say that binding `DataSource` is not wrong, even better it's provide more general approach, which for example give you possibility to use same viewmodel in Winforms and WPF applications. – Fabio Nov 08 '17 at 08:51
  • @Fabio none of complex properties should be subject to a reference change. You should just change their contents and properties, not their references. – Reza Aghaei Nov 08 '17 at 09:01
  • Sorry, but in case of collections it is ok to set new instance of collection than removing and adding new items – Fabio Nov 08 '17 at 09:07
  • I'm exactly in the situation @Fabio mentions. My (actual) model implements `INotifyPropertyChanged`. At some points, I change the `Options` property to a new reference, just the way I'd do in WPF. And I need a way to make sure that the options of the combo-box mirror that property. – Daniel Wolf Nov 08 '17 at 09:13
  • @DanielWolf - as other have suggested, binding properties to controls after `InitializeComponent` method should work. – Fabio Nov 08 '17 at 09:19
  • @DanielWolf Read the **Note** part to do a quick fix. It's basically the reason that `Load` event also works. – Reza Aghaei Nov 08 '17 at 09:33
  • @DanielWolf This dicision will force you to implement both `INotifyPropertyChanged` and have a list implementing `IBindingList` implemented. Or else in some way you should receive all change notification from the list (like `CollectionChanged` for `ObservableCollection`) and raise `PropertyChanged` event. – Reza Aghaei Nov 08 '17 at 09:42
  • 1
    @Fabio It's getting a bit opinion based and getting too broad to be able to have a conclusion in comments. Anyway, was a good technical discussion :) – Reza Aghaei Nov 08 '17 at 09:43