DataGridView - How to Bind columns in Row to List/Array/IEnumerable

Jaeden Ruiner 126 Reputation points
2021-02-13T19:11:59.063+00:00

Okay,

So i've got essentially a BindingList<BindingList<string>> .

I've managed to create the columns, and when I set the DataGridView.DataSource = myNestedBList, i get the number of rows showed in the grid. When I break at the DataGridView_RowEnter event, the current DataGridViewRow.DataBoundItem is my nested list, but the cells do not display any data.

Now i know that the DataGridView can work directly off of a DataTable which has a similar pattern, while not requiring me to implement the DataGridView.VIrtualMode. How can i update my "LIst" class to operate the same so I do not need to implement anything at the GridView level.

Thanks
Jaeden "Sifo Dyas" al'Raec Ruiner

Windows Forms
Windows Forms
A set of .NET Framework managed libraries for developing graphical user interfaces.
1,891 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,922 questions
0 comments No comments
{count} votes

Accepted answer
  1. Jaeden Ruiner 126 Reputation points
    2021-02-13T22:57:21.28+00:00

    Ah,

    Well, playing around a bit more I found the answer:

    System.ComponentModel.ICustomTypeDescriptor
    

    If in the Implementation of MyBindingList<T> (which implements ITypedList), I Implement the ITypeList.GetItemProperties() to check to see if the T type implements ICustomTypeDescriptor, i can force the issue. Thus the "T" could be a "List<T>" for all the system cares as long s the ITypedList.GetItemProperties() returns the custom PropertyDesciptor instances that know how to read/write from the "List<T>" and can provide the map.

    THat was the link. I had already implemented ITypedList but it evaluted T directly (using TypeDescriptor.GetProperties()) which was getting the properties of MyRow. If you create custom Descendants of PropertyDescriptor that provide how to "GetValue" and "SetValue" for a given property on an object parameter, it does not need to have access to direct property declarations, they can be in an array, a list, or made up at run time.

    So the solution was

    • Custom PropertyDescriptor class that works off the "table" model
    • Implement ICustomTypeDescriptor

    Thanks
    J"SD"a'RR

    Custom

    1 person found this answer helpful.
    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. Karen Payne MVP 35,421 Reputation points
    2021-02-13T20:10:30.827+00:00

    As you have learned a standard DataGridView does not handle nesting of objects. Best suggestion is to use a projection of the data. All of the following code was done via Entity Framework but that doesn't matter, what is important in this case are projections and how to handle changes as described below.

    Example, the task is to show a customer (one table) with a contact table and a country table.

    Customer model which in this case implements INotifyPropertyChanged for immediate updates to the user interface, ContactType model and Countries model.

    These are tied together with this class (projection) used in this method to read the data. The caller then has data to present where you would setup a BindingList to the data returned then set the BindingList to a BindingSource data source which then becomes the data source of the DataGridView which you would then setup data bindings to each property in the projection to display expect for primary keys.

    Now the above will not be sortable in the DataGridView so if sorting is needed replace a standard BindingList with a SortableBindingList<T>

    If you need to edit, add and delete you need to work with AddingNew and ListChanged events of the BindingList. If some choices are in a DataGridViewComboBox CellValueChanged event would be needed for this, see the following form for that.


  2. Jaeden Ruiner 126 Reputation points
    2021-02-13T20:37:09.13+00:00

    That's not what I'm looking for.
    I have several custom classes and all sorts of implementations beyond the standard Microsoft libraries.
    Effectively, I have a descendant of BindingList<T> which i use for most of my lists, and permutation of that descendant which handles dictionaries. All are thread safe, serializable, and support advanced multi-column sorting. Let's just call it MyBindingList<> for reference.

    The problem is when I have a class, say MyData which has a Name and Value property on it, fully supporting INotifyPropertyChanged, I can bind MyBindingLIst<MyData> to a DataGridView. I Create the columns, and set the DataGridViewColumn.DataPropertyName = to "Value" and "Name" respectively, and the data shows up, and is manageable as expected from the DataGridView control to the underlying data source.

    If however, MyData doesn't have any properties, but is in fact a descendant of MyBindingList<string>, or if it simply has an internal MyBindingList<string> to store the data, there are no "Properties" to bind the individual DataGrdiViewColumn classes to.

    Now, the other example for this is the DataTable with the DataRow classes. DataRow does not have a "Name" or "Value" property on it, but somehow, in the BindingContext and CurrencyManager of WinForms, the DGV is able to interpret from the DataTable and DataColumn and DataRow classes the correct row/column information in order to bind the individual cells.

    I am looking for that implementation. I know that DataRow is not a "list" class directly. It uses references back to the DataTable and Columns collection to grab the correct Column Data for that row. But that doesn't explain how the binding works between the DataGridView and these classes.

    so , for example:

    class MyTable : BindingList<MyRow>
    {
        public BindingList<MyColumn> Columns {get;}
    }
    
    
    class MyColumn {
       public string Name {get;set;}
       public string Caption {get;set;}
    }
    
    class MyRow {
    
       public this [MyColumn column] {get; set;}
       public this[int columnIndex] {get;set;}
       public this[string columnName] {get;set;}
    
       protected BindingLIst<string> Data {get;}
    }
    

    Now, i can create the columns in the DGV from the Columns Property. I can set the MyTable as the data source for the DGV. Now IF MyRow had "properties" on it (like Name, Value, DateOfBirth, etc) i could map those properies directly to the columns via the DataPropertyName on the DataGridViewColumn. But like the DataRow, MyRow lacks this definition, because it is a dynamically created "table" so to speak.

    When i use this above model as the datasource for the DataGridView i get a visible row for EACH of the items in MyTable. But all the cells are Blank. Even when the "column name" and DataPropertyName are assigned in the same fashion that it appears in the watch windows from when I'm using a DataTable.

    I'm asking given the above descriptors for these classes, what can i implement in the MyTable & MyRow classes in order achieve the same behavior as the DataTable and DataRow. There are no properties to bind on the DataRow class, and yet if there is DataColumn "Name" and a DataColumn "Value" in that DataTable, it shows up perfectly in the grid for each of the DataRow classes found in the provided DataTable. Each DataGridViewRow.DataBoundItem is returns to be a DataRow. The DataColumn's have a DataPropertyName of "Name" and "Value", yet there is still no property to map to on the underlying bound item type of DataRow. How does it achieve this?

    I do not care about entity framework, or other sorting aspects, i am asking how the DataTable with DataColumn and DataRow that do not have "bindable properties' end up binding correctly to the DataGridView so i can mimic this behavior.

    Thanks
    J"SD"a'RR

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.