Search code examples
c#wpfmvvmdatagrid

WPF: Best way to create bindings to unknown types in MVVM


I am looking for a way to display data in a DataGrid from types that are unknown at compile-time.

I have the following base class

public abstract class Entity
{
    // Some implementation of methods ...
}

In run-time, I load a plug-in DLL and use reflection to get a list of all the types derived from Entity. For example:

public class A : Entity
{
    public LocalAddress Address{ get; set; }
}

public class B : Entity
{
    public Vendor Vendor { get; set; }

    public string Name { get; set; }
}

Then I retreive a list of their instances from DB

public IEnumerable<Entity> Entities { get; set; } // A list of instances of type A for example

Entities is the DataGrid's ItemsSource, But what's the best way I can bind the properties to the DataGrid? Since the properties can be complex, I also need to be able to bind to a specific path, for example Address.HomeNum ...

Clarifications

  1. I only need to show a one grid of a type's instances at a time. The complete scenario is this:

    1. I get a list of types that derive from Entity from the plug-in DLL through reflection
    2. I show their names in a List. (in this example that list will contain A and B
    3. When the user clicks on a specific item, let's say A, I get a list of A instances from DB - so far so good.
    4. I want to display that list of A's instances in a DataGrid.
    5. When the user selects another item from the list (meaning another type, lets say B), I get a list of B's instances from DB and need to display those in the grid and so on ...
  2. The plug-in DLL is a class library with no xamls (also my users are the ones making this plug-ins and I don't want them to have to write DataTemplates for their entities. I also can't make predifned DataTemplates as I don't know the types I'll need to display until run-time. Each type can have different types and amount of properties. All I know in complie-time is that they all derived from Entity.

  3. The grid should also be editable.

Solution

  • Since you don't know the property names of the Entities beforehand, I think your best option is to keep your DataGrid in Xaml but move the defintion and the Bindings of its DataGridColumns to the code behind.

    AddColumnsForProperty(PropertyInfo property, string parentPath = "")
    {
         var title = property.Name;
         var path = parentPath + (parentPath=="" ? "" : ".") + property.Name;
    
         if(property.PropertyType == typeof(string))
         {
            var column = new DataGridTextColumn();
            column.Header = title;
            column.Binding = new Binding(path);
            dataGrid.Columns.Add(column);
         }
         else if(property.PropertyType == typeof(bool))
         {
            //use DataGridCheckBoxColumn and so on
         }
         else
         {
              //...
         }
    
         var properties = property.GetProperties();
         foreach(var item in properties)
         {
              AddColumnsForProperty(item, path);
         }
    }
    

    Now if you execute these you'll have your dataGrid columns filled. and by adding all instances of the desired type in an observable collection and bind it to ItemsSource of the DataGrid it should work. selectedItem should be an instance of one the classes derived from Entity. The listbox contains new A() and new B() (or any existing instances of A and B) so selectedItem can be used in the following statement.

    var propertyList = selectedItem.GetType().GetProperties();
    foreach (var property in propertyList) 
        AddColumnsForProperty(PropertyInfo property);
    

    how to write DataGridColumnTemplate in code


    Edit:

    Member can't be used in this scenario because INotifyPropertyChanged should get involved, so I replaced members with properties.