Posted by: Zeeshan Amjad | September 15, 2011

DataGrid with Dynamic Columns


I came across a problem where I am suppose to create a data grid with dynamic columns in it. I.e. number of columns decided on run time depends on the data in it. I came across an interesting technique on net here. Try to better understand it I thought why not keep my hands dirty with it and give it a short.

I decided to modify the code little bit and make is simpler to understand. Here are few goals in my mind while working on it.

1. I wanted to make a simple application, so remove everything related to MVVM.

2. That application works only with read only data, I wanted to make data editable, therefore I modified code accordingly to make it writable dynamic columns grid.

3. That code uses two different approaches for header and cells, (style vs non-style) I used the same approach for both.

4. That code fixes the width of the cells, I made it dynamic too.

5. As a added benefit I also add the property to change the color of individual item or header and make it easy to add even future addition.

Here is my starting point. I started with a simple class to store some basic information about individual cell.

Code Snippet
public class ColumnData
{
    public string Data
    { get; set; }

    public int Width
    { get; set; }

    public Brush Color
    { get; set; }
}

 

I set only data, width and color, but we can easily add more properties in this class. My next step is to create a column class, which is nothing more than a collection of ColumnData class object. Here is a code of it.

Code Snippet
public class Column
{
    public ObservableCollection<ColumnData> ColumnsData
    { get; set; }
}

 

Then I created a table class that contains collection of columns and collection of header. Here is a definition of my table class.

Code Snippet
public class Table
{
    public ObservableCollection<Column> Columns
    { get; set; }

    public ObservableCollection<ColumnData> Headers
    { get; set; }

    public Table()
    {
        Columns = new ObservableCollection<Column>();
        Headers = new ObservableCollection<ColumnData>();
    }
}

Rest of the step is just populate some data in class and assigned its object to DataContext of my Window class.

Because I have two properties in my Table class, therefore I have to assigned it for header and data in DataGrid.

Here is a code to assigned my data class to data grid.

Code Snippet
<DataGrid Margin="5" AutoGenerateColumns="False" ItemsSource="{Binding Columns}">

 

First take a look at the header. For header I set HeaderTemplateColumn of the data grid. Because we have already bind the data grid to Columns property, therefore we can’t directly uses the Header property of DataContext. There are multiple ways to achieve this, such as assigned one name to our Grid and uses ElementName for binding (UIElement to UIElement binding) or use the RelativeResource and keep finding Ancestor until we reach the Window type and get the DataContext property from there or find the Ancestor level few level up and do the same. I uses the second approach.

Code Snippet
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},
                                Path=DataContext.Headers}">

 

At this stage we got the data, now the next question is how to display it. Also noticed that we uses the ItemsControl class, because there are more than one header value (it is ObservableCollection). We uses the ItemPanel template and set the StackPanel orientation to Horizontal to give the list header effect. To display the data we define the simple data template and bind the properties of my header class (to be more accurate ColumnData class) accordingly. Here is a code to define the header.

Code Snippet
<DataGridTemplateColumn.HeaderTemplate>
    <DataTemplate>
        <ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},
            Path=DataContext.Headers}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
                                            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Margin="2" Width="{Binding Width}" Foreground="{Binding Color}" Text="{Binding Data}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>

 

In the same way we can define the cell template. Defining cell template is even easier, because we already have access to Columns collection and we don’t have to do anything special like relative binding or UIElement to UIElement binding. Here is a code to define the cell template.

Code Snippet
<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
        <ItemsControl ItemsSource="{Binding ColumnsData}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBox Margin="2" Width="{Binding Width}" Text="{Binding Data}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>

 

That’s all we have to do. Here is complete XAML code of the program.

Code Snippet
<Window x:Class="WpfGridDynamicColumn.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
        Title="Dynamic Column Data Grid" Height="350" Width="525">
    <Grid>
        <DataGrid Margin="5" AutoGenerateColumns="False" ItemsSource="{Binding Columns}">
            <DataGrid.Columns>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.HeaderTemplate>
                        <DataTemplate>
                            <ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},
                                Path=DataContext.Headers}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <StackPanel Orientation="Horizontal"/>
                                    </ItemsPanelTemplate>
                                                                </ItemsControl.ItemsPanel>
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <TextBlock Margin="2" Width="{Binding Width}" Foreground="{Binding Color}" Text="{Binding Data}"/>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </DataTemplate>
                    </DataGridTemplateColumn.HeaderTemplate>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ItemsControl ItemsSource="{Binding ColumnsData}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <StackPanel Orientation="Horizontal"/>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <TextBox Margin="2" Width="{Binding Width}" Text="{Binding Data}"/>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

 

And here is complete C# code of the program with some sample data.

Code Snippet
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Media;

