Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / WPF

Binding a ListView to a Data Matrix

Rate me:
Please Sign up or sign in to vote.
5.00/5 (23 votes)
15 May 2009MIT3 min read 160.5K   4K   63   32
Binding a WPF ListView to a DataMatrix with columns determined at runtime

DynamicListView

Introduction

This article mainly shows how to bind a WPF ListView to a DataMatrix (an undefined data source with dynamic columns) where the ListView columns cannot be determined until runtime.

This article assumes that the reader has prior knowledge of WPF Data Binding and Dependency Properties. For more information on these topics, check out the following articles here on the CodeProject.

Background

Most of the work I do consists of churning undefined (but structured) datasets and generating meaningful information and trends - i.e., Data Analysis, Knowledge Discovery, and Trend Analysis. And as such, most of the data I get are never really similar. In situations like these, you need to come up with a generic way of doing basic stuff like data presentations (reporting) for any kind of dataset. This led me to create a DataMatrix a long time ago. But as we all know, it is not very obvious how to handle anonymous data in WPF.

I know I could have just used a regular DataTable and bound it to the WPF DataGrid, but I can assure you, when you deal with millions of records that cannot be virutalized, a DataTable becomes very heavy, hogs memory, and results in too much messy code [behind] with "Magic" strings all over the place. Besides, we have to keep things as M-V-VM as possible.

Using the Code

Basically, what we are going to do is bind a ListView to a DataMatrix class as shown below:

C#
public class DataMatrix : IEnumerable
{
    public List<MatrixColumn> Columns { get; set; }
    public List<object[]> Rows { get; set; } 

    IEnumerator IEnumerable.GetEnumerator()
    {
        return new GenericEnumerator(Rows.ToArray());
    }
}

public class MatrixColumn
{
    public string Name { get; set; }
    public string StringFormat { get; set; }
}

Note that the MatrixColumn can always be extended with all the properties you need to format the GridViewColumns.

To achieve this, we need to create a DependencyProperty which will be added to the ListView so that when the DataMatrixSource is set, we can then bind the undefined columns to the ListView at runtime. Below is the class that contains the DependencyProperty.

C#
public class ListViewExtension
{
    public static readonly DependencyProperty MatrixSourceProperty =
        DependencyProperty.RegisterAttached("MatrixSource",
        typeof(DataMatrix), typeof(ListViewExtension),
            new  FrameworkPropertyMetadata(null,
                new  PropertyChangedCallback(OnMatrixSourceChanged)));

    public static DataMatrix GetMatrixSource(DependencyObject d)
    {
        return (DataMatrix)d.GetValue(MatrixSourceProperty);
    }

    public static void SetMatrixSource(DependencyObject d, DataMatrix value)
    {
        d.SetValue(MatrixSourceProperty, value);
    }

    private static void OnMatrixSourceChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        ListView listView = d as ListView;
        DataMatrix dataMatrix = e.NewValue as DataMatrix;

        listView.ItemsSource = dataMatrix; 
        GridView gridView = listView.View as GridView; 
        int count = 0; 
        gridView.Columns.Clear();
        foreach (var col in dataMatrix.Columns)
        {
            gridView.Columns.Add(
                new  GridViewColumn
                    {
                        Header = col.Name,
                        DisplayMemberBinding = new Binding(stringM.Format("[{0}]", count))
                    }); 
            count++;
        }
    }
}

This will then be attached to the ListView as shown below:

XML
<ListView cc:ListViewExtension.MatrixSource="{Binding MyDataMatrix}"/>

And that's all. At runtime, your columns will be added dynamically to the ListView's GridView. Note that the ListView's ItemsSource property expects an IEnumerable as the binding element; that is why the matrix implements this interface. This is provided by the GenericEnumerator shown below:

C#
class GenericEnumerator : IEnumerator
{
    private readonly object[] _list;
    // Enumerators are positioned before the first element
    // until the first MoveNext() call. 

    private int _position = -1;

    public GenericEnumerator(object[] list)
    {
        _list = list;
    }

    public bool MoveNext()
    {
        _position++;
        return (_position < _list.Length);
    }

    public void Reset()
    {
        _position = -1;
    }

    public object Current
    {
        get
        {
            try { return _list[_position]; }
            catch (IndexOutOfRangeException) { throw new InvalidOperationException(); }
        }
    }
}

For this example, I used the generic Northwind database and bound to the Orders table (orders count per month/year); then I built a simple DataMatrix from the cross tabulation of the Year (row source) vs. Months (columns source) against the sum for the number of orders.

Given the Orders table, you can generate and bind to a well-known dataset to get the "Number of Orders per Month" as follows:

C#
var orders = from o in db.Orders
 group o by new { o.OrderDate.Value.Year, o.OrderDate.Value.Month }
     into g
     select new MonthlyOrderCount() { Year = g.Key.Year, 
            Month = g.Key.Month, NumberOfOrders = g.Count() };

Which will result as shown below:

Image 2

To get a more meaningful dataset from the above query, we need to cross-tabulate the year vs. month to get the total number of orders. So, we can run the dataset through a reporting algorithm and generate a DataMatrix as follows:

