2

I have been trying to create a small form application and I wanted to try out binding a DataGridView directly to a collection of objects.

I created the following classes

public class MyClassRepository
{
    public List<MyClass> MyClassList { get; set; } = new List<MyClass> { new MyClass { Name = "Test" } };
}

public class MyClass
{
    public string Name { get; set; }
}

and I added the following code to a form to test. I based this off of the code in the designer after setting the BindingSource through the UI (while following this walk through https://msdn.microsoft.com/en-us/library/ms171892.aspx)

var tmp = new BindingSource();
tmp.DataMember = "MyClassList";
tmp.DataSource = typeof(MyClassRepository);

When this didn't work I started running through the code behind BindingSource to see what was happening. The setter calls ResetList which tries to create a dataSourceInstance by calling ListBindingHelper.GetListFromType. This call ultimately calls SecurityUtils.SecureCreateInstance(Type) where type is a BindingList<MyClassRepository>. This passes null to args which is passed Activator.CreateInstance which returns an empty collection.

After this ListBindingHelper.GetList(dataSourceInstance, this.dataMember) is called. This method calls ListBindingHelper.GetListItemProperties which results in a PropertyDescriptor for my MyClassList property and assigns it to dmProp.

At this point GetList calls GetFirstItemByEnumerable(dataSource as IEnumerable) where dataSource is the previously created (and empty) instance of BindingList<MyClassRepository> and returns (currentItem == null) ? null : dmProp.GetValue(currentItem);.

The value of dmProp/MyClassList is never accessed and the BindingSource is never populated with the instance I created. Am I doing something wrong? If not is there a bug in the source code? It seems to me like either SecureCreateInstance(Type type, object[] args) should be called and MyClassList should be passed via args instead of the existing call to SecureCreateInstance(Type type) or the value of dmProp should be used regardless?

If that is not correct how do I make the Designers automatically generated code set the DataSource to an instance of the object? Or do I have to inherit from BindingSource? If the latter why does it give you the option to choose a class that does not inherit from BindingSource?

10
  • The Binding source should be an instance of the type you want to bind to, not the type itself, so tmp.DataSource = typeof(MyClassRepository); is very wrong. You create a var myRepository = new MyClassRepository(); and then you do tmp.DataSource = myRepository;. Commented Apr 13, 2018 at 21:13
  • 1
    Like I said, I based that on what the Designer did when I chose the BindingSource. It set the DataSource to typeof(MyClassRepository). If that is very wrong why does it happen? Commented Apr 13, 2018 at 21:16
  • 2
    Five years later and I'm running into some similar confusion, and unfortunately the answers here don't really seem to answer it. If your goal is to do as much as possible in the VS Designer, then when you create a BindingSource, the Designer sets the .DataSource property to a Type, not an instance. It's unclear when exactly the DataSource property actually becomes an instance, and I don't think the answers really address this. Commented May 19, 2023 at 16:39
  • 2
    Follow up: After studying the comments and answers here more, I think our collective confusion is maybe stemming from the documentation not being terribly clear. Especially when using the Designer, it's unclear where the handoff occurs between the automation creating data and when we need to start manually filling properties. So when the DataSource is being set to a type, that's our cue to set it to an instance. I think I get it! Commented May 19, 2023 at 16:52
  • 1
    @gcode I appriciate the followup! I definitely think I understand it now. It seems that Microsofts design may abuse that property a bit, and that might have been what caused my confusion, assuming I understand correctly. Thank you, again! Commented Nov 2, 2023 at 11:37

3 Answers 3

4

As Reza Aghaei points out, in the designer, setting the BindingSource.DataSource to the “MyClassRepository” may work, however you still need to initialize (create a new) MyClassRepository object. I do not see this line of code anywhere in the posted code: MyClassRepository myRepositiory = new MyClassRepository(); The data source is empty because you have not created an instance of “MyClassRepository” and as Reza points out, this is usually done in the forms Load event.

To keep it simple, remove the DataSource for the BindingSource in the designer and simply set up the BindingSource’s data source in the form load event like below. First, create a new “instance” of the MyClassRepository then use its MyClassList property as a data source to the BindingSource. I hope this helps.

MyClassRepository repOfMyClass;

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
  repOfMyClass = new MyClassRepository();
  bindingSource1.DataSource = repOfMyClass.MyClassList;
  dataGridView1.DataSource = bindingSource1;
}

