Search code examples
.netwinformsdata-bindingdatagridviewbindingsource

Using DataGridView with BindingSource.DataSource=List<T>, need to bind to inherited type of T


I'm looking to resolve databinding to a Winforms datagridview using C# & .Net3.5. It's a bit involved and encompasses a few classes which I will provide code.

I have have an Interface defined as such:

public interface MyElement : IEditableObject {
    string Name { get; set; }
    string Description { get; set; }
}

I next have an abstaract class that inherits from MyElement defined as such:

abstract public class MyElementAdapter : MyElement {
    private string myName;
    private string myDescription;
    private ModelElement myParent;
    public MyElementAdapter(MyElement parent) {
        myName = "New_Element";
        myDescription = string.Empty;
    }
    virtual public string Name {
        get { return myName; }
        set { myName = value; }
    }
    virtual public string Description {
        get { return myDescription; }
        set { myDescription = value; }
    }
    public MyElement Parent {
        get { return myParent; }
        set { myParent = value; }
    }
    public void BeginEdit() {
        // code to begin edit
    }
    public void CancelEdit() {
        // code to cancel edit
    }
    public void EndEdit() {
        // code to end edit
    }
}

There is a generic list of the interface type that is defined as such - its very basic and just retains a generic list of MyElements:

public class MyElementList : List<MyElement> {
    public MyElementList() {
    }
}

There is an inherited class named MyField which derives from MyElementAdapter defined as such:

public class MyField : MyElementAdapter {
    private int size;
    public MyField(MyElement parent) : base(parent) {
        size = 1;
    }
    public string MyFieldName {
        get { return this.Name; }
        set { this.Name = value;}
    }
    public string MyFieldDescription {
        get { return this.Description; }
        set { this.Description = value;}
    }
    public int Size {
        get { return size; }
        set { size = value; }
    }
}

There is a class which uses MyElementList where MyField is the List type:

public class MyRecordOfFields : MyElementAdapter {
    private MyElementList myMembers;
    public MyRecordOfFields(MyElement parent) : base(parent) {
        myMembers = new MyElementList();
    }
    public MyElementList MyMembers {
        get { return myMembers; }
        set { myMembers = value; }
    }
}

And finally, there is a form that I am using to edit an object of type MyRecordOfFields.

public class Form1 : Form {
    private BindingSource bindingSource1;
    private MyRecordOfFields myLocalFields;
    private DataGridView dgv;

    private Form1_Load() {
        myLocalFields = new MyRecordOfFields(null);
        MyField field1 = new MyField(null);
        field1.MyFieldName = "Field_1";
        field1.MyFieldDescription = "Description1";
        field1.Size = 3;
        myLocalFields.MyMembers.Add(field1);
        MyField field2 = new MyField(null);
        field2.MyFieldName = "Field_2";
        field2.MyFieldDescription = "Description2";
        field2.Size = 3;
        myLocalFields.MyMembers.Add(field2);
        MyField field3 = new MyField(null);
        field3.MyFieldName = "Field_3";
        field3.MyFieldDescription = "Description3";
        field3.Size = 3;
        myLocalFields.MyMembers.Add(field3);

        bindingSource1 = new BindingSource();
        bindingSource1.AllowNew = true;

        dgv.AutoGenerateColumns = false;
        int colIndex = dgv.Columns.Add("MyFieldName", "MyFieldName");
        dgv.Columns[colIndex].DataPropertyName = "MyFieldName";
        colIndex = dgv.Columns.Add("MyFieldDescription", "MyFieldDescription");
        dgv.Columns[colIndex].DataPropertyName = "MyFieldDescription";
        colIndex = dgv.Columns.Add("Size", "Size");
        dgv.Columns[colIndex].DataPropertyName = "Size";
        dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
        dgv.AutoResizeColumns();

        bindingSource1.DataSource = myLocalFields.MyMembers;
        dgv.DataSource = bindingSource1;
        dgv.Invalidate();
    }
}

When this form is loaded, everything compiles and runs however when the form is displayed, the datagridview has 3 row but all blank. I am trying to directly set the datagridview's datasource to the MyElementList in the MyRecordOfFields class however nothing is displayed expect for the number of rows added to the list.

When I don't set the datagridview's datasource to the list, but add each element in that list in a loop, data is displayed as it should.