Note: This is a fake DataMatrixGenerator. A real one will determine the columns (at runtime) based on the parameters specified.

C#
private static DataMatrix CreateMatrix(IEnumerable<monthlyordercount> orders)
{
    DataMatrix result = new DataMatrix {Columns = new List<matrixcolumn>(), 
                                        Rows = new List<object[]>()};
    result.Columns.Add(new MatrixColumn() { Name = "Year" });
    for (int i = 0; i < 12; i++)
        result.Columns.Add(new MatrixColumn()
        {
            Name = string.Format("{0:MMM}", new DateTime(2009, i + 1, 1))
        });
    for (int i = 1996; i < 1999; i++)
    {
        object[] row = new object[13];
        row[0] = i;
        for (int j = 1; j <= 12; j++)
        {
            int count = (from o in orders
                         where o.Year == i && o.Month == j
                         select o.NumberOfOrders).Sum();
            row[j] = count;
        }
        result.Rows.Add(row);
    }
    return result;
}

which will result as shown below:

Image 3

We can then use this result set at runtime to bind dynamically to the list view using the DataMatrix.

Points of Interest

This approach can also be used for generating flow documents.

History

  • May 15 2009: Initial release.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Architect
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Member 1497056520-Oct-20 7:37
Member 1497056520-Oct-20 7:37 
QuestionThanks Pin
Vessago23-May-20 21:20
Vessago23-May-20 21:20 
QuestionHow to use Celltemplate instead of DisplayMemberBinding in your example Pin
Dhadhan16-Jul-15 17:41
Dhadhan16-Jul-15 17:41 
GeneralMy vote of 5 Pin
Otto77713-May-12 21:46
Otto77713-May-12 21:46 
QuestionHow can I add textbox to each of the columns? Pin
ReddyKM7-Nov-11 15:11
ReddyKM7-Nov-11 15:11 
GeneralMy vote of 5 Pin
gjvdkamp28-Feb-11 8:38
gjvdkamp28-Feb-11 8:38 
QuestionHow did I miss this article for a long time?? question about GenericEnumerator() Pin
Namgeun Jeong19-Nov-10 8:43
professionalNamgeun Jeong19-Nov-10 8:43 
AnswerRe: How did I miss this article for a long time?? question about GenericEnumerator() Pin
Tawani Anyangwe19-Nov-10 9:17
Tawani Anyangwe19-Nov-10 9:17 
GeneralExcellent. Pin
ozczecho26-Aug-10 21:21
ozczecho26-Aug-10 21:21 
GeneralMy vote of 5 Pin
MSdispenser29-Jun-10 2:25
MSdispenser29-Jun-10 2:25 
GeneralAdd checkbox Pin
lizamathew5-May-10 8:36
lizamathew5-May-10 8:36 
GeneralGreat article.Can you please give me some help Pin
defanana25-Apr-10 22:37
defanana25-Apr-10 22:37 
QuestionAlso for DataGrid? Pin
ChrDressler19-Nov-09 8:45
ChrDressler19-Nov-09 8:45 
AnswerRe: Also for DataGrid? Pin
Tawani Anyangwe19-Nov-09 9:50
Tawani Anyangwe19-Nov-09 9:50 
I'm sure the DataGrid can bind directly to a DataTable so you might not need this. However, I think the same technique will work for the WPF Toolkit DataGrid.
QuestionHow can changes to the Data Matrix appear in the ListView [modified] Pin
Kortermand29-Jun-09 4:03
Kortermand29-Jun-09 4:03 
AnswerRe: How can changes to the Data Matrix appear in the ListView Pin
Tawani Anyangwe1-Jul-09 9:23
Tawani Anyangwe1-Jul-09 9:23 
GeneralNice one! Pin
Philipp Sumi26-May-09 7:25
Philipp Sumi26-May-09 7:25 
GeneralExcellent article Pin
Pete O'Hanlon25-May-09 23:42
subeditorPete O'Hanlon25-May-09 23:42 
GeneralRe: Excellent article Pin
Tawani Anyangwe26-May-09 5:20
Tawani Anyangwe26-May-09 5:20 
Generalapply cell template to the list view cell Pin
caveman_197823-May-09 10:30
caveman_197823-May-09 10:30 
GeneralRe: apply cell template to the list view cell [modified] Pin
Tawani Anyangwe26-May-09 3:43
Tawani Anyangwe26-May-09 3:43 
GeneralRe: apply cell template to the list view cell Pin
caveman_197826-May-09 7:40
caveman_197826-May-09 7:40 
GeneralRe: apply cell template to the list view cell Pin
caveman_19782-Jun-09 11:01
caveman_19782-Jun-09 11:01 
GeneralRe: apply cell template to the list view cell Pin
Tawani Anyangwe6-Jun-09 7:20
Tawani Anyangwe6-Jun-09 7:20 
GeneralRe: apply cell template to the list view cell Pin
Izhar Lotem14-Jun-10 3:02
Izhar Lotem14-Jun-10 3:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.