Edit-----

After further review… I agree that you should be able to do as you describe. I was able to get it working as expected with the code below.

BindingSource bindingSource1;
private void Form1_Load(object sender, EventArgs e) {
  bindingSource1 = new BindingSource();
  bindingSource1.DataSource = typeof(MyClassRepository);
  bindingSource1.DataMember = "MyClassList";
  dataGridView1.DataSource = bindingSource1;
}

I followed the same steps in the “designer” and it worked as expected. Is there something else I am missing? As you stated… using MyClassRepository mcr = new MyClassRepository() appears to be unnecessary. In addition, if you cannot get it to work using one of the two ways above… then something else is going on. If it does not work as above, what happens?

Edit 2

Without creating a “new” MyClassRepository, object was unexpected and I did not realize that new items added to the list/grid were going into the bindingSource1. The main point, is that without instantiating a “new” MyClassRepository object, the constructor will never run. This means that the property List<MyClass> MyClassList will never get instantiated. Nor will the default values get set.

Therefore, the MyClassList variable will be inaccessible in this context. Example in this particular case, if rows are added to the grid, then bindingSource1.Count property would return the correct number of rows. Not only will the row count be zero (0) in MyClassList but also more importantly… is “how” would you even access the MyClassList property without first instantiating a “new” MyClassRepository object? Because of this inaccessibility, MyClassList will never be used.

Edit 3 ---

What you are trying to achieve can be done in a myriad number of ways. Using your code and my posted code will not work if you want MyClassList to contain the real time changes made in the grid by the user. Example, in your code and mine… if the user adds a row to the grid, it will add that item to the “bindingSource” but it will NOT add it to MyClassList. I can only guess this is not what you want. Otherwise, what is the purpose of MyClassList. The code below “will” use MyClassList as expected. If you drop the “designer” perspective… you can do the same thing in three (3) lines of code... IF you fix the broken MyClassRepository class and create a new one on the form load event. IMHO this is much easier than fiddling with the designer.

Changes to MyClassRepository… added a constructor, added a size property and a method as an example.

class MyClassRepository {

  public List<MyClass> MyClassList { get; set; }
  public int MaxSize { get; set; }

  public MyClassRepository() {
    MyClassList = new List<MyClass>();
    MaxSize = 1000;
  }

  public void MyClassListSize() {
    MessageBox.Show("MyClassList.Count: " + MyClassList.Count);
  }

  // other list manager methods....

}

Changes to MyClass… added a property as an example.

class MyClass {
  public string Name { get; set; }
  public string Age { get; set; }
}

Finaly, the form load event to create a new MyClassRepository, set up the binding source to point to MyClassList and lastly set the binding source as a data source to the grid. NOTE: making myClassRepository and gridBindingSource global variables is unnecessary and is set this way to check that MyClassList is updated in real time with what the user does in the grid. This is done in the button click event below.

MyClassRepository myClassRepository;
BindingSource gridBindingSource;

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
  try {
    myClassRepository = new MyClassRepository();
    gridBindingSource = new BindingSource(myClassRepository.MyClassList, "");
    dataGridView1.DataSource = gridBindingSource;
  }
  catch (Exception ex) {
    MessageBox.Show("Error: " + ex.Message); 
  }
}

private void button1_Click_1(object sender, EventArgs e) {
  MessageBox.Show("Binding source count:" + gridBindingSource.Count + Environment.NewLine +
                  "MyClassList count: " + myClassRepository.MyClassList.Count);
}

