arrow-left arrow-right brightness-2 chevron-left chevron-right circle-half-full dots-horizontal facebook-box facebook loader magnify menu-down RSS star Twitter twitter GitHub white-balance-sunny window-close
WPF Dynamically Generated DataGrid
2 min read

WPF Dynamically Generated DataGrid

This is an old post and doesn't necessarily reflect my current thinking on a topic, and some links or images may not work. The text is preserved here for posterity.

When building an application, I might not have the luxury of creating model classes to represent the objects I'll be rendering on screen, because they are dynamic. If I don't know the shape of my data, I can use a meta model (a model of the final model) to represent it.

Some examples of where this exist are:

  • SharePoint - users can define custom lists with custom columns, so a particular ListItem has many "properties"
  • Microsoft CRM - users can define custom entities, again with custom attributes

For example, I might have the concept of a Property:

public class Property : INotifyPropertyChanged
{
    public Property(string name, object value)
    {
        Name = name;
        Value = value;
    }

    public string Name { get; private set; }
    public object Value { get; set; }
}

And the concept of a Record, which is really just a bag of properties:

public class Record
{
    private readonly ObservableCollection<Property> properties = new ObservableCollection<Property>();

    public Record(params Property[] properties)
    {
        foreach (var property in properties)
            Properties.Add(property);
    }

    public ObservableCollection<Property> Properties
    {
        get { return properties; }
    }
}

Here's how I might fill the data:

var records = new ObservableCollection<Record>();
records.Add(new Record(new Property("FirstName", "Paul"), new Property("LastName", "Stovell")));
records.Add(new Record(new Property("FirstName", "Tony"), new Property("LastName", "Black")));

Rendering basic columns in a DataGrid

Building a DataGrid to render this model is pretty easy. The XAML would be:

<DataGrid 
   Name="dataGrid" 
   AutoGenerateColumns="false" 
   ItemsSource="{Binding Path=Records}" 
   />

Since I don't know the names of the columns at design time, I'll have to dynamically generate them. This part is easy:

var columns = records.First()
    .Properties
    .Select((x, i) => new {Name = x.Name, Index = i})
    .ToArray();

foreach (var column in columns)
{
    var binding = new Binding(string.Format("Properties[{0}].Value", column.Index));

    dataGrid.Columns.Add(new DataGridTextColumn() {Header = column.Name, Binding = binding });
} 

As you can see, I dynamically create a Binding, and use the index of the column in my model as the binding path.

Rendering templated columns

This part gets harder. If I wanted to use a custom CellTemplate to render my properties, I might have done this in XAML:

<DataGridTemplateColumn>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Border Padding="3" Background="Purple">
                <TextBox Text="{Binding Path=FirstName}" />
            </Border>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Notice I hardcoded Path=FirstName in the binding above - that's not going to work when we have a dynamic model.

It took a lot of experimentation to make this happen, but the result isn't much more complicated. I'd start by making a DataTemplate for the cell as a resource:

<Window.Resources>

    <DataTemplate x:Key="CustomTemplate">
        <Border Padding="3" Background="Purple">
            <TextBox Text="{Binding Path=Value}" />
        </Border>
    </DataTemplate>

</Window.Resources>

I'd then dynamically generate the columns like this:

foreach (var column in columns)
{
    var binding = new Binding(string.Format("Properties[{0}]", column.Index));
    dataGrid.Columns.Add(new CustomBoundColumn() 
    { 
        Header = column.Name, 
        Binding = binding, 
        TemplateName = "CustomTemplate" 
    });
} 

The part that makes it work is the CustomBoundColumn - I had to implement this myself. Here it is:

public class CustomBoundColumn : DataGridBoundColumn
{
    public string TemplateName { get; set; }

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        var binding = new Binding(((Binding)Binding).Path.Path);
        binding.Source = dataItem;

        var content = new ContentControl();
        content.ContentTemplate = (DataTemplate)cell.FindResource(TemplateName);
        content.SetBinding(ContentControl.ContentProperty, binding);
        return content;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        return GenerateElement(cell, dataItem);
    }
}

Note that the name of the DataTemplate is passed as a property to the CustomBoundColumn, so you could dynamically choose a DataTemplate to use based on the type of property (e.g., use a different template if the value is numeric).

Enjoying these posts? Subscribe for more

Subscribe now
Already have an account? Sign in
Paul Stovell's Blog

Hello, I'm Paul Stovell

I'm a Brisbane-based software developer, and founder of Octopus Deploy, a DevOps automation software company. This is my personal blog where I write about my journey with Octopus and software development.

I write new blog posts about once a month. Subscribe and I'll send you an email when I publish something new.

Subscribe

Comments

You've successfully subscribed to Paul Stovell's Blog.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info is updated.