Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a DataSource with an object in Windows Forms

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?

like image 853
Marie Avatar asked Apr 09 '26 04:04

Marie


1 Answers

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. ;-)

like image 99
JohnG Avatar answered Apr 11 '26 18:04

JohnG