Click here to Skip to main content
Click here to Skip to main content
Go to top

Binding a ListView to a Data Matrix

, 15 May 2009
Rate this:
Please Sign up or sign in to vote.
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:

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.

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:

<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:

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:

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:

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.

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:

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

Share

About the Author

Tawani Anyangwe
Architect
United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberOtto77713-May-12 21:46 
QuestionHow can I add textbox to each of the columns? PinmemberReddyKM7-Nov-11 15:11 
GeneralMy vote of 5 Pinmembergjvdkamp28-Feb-11 8:38 
QuestionHow did I miss this article for a long time?? question about GenericEnumerator() PinmemberNamgeun Jeong19-Nov-10 8:43 
AnswerRe: How did I miss this article for a long time?? question about GenericEnumerator() PinmemberTawani Anyangwe19-Nov-10 9:17 
GeneralExcellent. Pinmemberozczecho26-Aug-10 21:21 
GeneralMy vote of 5 PinmemberMSdispenser29-Jun-10 2:25 
GeneralAdd checkbox Pinmemberlizamathew5-May-10 8:36 
GeneralGreat article.Can you please give me some help Pinmemberdefanana25-Apr-10 22:37 
QuestionAlso for DataGrid? PinmemberChrDressler19-Nov-09 8:45 
AnswerRe: Also for DataGrid? PinmemberTawani Anyangwe19-Nov-09 9:50 
QuestionHow can changes to the Data Matrix appear in the ListView [modified] PinmemberKortermand29-Jun-09 4:03 
AnswerRe: How can changes to the Data Matrix appear in the ListView PinmemberTawani Anyangwe1-Jul-09 9:23 
This is just a rough suggestion.
 
Given the helper method below:
 
<font color="blue">public static class</font> <font color="teal">Helpers</font>
{
    <font color="blue">public static</font> <font color="teal">IDataMatrix</font> ToDataMatrix(<font color="blue">this</font> <font color="teal">IDataReader</font> dr)
    {
        <font color="blue">int</font> fieldCount = dr.FieldCount;
        <font color="teal">IDataMatrix</font> result = <font color="blue">new</font> <font color="teal">DataMatrix</font>();
        <font color="blue">for</font> (<font color="blue">int</font> i = 0; i < fieldCount; i++)
        {
            result.AddColumn(dr.GetName(i), dr.GetFieldType(i));
        }
        <font color="blue">while</font> (dr.Read())
        {
            <font color="blue">object</font>[] row = <font color="blue">new</font> <font color="blue">object</font>[fieldCount];
 
            for (<font color="blue">int</font> i = 0; i < dr.FieldCount; i++)
            {
                row[i] = dr.GetValue(i);
            }
            result.AddRow(row);
        }
        <font color="blue">return</font> result;
    }    
}
 
You can dynamically create your matrices and bind them [to the ViewModel property] as follows:
 
<font color="blue">void</font> OnSelectionChange(<font color="blue">string</font> table)
{
    <font color="blue">string</font> sql = <font color="blue">null</font>;
    <font color="blue">if</font> (table == <font color="firebrick">"orders"</font>)
        sql = <font color="blue">"select * from orders"</font>;
    <font color="blue">else if</font> (table == <font color="firebrick">"customers"</font>)
        sql = <font color="firebrick">"select CustomerID as ID, companyName as Company from customers"</font>;
    <font color="blue">else
        throw new</font> ArgumentOutOfRangeException</font>(table);
    <font color="teal">IDataReader</font> dr = db.ExecuteReader(sql);
    <font color="blue">this</font>.MatrixSource = dr.ToDataMatrix();        
}
 
Then the INotifyPropertyChange should handle the currently displayed data in the listview.
GeneralNice one! PinmemberPhilipp Sumi26-May-09 7:25 
GeneralExcellent article PinmvpPete O'Hanlon25-May-09 23:42 
GeneralRe: Excellent article PinmemberTawani Anyangwe26-May-09 5:20 
Generalapply cell template to the list view cell Pinmembercaveman_197823-May-09 10:30 
GeneralRe: apply cell template to the list view cell [modified] PinmemberTawani Anyangwe26-May-09 3:43 
GeneralRe: apply cell template to the list view cell Pinmembercaveman_197826-May-09 7:40 
GeneralRe: apply cell template to the list view cell Pinmembercaveman_19782-Jun-09 11:01 
GeneralRe: apply cell template to the list view cell PinmemberTawani Anyangwe6-Jun-09 7:20 
GeneralRe: apply cell template to the list view cell PinmemberIzhar Lotem14-Jun-10 3:02 
GeneralRe: apply cell template to the list view cell Pinmemberkrishnan.s25-Aug-10 16:17 
GeneralVery nice sir PinmvpSacha Barber17-May-09 6:45 
GeneralRe: Very nice sir PinmemberTawani Anyangwe17-May-09 7:55 
GeneralGreat article ... I've been looking for. Pinmemberpiere7716-May-09 13:45 
GeneralRe: Great article ... I've been looking for. PinmemberTawani Anyangwe17-May-09 7:57 
GeneralVery nice PinmemberGSerjo16-May-09 8:54 
GeneralRe: Very nice PinmemberTawani Anyangwe17-May-09 7:57 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140926.1 | Last Updated 15 May 2009
Article Copyright 2009 by Tawani Anyangwe
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid