Part1: AvalonControlsLibrary DataGridView

Intro

In real life we developers deal with a lot of data. Our job is to make this data easily accessible to the user… Unfortunately WPF does not ship with a native DataGrid (at least not for now 😀 ). A DataGrid is a very handy control to have because you can just feed it a list of objects and it creates columns for each property of the object. On the other hand WPF comes with a ListView, and may I say it is a very good and powerful control. The issue with the ListView is that it cannot auto generate columns for you. Also it does not support sorting or at least does not support it easily

Enter AvalonControlLibrary….

I created a DataGridView for WPF in AvalonControlsLibray v1. This control is able to look at the type of objects you are feeding it and automatically create the columns for that object. I decided to refactor (and also fix some minor bugs 😀 ) this control to also support new features like EditMode and Custom CollectionBehaviour (these new features will be included in AvalonControlsLibrary v3).

P.S I must reveal a secret AvalonControlsLibrary DataGridView is not really a DataGrid…It is just a ListView on steroids 😀

What this article is all about

— Managing the AutoGeneration of columns
— Managing Properties of type Collections
— Sorting the data in the DataGridView
— What’s Next?

Managing the AutoGeneration of columns

Lets start from the basics. How can one use this control to autogenerate the columns. Well it quite easy just feed the data and the DataGridView will do all the work for you. Long story short, the DataGridView will use reflection to look up all the properties and create a column for each one.

<controls:DataGridView ItemsSource="{Binding}" />

When I created the auto generation of columns I was aware that sometimes the user wants to have more control on how the columns get generated so I decided to use a declarative approach for this problem. Basically you can use a special Attribute called DataGridViewPropertyDescriptor to decorate a specific property. This DataGridViewPropertyDescriptor lets you manage how the column for a specific property should be generated.

The following is a list of properties that you can use with the DataGridViewPropertyDescriptor

Property Name Description
DisplayName This is the text used for the Column Header
Position This lets you decide the order of the columns in the DataGridView. The position you assign, do not need to be sequential. (ex: you can have 1 and then 80 etc… )
Exclude If you pass true, the property will be excluded from the auto generation
CollectionBehaviour This lets you decide how you want to display a collection inside the column (this will be explained further down in the article in the section “Managing Properties of type Collections”)
CollectionNotificationStrategy This property accepts a type. The type should be the type of object that will handle the collection for you. (this will be explained further down in the article in the section “Managing Properties of type Collections”)
SortName The name of property to sort (this will be explained in the section “Sorting the data in the DataGridView”)

The DataGridViewPropertyDescriptor also has 3 constructors as shown below

public DataGridViewPropertyDescriptorAttribute(bool exclude)

As you can imagine, this lets you exclude a specific property from being generated as a column in the UI.

public DataGridViewPropertyDescriptorAttribute(string bindingPath, string displayName)

This constructor lets you specify the bindingPath (which is the name of property being applied to) and the display name that is the text to display in the header row of the column.

public DataGridViewPropertyDescriptorAttribute(string bindingPath, string displayName, string sortName)

This constructor lets you also specify the sortName that is used for sorting (explained in the section “Sorting the data in the DataGridView”)

If you want to use the Position or any other property that this attribute exposes you can do so by using named parameters like so.

[DataGridViewPropertyDescriptor("Languages", "Langs", Position = 99,
    CollectionBehaviour = CollectionBehaviour.LastValue)]

Here I am setting the Position and also the CollectionBehaviour property.

Managing Properties of type Collections

When I was writing the DataGridView I asked myself… What about Collections?… Many times we have a collection as a property of our object… How should we display it? I guess there are multiple strategies how a user would want to do this. For example maybe you want to show the LastValue or the Count of objects you have in the collection. So I created a property called CollectionBehaviour that is basically an enum like so

/// <summary>
/// The behaviour to have for a collection
/// </summary>
public enum CollectionBehaviour
{
    /// <summary>
    /// Does not use this field
    /// </summary>
    None,
    /// <summary>
    /// Gets this last value added in the collection
    /// </summary>
    LastValue,
    /// <summary>
    /// Gets the count of the collection
    /// </summary>
    Count
}

So by setting this property you can specify that you want to display the data of the collection by LastValue or display the Count of object you have in the collection. The value will be updated if the collection implements INotifyCollectionChanged. Basically this works by injecting a converter in the binding to show the data for the property.

