Reputation: 2217
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?
Upvotes: 2
Views: 15505
Reputation: 2989
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.
Upvotes: 2
Reputation: 9479
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. ;-)
Upvotes: 4
Reputation: 125292
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.
Upvotes: 2