The following line:

        bindingSource1.DataSource = myLocalFields.MyMembers;

Changed to a loop:

        foreach (MyElement me in myLocalFields.MyMembers) {
            // No casting needed due to inheritance
            bindingSource1.Add(me);
        }

And after I changed to a loop, I had to add event handlers for adding, deleting, and updating to the underlying data source which is myLocalFields (MyRecordOfFields.MyMembers).

Is there something I am missing or not doing correctly in my scenario?? I know it's a bit involved with the Interface & abstract classes, but I'm working from predefined implementations and so changing these underlying/basic classes is not an option. Thank you in advance.

  • Lorentz

UPDATE: I've tried a few suggestion and to no avail nothing. So my question is this: Is it possible for me to expose the inherited properties (like I designed the datagridview [columns]) of MyField by setting the bindingSource1.DataSource to the MyElementLIst in MyRecordOfFields.MyMembers property??

This is where I need some guidance: MyElement is an interface; MyElementAdapter is an abstract derived from MyElement; MyElementList is a generic list of MyElement; and MyField is derived from MyElementAdapter. Adding MyElement or any derived class (ie MyField) to MyElementList works. Yet it is these derived classes' properties I want to expose in the UI (datagridview, controls, etc.).

UPDATE#2: Now that I have my datagridview bounded to my MyElementList everything is working fine. I am now working on adding/deleting/updating myLocalFields.MyMembers list. I've added a button with the following code:

private void AddItem_Click(object sender, EventArgs e) {
    MyField field1 = new MyField(null);
    field1.MyFieldName = "Field_" + myLocalFields.MyMembers.Count + 1;
    field1.MyFieldDescription = "Description" + myLocalFields.MyMembers.Count + 1;
    field1.Size = myLocalFields.MyMembers.Count + 1;
    myLocalFields.MyMembers.Add(field1);
    ((BindingSource)dataGridView1.DataSource).ResetBindings(false);
}

When this method is done, I see that my underlying data (myLocalField.MyMembers) changes, but bindingSource1 and dataGridView1 do not. In the past, any updates to the underlying data was done to the underlying data as well as the bindingsource - I would like to make changes to the underlying data and have that propogate to the bindingsource/datagridview.

I figured the following would take care of any changes to the datagridview but it doesn not:

    ((BindingSource)dataGridView1.DataSource).ResetBindings(false);

Is there another method that I need to use in order for me to update the datagridview without adding additional datagridview events?


Solution

  • Firstly:

    • please consider accepting answers to your questions.
    • please post code that compiles.

    You really should prefix your interfaces with I. It makes your code easier to interpret.

    You are setting your datasource to a List<MyElement>. That means the available properties it can bind to are Name and Description.

    public interface MyElement : IEditableObject { 
        public string Name { get; set; } 
        public string Description { get; set; } 
    } 
    

    But you're manually creating DataGridView columns which are bound to properties called MyFieldName and MyFieldDescription and Size. The data binder can't match up what you told it to expect with what you gave it.

    When you do this, without first setting the datasource:

    foreach (MyElement me in myLocalFields.MyMembers) {         
                bindingSource1.Add(me);         
            }         
    

    I believe the first object you add is what establishes the bindings, and in this case it's a MyField, which has the properties your DataGridView expects.

    You have several options. Here are some of them in no particular order.

    Option 1. Use AutoGenerateColumns = true, and delete all the code that creates columns manually.

    Option 2. When you create the columns, use the MyElement property names:

    int colIndex = dgv.Columns.Add("MyFieldName", "MyFieldName");  
    dgv.Columns[colIndex].DataPropertyName = "Name";  
    colIndex = dgv.Columns.Add("MyFieldDescription", "MyFieldDescription");  
    dgv.Columns[colIndex].DataPropertyName = "Description";  
    

    Option 3. Cast the objects to MyField, so that the BindingSource generates the bindings for the properties you told the grid to expect:

    Instead of this:

    bindingSource1.DataSource = myLocalFields.MyMembers;
    

    Do this:

    bindingSource1.DataSource = myLocalFields.MyMembers.Cast<MyField>();
    

    Hopefully you can see the theme here. The point is that you're currently combining explicitly bound columns with automatically created bindings, and the automatic bindings aren't what your columns are set up for. The options I've given you all address this problem in different ways.