I decide to open the API for more flexible use. Basically I added a property called CollectionNotificationStrategy that accepts a type of object that will do the work of showing the collection data. So if you want, now you can create your own object that displays the data for the collection, the question is how …. 🙂

It is quite easy (or is it?? hihi ), lets do an example.. Lets say that I want to show all fields of the collection ‘|’ separated. First you have to create a class that implements the ICollectionNotificationStrategy (that is in the AC.AvalonControlsLibrary.Core namespace). Now this interface has a method called GetDisplayMember that accepts a parameter IEnumerable source. The “source” parameter is the collection being passed to you and at this point you may do whatever you like with it. Here is an sample implementation (also supplied in the demo project available for download)

public class DelimitedCollectionStrategy : ICollectionNotificationStrategy
{
    #region ICollectionNotificationStrategy Members

    public object GetDisplayMember(IEnumerable source)
    {
        if (source != null)
        {
            StringBuilder builder = new StringBuilder();
            foreach (object item in source)
            {
                builder.Append(item);
                builder.Append(" | ");
            }
            return builder.ToString();
        }
        return String.Empty;
    }

    #endregion
}

Once you create you own CollectionNotificationStrategy, you have to just specify the named parameter CollectionNotificationStrategy in the attribute for the property like so…

[DataGridViewPropertyDescriptor("Languages", "Programming Languages",
            CollectionNotificationStrategy = typeof(DelimitedCollectionStrategy))]

And you are done…

Sorting the data in the DataGridView (and also ListView)

Unfortunately the WPF Listview does not support sorting, so I decided to support sorting myself. What is cool about this is that I not only support it for the DataGridView but also for ListView (that uses a GridView as View).

Basically you enable sorting by using an attached property called GridViewSortHandler.GridViewSortHandler ( this is in the namespace AC.AvalonControlsLibrary.Core). This property accepts a GridViewSortHandler. This object takes care of all the sorting work for you. You must create a GridViewSortHandler and pass the instance of that object to the attached property. Something like this

<core:GridViewSortHandler x:Key="sortHandler" />

Once you have your GridViewSortHandler you can then pass that object to the attached property like this

<controls:DataGridView ItemsSource="{Binding}"
    core:GridViewSortHandler.GridViewSortHandler="{StaticResource sortHandler}"/>

Please note I defined the sortHandler in the Resources section.

You can also specify 3 DataTemplate for the GridViewSortHandler to tell it how to display the column header when sorting is active (please note: these properties are optional). The following are the properties

ColumnHeaderSortedAscendingTemplate The name of the DataTemplate to show when the sort is being applied in Ascending order
ColumnHeaderSortedDescendingTemplate The name of the DataTemplate to show when the sort is being applied in Descending order
ColumnHeaderNotSortedTemplate The name of the DataTemplate to show when the sort is not being applied

Here is an example of such a DataTemplate

<DataTemplate x:Key="HeaderTemplateArrowDown">
    <DockPanel>
        <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>
        <Path x:Name="arrow" StrokeThickness = "1"
            Fill = "Gray" Data="M 5,5 L 10,10 L 15,5 L 5,5"/>
    </DockPanel>
</DataTemplate>

Note: {Binding} will be the Header Text.

The SortHandler works only if you are setting the SortName property of the DataGridViewPropertyDescriptor. If you are using the DataGridView than this comes for free. Basically the autogeneration of columns sets this property for you. But I promised you that this would work in a native ListView as well… Here is how you can use this for a Listview…

Basically the DataGridViewColumn inherits from GridViewColumn so you can use the DataGridViewColumn to populate your ListView columns. When doing so you must also set the SortPropertyName to the name of the property you are binding to. Here is an example of using the GridViewSortHandler with a ListView

<ListView ItemsSource="{Binding}"
          core:GridViewSortHandler.GridViewSortHandler="{StaticResource sortHandler}">
    <ListView.View>
        <GridView>
            <controls:DataGridViewColumn Header="Name" SortPropertyName="Name"
                DisplayMemberBinding="{Binding Name}"/>
            <controls:DataGridViewColumn Header="Surname" SortPropertyName="Surname"
                DisplayMemberBinding="{Binding Surname}"/>
        </GridView>
    </ListView.View>
</ListView>

There you are. Now that you have all that you are done. You have a Listview / DataGridView with sorting !

But there is more…..