I hope this makes sense. ;-)

Sign up to request clarification or add additional context in comments.

5 Comments

Microsofts code examples for binding a datagridview to an object pretty consistently set it to just typeof(TheObject). Even their examples that do not use a designer. I have yet to see a single MS example that sets the binding source to a type and overrides it with an instance.
Thank you for taking the time to look into it a bit. Any idea what the purpose of DataMember is? Based on the MSDN I had assumed it was to support something like this
Thank you for keeping at it with me! I had gotten it to work a bit by implementing ilistsource but that seemed redundant based on DataMember and such. If you don't mind me asking do you have a strong preference at this point?
If you must use a List<T>, then a binding source/list may be a better approach. If “I” “had” to use a List<T> as a data source, and depending on what is being done with the data, chances are high that I would probably make a converter to take a List<T> and convert it to a DataTable.
IMHO, DataGridView and DataTable controls play nicely together and have features built in that you will most likely need to implement using a `List<T>. I sympathize with using the “designer”. It is often convenient however, when it comes to setting up data connects etc.… I find this easier to do in code. Glad you got it working. ;-)
2

Designer sets DataSource = typeof(Something) for design-time support, for example to let you choose DataMember from a dropdown or to let you choose the data source property from dropdown while setting up data-bindings.

How do I make the Designers automatically generated code set the DataSource to an instance of the object?

Forcing the designer to do that doesn't make much sense, because the designer doesn't have any idea about what the real data source you are going to use to load data. It can be a web service, a WCF service, a business logic layer class.

So at run-time you need to assign an instance of your list to DataSource. For example in Load event of the form.

5 Comments

Microsofts code examples for binding a datagridview to an object pretty consistently set it to just typeof(TheObject). Even their examples that do not use a designer. I have yet to see a single MS example that sets the binding source to a type and overrides it with an instance.
I described all you needed. Setting DataSource to a Type brings you design time support and it allows setting up data-binding before loading actual data. But to make it working with data, you definitely need to assign actual data list to DataSource. Bringing DataMember into the game changes nothing.
That is not true. The MSDN for DataSource shows that you can set it to a type and the underlying list is automatically resolved to an IBindingList<T>. As a quick test you can do var list = new BindingSource(); list.DataSource=typeof(string); list.Add("Test");
I'm not sure what the usage you are imagining in action for above example. Anyway, I's unclear what you are looking for and what the problem you are trying to solve ...
That is fair, sorry for not being clear. I think that the behavior of BindingSource is just a little too complex. I was assuming there was some way to define a type, define a member that returns a list of that type, and have a data bound control automatically populate without having to manually create the repository, set the data source, or add the items.
2

Building on the other answers here, the Visual Studio Designer will create a BindingSource object and set its DataSource property to the Type of the object that you intend to bind. The reason it does this is to inform the Designer of what properties are available to be bound. The Designer has no concept of object instances because your program isn't running at the time.

If you follow Microsoft's How to: Create a simple-bound control on a Windows Form, you might find yourself confused because the Form still doesn't appear to function as if there's any bound properties. Unfortunately, that guide in particular is lacking and does not follow up with the code to complete the goal. As the programmer, it's still your responsibility to create the instance of the object that you intend to bind, and then execute any code that will cause the object to become populated with the data that will eventually appear in your bound Form.

How we do that is best covered by Reza's answer, I think; create a private member in your Form's class that will hold an instance of the object you want to bind. You will then use the Form's Load event to instantiate your object (or acquire the instance by other means if it's created elsewhere in your code), assign it to the private member in your Form, and then set the BindingSource's .DataSource property to your new private object instance. This is the critical step I and I think many others missed, and which could be better documented by Microsoft. For example, take a look at their documentation for the BindingSource class.

binding1.DataSource = fonts
listBox1.DataSource = binding1
listBox1.DisplayMember = "Name"

The only part you need to worry about is that binding1.DataSource line; that is where you assign your backing object to act as the data source.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.