Table of Contents

Introduction

The DataGrid control in WPF provides a flexible way to display, sort, group and filter tabular data. A common requirement is the ability to export this data to some physical file that can be imported into Microsoft Excel or some similar software for further processing.

This post provides an example of how you could create a .csv file out of a data-bound collection of objects of any type by using reflection and extending the functionality of the System.Windows.Controls.DataGrid control by implementing an extension method.

CSV

CSV stands for “Comma Separated Values” and is a common and widely supported file format for storing tabular data in plain text. A CSV file consists of a number of records, separated by line breaks. Each record consists of columns that are separated by some character, typically a comma (,) or a semicolon (;).

Sample code

You can download the sample code in this post from the MSDN Samples Code Gallery here.

The sample application consists of a single window with two public properties that returns collections of Product objects and related Category objects respectively, and an event handler for an export button that will call the extension method of the DataGrid that creates the actual export file:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
  
        /* initialize categories */
        this.Categories = new List<Category>();
        this.Categories.Add(new Category() { Id = 1, Name = "Category A" });
        this.Categories.Add(new Category() { Id = 2, Name = "Category B" });
  
        /* initialize products */
        this.Products = new List<Product>();
        for (int i = 0; i < 100; ++i)
        {
            Product item = new Product();
            item.Id = i;
            item.Name = string.Format("Item {0}", i);
  
            bool b = (i % 2).Equals(0);
            item.IsAvailable = b;
            item.Category = b ? this.Categories[0] : this.Categories[1];
            this.Products.Add(item);
        }
    }
  
    public IList<Category> Categories
    {
        get;
        private set;
    }
  
    public IList<Product> Products
    {
        get;
        private set;
    }
  
    private void Export_Click(object sender, RoutedEventArgs e)
    {
        const string path = "test.csv";
        IExporter csvExporter = new CsvExporter(';');
        dataGrid.ExportUsingRefection(csvExporter, path);
    }
}
  
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsAvailable { get; set; }
    public Category Category { get; set; }
}
  
public class Category
{
    public int Id { get; set; }
    public   
public class Category
{
e="color:#006699;font-weight:bold;">string Name { get; set; }
}

The XAML markup then defines a DataGrid control that is bound to the Products collection, with a column per property of the Product type:

<Window x:Class="Mm.ExportableDataGrid.Wpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <CollectionViewSource x:Key="categories" Source="{Binding Categories}"/>
    </Window.Resources>
    <DockPanel>
        <Button Content="Export data" Click="Export_Click" DockPanel.Dock="Bottom"/>
        <DataGrid x:Name="dataGrid" ItemsSource="{Binding Products}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
             n>            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
            &nb   <DataGridTextColumn Header="Id" Binding="{Binding Id}" />
                <DataGridCheckBoxColumn Header="Checked" Binding="{Binding IsAvailable}"/>
                <DataGridComboBoxColumn Header="Category"
                                        ItemsSource="{Binding Source={StaticResource categories}}"
                                        SelectedValueBinding="{Binding Category}" DisplayMemberPath="Name"/>
                <DataGridHyperlinkColumn ContentBinding="{Binding Name}" Binding="{Binding Name}"/>
            </DataGrid.Columns>
        </DataGrid>
    </DockPanel>
</Window>

Note that the ItemsSource property of the DataGridComboBoxColumn cannot be bound directly to the Categories collection of the DataContext. This is because the columns in the DataGrid don’t inherit the DataContext as they are never added to the visual tree.

Extension methods

An extension method is a feature in C# that lets you “add” new methods to an existing type without creating a new derived type or otherwise modifying the original type. To implement an extension method for some type you simply create a static method in a static class. The first parameter of the method specifies the type that the method operates on and it must be preceded with the this modifier.

Below is for example how you would create an extension method for the built-in DataGrid control that takes two additional parameters. This method can then be called for any DataGrid provided that it has an access modifier that makes it visible to the client code that calls it.

public static class DataGridExtensions
{
    public static void ExportUsingRefection(this DataGrid grid, IExporter exporter, string exportPath)
    {
        ...
    }
}

 

IExporter

Besides the string argument that specifies the path where the export file will be saved on the disk, the ExportUsingReflection extension method above also takes a parameter of type IExporter. This is a custom interface for creating the rows and columns of the file:

public interface IExporter
{
    void AddColumn(string value);
    void AddLineBreak();
    string Export(string exportPath);
}

By letting classes that implement this interface take care of the creation of the file, the application will be able to support additional export formats without requiring any changes to the implementation of the export method itself. This is actually a sample implementation of the builder design pattern where the interface above acts as the builder, the CsvExporter class below as a concrete builder and where the export file becomes the final product.

public class CsvExporter : IExporter
{
    private readonly StringBuilder sb = new StringBuilder();
    private readonly string _delimiter;
class CsvExporter : IExporter
{
    private readonly StringBuilder sb = new StringBuilder();
  