The GridViewSortHandler uses SortDescriptions by default. Now I am not saying that SortDescriptions are slow but if you have a huge amount of data I would not suggest to use that… I created another attached property called GridViewSortHandler.CustomComparer. This property lets you creating your own ListComparer and this will speed up the sorting a lot because this approach does not use Reflection. So how can we create one of these 🙂

First we have to create a new class that inherits from GridViewCustomComparer. GridViewCustomComparer is a special class in AvalonControlsLibrary that manages the sorting. This class makes the whole process much easier. Basically all you have to do is to override the CompareOverride method and do your thing in there. The method signature looks like this

public override int CompareOverride(object x, object y)

GridViewCustomComparer has two modes. Basically you can ask the GridViewCustomComparer to pass the value of the property being sorted or you can ask it to pass the whole object instead. You specify this by passing a boolean in the base constructor like so

public WPFDiscipleComparer()
            : base(true)
        { }

IMPORTANT: If you pass false you are up to no good; might as well not create GridViewCustomComparer. I left this option available for special cases only! So please consider it before using it …

Let’s say that you specified true here, so the whole object will be passed to you for the sorting. First thing you must know is which property is being sorted. You can get this info by looking at the SortPropertyName of the GridViewCustomComparer. You must also know which way the sorting is being applied, ascending or descending. You can get this by looking at the Direction property of the GridViewCustomComparer. Ok now that we know how to get this info let’s look at an example implementation of a GridViewCustomComparer.

public class WPFDiscipleComparer : GridViewCustomComparer
{
    /// <summary>
    /// Default construtor
    /// </summary>
    public WPFDiscipleComparer()
        : base(true) { }

    public override int CompareOverride(object x, object y)
    {
        WPFDisciple x1 = (WPFDisciple)x;
        WPFDisciple y1 = (WPFDisciple)y;

        int result = 0;

        //this will give you the property that is currently being sorted
        switch (base.SortPropertyName)
        {
            case "Name":
                result = x1.Name.CompareTo(y1.Name);
                break;
            case "Surname":
                result = x1.Surname.CompareTo(y1.Surname);
                break;
            default:
                return 0;
        }

        if (base.Direction == ListSortDirection.Ascending)
            return result;
        else
            return (-1) * result;
    }
}

As you can see it is very easy to create a comparer for you own class and believe me the performance gain that you get is simple amazing.. If you download the AvalonControlsLibrary v1 or even v2 open the Demo application and go to the Sorting demo. Over there I showed a listview with 100’s of records sorting without a CustomComparer and then the same thing with a SortComparer and the difference is amazing!

Last step we have to do is to use our GridViewCustomComparer for a ListView or a DataGridView. This is quite easy basically it is the same as setting the GridViewSortHandler but you set the CustomComparer attached property instead. Like so….

<Window.Resources>
    <core:GridViewSortHandler x:Key="sortHandler"

    <local:WPFDiscipleComparer x:Key="customSort"/>
</Window.Resources>

<controls:DataGridView ItemsSource="{Binding}"
         core:GridViewSortHandler.GridViewSortHandler="{StaticResource sortHandler}"
         core:GridViewSortHandler.CustomComparer="{StaticResource customSort}"/>

Keep in mind that in order to have sorting the GridViewSortHandler is compulsory and it is useless to set the CustomComparer without the GridViewSortHandler.

What’s Next?

Well today I explained how the AvalonControlsLibarary DataGridView works and also how sorting works. I also revealed a new feature of AvalonControlsLibray v3 “CollectionNotificationStrategy”. In Part 2 I will show you how to use the DataGridView in edit mode so that the user can edit data from the DataGridView 🙂

Note: I decided to make available all code for the CollectionNotificationStrategy in this demo. The final version of the DataGridView will be on CodePlex in the AvalonControlsLibrary v3. I will not update the source code of this demo. If there are any bug fixes for the control you will have to download the source from CodePlex…. or if you want just send me an email and I will send you the code 😀

Download Demo Project and Source Code

kick it on DotNetKicks.com

6 thoughts on “Part1: AvalonControlsLibrary DataGridView

  1. Marlon,
    Thanks again for your post. Just to clarify I have a large number of columns and I would like to make static the first column and be able to scroll the remaining columns whilst keeping the first column in view. Does the DataGridView enable such a feature?

    Thanks,
    Glenn

  2. Pingback: ICollectionView explained « C# Disciples

  3. Pingback: How to Re Style the AvalonControlsLibrary DataGridView « C# Disciples

Leave a comment