namespace WpfGridDynamicColumn
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new Table();
        }       
    }

    public class Table
    {
        public ObservableCollection<Column> Columns
        { get; set; }

        public ObservableCollection<ColumnData> Headers
        { get; set; }

        public Table()
        {
            Columns = new ObservableCollection<Column>();
            Headers = new ObservableCollection<ColumnData>();

            Columns.Add(new Column()
                {
                    ColumnsData = new ObservableCollection<ColumnData>()
                    {
                        new ColumnData()
                        {
                            Data = "row 1 col 1",
                            Width = 100
                        },
                        new ColumnData()
                        {
                            Data = "row 1 col 2",
                            Width = 125
                        },
                        new ColumnData()
                        {
                            Data = "row 1 col 3",
                            Width = 150
                        }
                    }
                });

            Columns.Add(new Column()
                {
                    ColumnsData = new ObservableCollection<ColumnData>()
                    {
                        new ColumnData()
                        {
                            Data = "row 2 col 1",
                            Width = 100
                        },
                        new ColumnData()
                        {
                            Data = "row 2 col 2",
                            Width = 125
                        },
                        new ColumnData()
                        {
                            Data = "row 2 col 3",
                            Width = 150
                        }
                    }
                });

            Columns.Add(new Column()
                {
                    ColumnsData = new ObservableCollection<ColumnData>()
                    {
                        new ColumnData()
                        {
                            Data = "row 3 col 1",
                            Width = 100
                        },
                        new ColumnData()
                        {
                            Data = "row 3 col 2",
                            Width = 125
                        },
                        new ColumnData()
                        {
                            Data = "row 3 col 3",
                            Width = 150
                        }
                    }
                });

            Columns.Add(new Column()
                {
                    ColumnsData = new ObservableCollection<ColumnData>()
                    {
                        new ColumnData()
                        {
                            Data = "row 4 col 1",
                            Width = 100
                        },
                        new ColumnData()
                        {
                            Data = "row 4 col 2",
                            Width = 125
                        },
                        new ColumnData()
                        {
                            Data = "row 4 col 3",
                            Width = 150
                        }
                    }
                });

            Headers.Add(new ColumnData()
            {
                Data = "Column 1 Title",
                Width = 100,
                Color = Brushes.Red
            });

            Headers.Add(new ColumnData()
            {
                Data = "Column 2 Title",
                Width = 125,
                Color = Brushes.Blue
            });

            Headers.Add(new ColumnData()
            {
                Data = "Column 3 Title",
                Width = 150,
                Color = Brushes.Green
            });
        }
    }

    public class Column
    {
        public ObservableCollection<ColumnData> ColumnsData
        { get; set; }
    }

    public class ColumnData
    {
        public string Data
        { get; set; }

        public int Width
        { get; set; }

        public Brush Color
        { get; set; }
    }
}

 

Here is the output of the program.

DynamicColumnGrid_01

I just added one more column in the code. Here is a code of it.

Code Snippet
Columns.Add(new Column()
    {
        ColumnsData = new ObservableCollection<ColumnData>()
        {
            new ColumnData()
            {
                Data = "row 1 col 1",
                Width = 100
            },
            new ColumnData()
            {
                Data = "row 1 col 2",
                Width = 125
            },
            new ColumnData()
            {
                Data = "row 1 col 3",
                Width = 150
            },
            new ColumnData()
            {
                Data = "row 1 col 4",
                Width = 75
            }
        }
    });

Columns.Add(new Column()
    {
        ColumnsData = new ObservableCollection<ColumnData>()
        {
            new ColumnData()
            {
                Data = "row 2 col 1",
                Width = 100
            },
            new ColumnData()
            {
                Data = "row 2 col 2",
                Width = 125
            },
            new ColumnData()
            {
                Data = "row 2 col 3",
                Width = 150
            },
            new ColumnData()
            {
                Data = "row 2 col 4",
                Width = 75
            }
        }
    });

Columns.Add(new Column()
    {
        ColumnsData = new ObservableCollection<ColumnData>()
        {
            new ColumnData()
            {
                Data = "row 3 col 1",
                Width = 100
            },
            new ColumnData()
            {
                Data = "row 3 col 2",
                Width = 125
            },
            new ColumnData()
            {
                Data = "row 3 col 3",
                Width = 150
            },
            new ColumnData()
            {
                Data = "row 3 col 4",
                Width = 75
            }  
        }
    });

Columns.Add(new Column()
    {
        ColumnsData = new ObservableCollection<ColumnData>()
        {
            new ColumnData()
            {
                Data = "row 4 col 1",
                Width = 100
            },
            new ColumnData()
            {
                Data = "row 4 col 2",
                Width = 125
            },
            new ColumnData()
            {
                Data = "row 4 col 3",
                Width = 150
            },
            new ColumnData()
            {
                Data = "row 4 col 4",
                Width = 75
            }
        }
    });

Headers.Add(new ColumnData()
{
    Data = "Column 1 Title",
    Width = 100,
    Color = Brushes.Red
});

Headers.Add(new ColumnData()
{
    Data = "Column 2 Title",
    Width = 125,
    Color = Brushes.Blue
});

Headers.Add(new ColumnData()
{
    Data = "Column 3 Title",
    Width = 150,
    Color = Brushes.Green
});

Headers.Add(new ColumnData()
{
    Data = "Column 3",
    Width = 75,
    Color = Brushes.Brown
});

 

Now it displays four columns without changing anything in XAML.

Here is an out put of this program.

DynamicColumnGrid_02


Responses

  1. […] with Dynamic Columns Revisited We saw one example of dynamic columns here. Now we are going to expand it little bit and introduce different controls in the data grid. We can […]

  2. But the colums are not sortable, right?

    • Yes because technically it is one column contains grid layout to display multiple data. You can truly create dynamic columns using C# code and insert columns during runtime.

      Regards
      Zeeshan Amjad

  3. i like yr code verymuch and make my observable collection to learn very easier. Thanks alot.

    is there a method to convert a datable to observable collection without specifying any column names becoz column may vary and i dont like to add each row by iterating (which will affect the performance)

  4. how can we implement this in extendeddatagrid

    • I am not sure if I understand your question correctly or not. Can you please explain it a bit more. Thanks for visiting the blog.

  5. Zeeshan,

    Thank you very much, this post is very helpful and very clearly written!

    • Thanks to like it. I am glad that it helped.

  6. Thank you, It’s helpful

  7. This post was very helpful and worked great for me. Thanks for your post!
    A question:

    Is it possible to make the columns sortable, e.g. sort when header is clicked?
    In my example it does not seem to sort. Any idea?
    Thanks in advance!


Leave a comment

Categories