    public CsvExporter(char delimiter)
    {
        _delimiter = delimiter.ToString();
    }
  
    public char Delimiter
    {
        get { return _delimiter[0]; }
    }
  
    public void AddColumn(string value)
    {
        sb.Append(value.Replace(_delimiter,
            string.Format("\"{0}\"", _delimiter)));
        sb.Append(_delimiter);
    }
  
    public void AddLineBreak()
    {
        sb.Remove(sb.Length - 1, 1); //remove trailing delimiter
        sb.AppendLine();
    }
  
    public string Export(string exportPath)
    {
        if (string.IsNullOrEmpty(exportPath))
        {
            Random rnd = string new Random();
            exportPath = string.Format("{0}.csv", rnd.Next());
        }
        else if (!Path.GetExtension(exportPath).ToLower().Equals(".csv"))
        {
            throw new ArgumentException("Invalid file extension.", "exportPath");
        }
  
  
        File.WriteAllText(exportPath, sb.ToString().Trim());
        sb.Clear();
        return exportPath;
    }
}

If you are not familiar with design patterns, these provide solutions to common software design problems and are all about reusable design and interactions of objects.

DataGrid

Each row in a DataGrid control is bound to an object in the source collection and each column is bound to a property of an object in the same collection. By default, if you don’t set the AutoGenerateColumns property to false, the DataGrid control generates columns automatically when you set the ItemsSource property.

The DataGrid control has a Column property that contains all the columns in it. These must be of type System.Windows.Controls.DataGridColumn and there are five built-in derived types of this class; DataGridCheckBoxColumn, DataGridHyperlinkColumn and DataGridTextColumn which all derives from the DataGridBoundColumn base type that adds support for binding, DataGridComboBoxColumn and DataGridTemplateColumn. The type of column that is automatically generated when the AutoGenerateColumns is set to true depends on the type of the source property.

Any System.String (string) property of the class that represents the objects in the ItemsSource collection will generate a DataGridTextColumn, a System.Boolean (bool) property will generate a DataGridCheckBoxColumn, a System.Uri property will generate a DataGridHyperlinkColumn and an enumeration type (System.Enum) property will generate a DataGridComboBoxColumn.

The DataGridTemplateColumn is used when you want to define a custom column by specifying templates to be used when displaying and editing values.

ICollectionView

Whenever you bind to some collection of data in WPF, you are always binding to an automatically generated view and not to the actual collection itself. A view is a class that implements the System.ComponentModel.ICollectionView interface and provides functionality to sort, filter, group and keeping track of the current item in a collection. All collections have a default view which is shared by all bindings to the collection.

The default collection view for a source collection that implements the System.Collections.IList interface is the System.Windows.Data.ListCollectionView class. You can get the default view of a collection by using the static CollectionViewSource.GetDefaultView method and this comes in handy when you want to export sorted or grouped data from a DataGrid.

Order and Sorting

The user can sort columns in a DataGrid control by clicking the column header, provided that the CanUserSortColumns of the DataGrid is set to its default value of true. Also, the order of columns in the source collection doesn’t necessarily determine the order in which they appear in the DataGrid. If the CanUserReorderColumns property of the DataGrid control is set to true, which it also is by default, the user can change the order by dragging column headers with the mouse. There is a DisplayOrder property of the DataGridColumn class that determines the current position of the column in the grid, by which you can sort the columns before iterating them through in the export method:

public static void ExportUsingRefection(this DataGrid grid, IExporter exporter, string exportPath)
{
    if (grid.ItemsSource == null || grid.Items.Count.Equals(0))
        throw new InvalidOperationException("You cannot export any data from an empty DataGrid.");
  
    IEnumerable<DataGridColumn> columns = grid.Columns.OrderBy(c => c.DisplayIndex);
    ICollectionView collectionView = CollectionViewSource.GetDefaultView(grid.ItemsSource);
    foreach (object o in collectionView)
    {
        if (o.Equals(CollectionView.NewItemPlaceholder))
            continue;
  
        foreach (DataGridColumn column in columns)
        {
            ...
continue;
  
        foreach (DataGridColumn column         }
    exporter.AddLineBreak();
    }
    /* Create and open export file */
    Process.Start(exporter.Export(exportPath));
}