[WPF] Automatically sort a GridView when a column header is clicked

It’s quite simple, in WPF, to present data in a grid, thanks to the GridView class. If you want to sort it, however, it gets a little harder… With the DataGridView in Windows Forms, it was “automagic” : when the user clicked a column header, the grid was automatically sorted. To achieve the same behavior in WPF, you need to get your hands dirty… The method recommended by Microsoft is described in this article ; it is based on the Click event of the GridViewColumnHeader class. In my view, this approach has two major drawbacks :

  • The sorting must be done in code-behind, something we usually want to avoid if the application is designed according to the MVVM pattern. It also makes the code harder to reuse.
  • This method assumes that the text of the column header is also the name of the property to use as the sort criteria, which isn’t always true, far from it… We could use the DisplayMemberBinding of the column, but it’s not always set (for instance if a CellTemplate is defined instead).

After spending a long time trying to find a flexible and elegant approach, I came up with an interesting solution. It consists of a class with a few attached properties that can be set in XAML.

This class can be used as follows :

    <ListView ItemsSource="{Binding Persons}"
          IsSynchronizedWithCurrentItem="True"
          util:GridViewSort.AutoSort="True">
        <ListView.View>
            <GridView>
                <GridView.Columns>
                    <GridViewColumn Header="Name"
                                    DisplayMemberBinding="{Binding Name}"
                                    util:GridViewSort.PropertyName="Name"/>
                    <GridViewColumn Header="First name"
                                    DisplayMemberBinding="{Binding FirstName}"
                                    util:GridViewSort.PropertyName="FirstName"/>
                    <GridViewColumn Header="Date of birth"
                                    DisplayMemberBinding="{Binding DateOfBirth}"
                                    util:GridViewSort.PropertyName="DateOfBirth"/>
                </GridView.Columns>
            </GridView>
        </ListView.View>
    </ListView>

The GridViewSort.AutoSort property enables automatic sorting for the ListView. The GridViewSort.PropertyName property, defined for each column, indicates the property to use as the sort criteria. There is no extra code to write. A click on a column header triggers the sorting on this column ; if the ListView is already sorted on this column, the sort order is reversed.

In case you need to handle the sorting manually, I also added a GridViewSort.Command attached property. When used with the MVVM pattern, this property allows you to bind to a command declared in the ViewModel :

    <ListView ItemsSource="{Binding Persons}"
          IsSynchronizedWithCurrentItem="True"
          util:GridViewSort.Command="{Binding SortCommand}">
    ...

The sort command takes as parameter the name of the property to use as the sort criteria.

Note : if both the Command and AutoSort properties are set, Command has priority. AutoSort is ignored.

Here is the full code of the GridViewSort class :

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace Wpf.Util
{
    public class GridViewSort
    {
        #region Attached properties

        public static ICommand GetCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(CommandProperty);
        }

        public static void SetCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(CommandProperty, value);
        }

        // Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached(
                "Command",
                typeof(ICommand),
                typeof(GridViewSort),
                new UIPropertyMetadata(
                    null,
                    (o, e) =>
                    {
                        ItemsControl listView = o as ItemsControl;
                        if (listView != null)
                        {
                            if (!GetAutoSort(listView)) // Don't change click handler if AutoSort enabled
                            {
                                if (e.OldValue != null && e.NewValue == null)
                                {
                                    listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                                if (e.OldValue == null && e.NewValue != null)
                                {
                                    listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                            }
                        }
                    }
                )
            );

        public static bool GetAutoSort(DependencyObject obj)
        {
            return (bool)obj.GetValue(AutoSortProperty);
        }

        public static void SetAutoSort(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoSortProperty, value);
        }

        // Using a DependencyProperty as the backing store for AutoSort.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AutoSortProperty =
            DependencyProperty.RegisterAttached(
                "AutoSort",
                typeof(bool),
                typeof(GridViewSort),
                new UIPropertyMetadata(
                    false,
                    (o, e) =>
                    {
                        ListView listView = o as ListView;
                        if (listView != null)
                        {
                            if (GetCommand(listView) == null) // Don't change click handler if a command is set
                            {
                                bool oldValue = (bool)e.OldValue;
                                bool newValue = (bool)e.NewValue;
                                if (oldValue && !newValue)
                                {
                                    listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                                if (!oldValue && newValue)
                                {
                                    listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                }
                            }
                        }
                    }
                )
            );

        public static string GetPropertyName(DependencyObject obj)
        {
            return (string)obj.GetValue(PropertyNameProperty);
        }

        public static void SetPropertyName(DependencyObject obj, string value)
        {
            obj.SetValue(PropertyNameProperty, value);
        }

        // Using a DependencyProperty as the backing store for PropertyName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PropertyNameProperty =
            DependencyProperty.RegisterAttached(
                "PropertyName",
                typeof(string),
                typeof(GridViewSort),
                new UIPropertyMetadata(null)
            );

        #endregion

        #region Column header click event handler

        private static void ColumnHeader_Click(object sender, RoutedEventArgs e)
        {
            GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
            if (headerClicked != null)
            {
                string propertyName = GetPropertyName(headerClicked.Column);
                if (!string.IsNullOrEmpty(propertyName))
                {
                    ListView listView = GetAncestor<ListView>(headerClicked);
                    if (listView != null)
                    {
                        ICommand command = GetCommand(listView);
                        if (command != null)
                        {
                            if (command.CanExecute(propertyName))
                            {
                                command.Execute(propertyName);
                            }
                        }
                        else if (GetAutoSort(listView))
                        {
                            ApplySort(listView.Items, propertyName);
                        }
                    }
                }
            }
        }

        #endregion

        #region Helper methods

        public static T GetAncestor<T>(DependencyObject reference) where T : DependencyObject
        {
            DependencyObject parent = VisualTreeHelper.GetParent(reference);
            while (!(parent is T))
            {
                parent = VisualTreeHelper.GetParent(parent);
            }
            if (parent != null)
                return (T)parent;
            else
                return null;
        }

        public static void ApplySort(ICollectionView view, string propertyName)
        {
            ListSortDirection direction = ListSortDirection.Ascending;
            if (view.SortDescriptions.Count > 0)
            {
                SortDescription currentSort = view.SortDescriptions[0];
                if (currentSort.PropertyName == propertyName)
                {
                    if (currentSort.Direction == ListSortDirection.Ascending)
                        direction = ListSortDirection.Descending;
                    else
                        direction = ListSortDirection.Ascending;
                }
                view.SortDescriptions.Clear();
            }
            if (!string.IsNullOrEmpty(propertyName))
            {
                view.SortDescriptions.Add(new SortDescription(propertyName, direction));
            }
        }

        #endregion
    }
}

Of course, this class could probably be improved… for instance, we could add an arrow glyph on the sorted column (maybe by using an Adorner). Maybe I’ll do that someday… meanwhile, please feel free to use it 😉

Update : A new version that displays the sort glyph in the sorted column is now available in this blog post.

70 thoughts on “[WPF] Automatically sort a GridView when a column header is clicked”

  1. Bon boulot !
    J’avais trouvé un exemple il y a quelque temps sur CodeProject mais il me restait le problème du nom de la colonne vis-à-vis du nom de la propriété… J’aime bien aussi le binding avec la commande (MVVM powered :p)
    Merci à toi 🙂

  2. A hint to everyone. “AutoSort” is really “EnableFeature”, if you don’t set it, nothing happens. I had originally assumed that AutoSort meant “Sort the table when the data loads”, but this was not the case. Hopefully this saves people 10 minutes.

    1. Hi Matt,

      Indeed the name of that property may be somewhat misleading… perhaps “Enable” would have been a better name.

      Actually, you don’t have to set AutoSort to true : another option is to set the Command property, in order to sort the data manually (as opposed to automatically, hence the name “AutoSort”)

      1. Hello!

        I love your solution, works perfectly in my project!

        but i would really want to make it sort it self on load, as Matt is talking about.

        I have been trying for a while now, but i cannot find a solution when usin the MVVM pattern.

        Do you have an idea/hint/way to do it? 🙂

        Best Regards
        Jakob

        1. Hi Jakob,

          You just need to add a SortDescription to the default view for your collection :

          var view = CollectionViewSource.GetDefaultView(yourCollection);
          var sortDescription = new SortDescription("SomeProperty", ListSortDirection.Ascending);
          view.SortDescriptions.Add(sortDescription);

          Best Regards,
          Thomas

          1. Hi Thomas,

            Thanks for a great solution.

            I’ve just got a question.. This default sorting method works fine, sorting order is fine, but glyph is not visible at the beginning, user has to press on some column to make it visible..

            Any ideas to get it displayed also at the beginning?

            If it makes any difference, my binding happens in constructor.

          2. Hi Alexey,

            I’m aware of this issue, I was trying to solve it just 2 days ago… Unfortunately it turned out to be much harder than I expected, and I don’t have a solution yet. If I find a good solution I will post it here

            Regards,
            Thomas

  3. Here:

    public static string GetPropertyName(DependencyObject obj)
    {
    return (string)obj.GetValue(PropertyNameProperty);
    }

    you should check for obj != null
    null happens when you click the last extra column

  4. This sorting seem to be happening on the display string of values in column. Can we do something to really sort based on the value of date of birth?

  5. Hey Thomas,

    I’ve been looking into the code, but for some reason i can’t get it working.
    (Could be cause i’ve only been working with WPF for 2 weeks now.)

    The problem lies here:

    and here:

    I haven’t been able to get the util thing working for me. I figured by looking at your project it has to do with the namespace, so i already changed that to my projects namespace, but that didn’t help. Could you tell me what i’m missing here?

      1. Let’s see if this works:

        &lt;ListView ItemsSource=&quot;{Binding Persons}&quot;
                         IsSynchronizedWithCurrentItem=&quot;True&quot;
                         util:GridViewSort.AutoSort=&quot;True&quot;&gt;
                        &lt;ListView.View&gt;
                                      &lt;GridView&gt;
                                          &lt;GridView.Columns&gt;
                                                           &lt;GridViewColumn Header=&quot;Name&quot;
                                                                                         DisplayMemberBinding=&quot;{Binding Name}&quot;
                                                                                         util:GridViewSort.PropertyName=&quot;Name&quot;/&gt;
                                                           &lt;GridViewColumn Header=&quot;First name&quot;
                                                                                         DisplayMemberBinding=&quot;{Binding FirstName}&quot;
                                                                                         util:GridViewSort.PropertyName=&quot;FirstName&quot;/&gt;
                                                           &lt;GridViewColumn Header=&quot;Date of birth&quot;
                                                                                         DisplayMemberBinding=&quot;{Binding DateOfBirth}&quot;
                                                                                         util:GridViewSort.PropertyName=&quot;DateOfBirth&quot;/&gt;
                                          &lt;/GridView.Columns&gt;
                                        &lt;/GridView&gt;
                           &lt;/ListView.View&gt;
        &lt;/ListView&gt;
        
          1. Hi Thomas Levesque,

            Mapping util namespace
            – Am getting error like this ‘The NameSpace Prefix “util” is not defined’.(i.e, When i declare in ListView util:GridViewSort.AutoSort=”True”).

            So please tell me, how to define/include “util” in my namespace.

            Thanks in advance

        1. Hmm ok, nvm, looked some bit deeper into it and now got it working for a few of my columns. Still gotta fix the date notation though.

          Thanks for the example.

          1. Ye the problem was mapping to the class. Like i said, new in this so didn’t know that part, but trying some (for me) “weird” things helped.

  6. This is simply great and works perfect when binding to simple properties. Any thoughts on how to achieve similar functionality when binding and using a value converter? Some of my fields are complex types and I want to sort on the converted string values. Am I forced to use a command? I am hoping to avoid since it forces my viewmodel to know about how the view will render the data.

    1. Hi Ian,

      This solution is based on ICollectionView.SortDescriptions, and SortDescription only accepts a property name, not a “full” binding with a converter. If you need to sort based on more complex criteria, you can create an extra property in the VM of your data items, implement the converter logic in that property, and sort on that property. That’s what I do and it works fine.

      Regards,
      Thomas

  7. I actually liked your original idea here more: http://stackoverflow.com/questions/1221533/sort-wpf-listview-with-a-datatemplate-instead-of-displaymemberbinding

    So I enhanced the GetPropertyName so that the user can specify the GridViewSort.PropertyName. If they choose not to, it will fallback on automatically trying to get the DisplayMemberBinding Path. Makes for less repetitive looking code, but still lets you handle the special cases.

            public static string GetPropertyName(DependencyObject obj)
            {
                if( obj == null )
                    return null;
                try
                {
                    string propertyName = (string)obj.GetValue(PropertyNameProperty);
                    if( string.IsNullOrEmpty(propertyName) &amp;&amp; obj is GridViewColumn )
                    {
                        GridViewColumn column = (GridViewColumn)obj;
                        if( column.DisplayMemberBinding != null )
                        {
                            propertyName = ((Binding)column.DisplayMemberBinding).Path.Path;
                        }
                    }
                    return propertyName;
                }
                catch
                {
                    return null;
                }
            }
    
    1. Thomas, thank you! The code works fantastically for what I need it to do.

      And alainbryden, thank you, too, because your modification does EXACTLY what I was just about to go figure out how to do. Using DisplayMemberBinding.Path.Path is a special kind of magic. 🙂

    1. Hi Ray, there is no restriction whatsoever on the use of this code, you can use it in any project you want.

      1. Thanks! It works well.

        As a side note, thanks for giving permission explicitly that any use is appropriate for this code (you mentioned this in one of the comments above). Perhaps it would be worth it for you to mention something the effect of “All code in this blog, unless otherwise specified, may be used for any purpose” in your About page. Or, even better, specify a simple license that all code on this blog is under (such as the MIT license). It’s just that, at least in the US, code without a license (or without explicit permission) is under copyright. Such code is unusable from a legal standpoint in most commercial applications. So perhaps it would help myself (and others) who want to use the code on your site if you would do so – some of us have to follow strict rules concerning what code we use in our work.

        Licenses also give you some manner of protection from liability (it would be really unlikely that anyone would try to collect, but just a thought).

        Thanks!

        1. Hi Daniel,

          Thanks for your comment. You’re right, I should probably add an explicit license. My thinking was that if I published something, it was for people to use it, but I realize that copyright laws don’t work that way (at least not everywhere).

    1. Hi Jesson,

      You need to map the C# namespace where you declare the class to the “util” XML namespace on the root element, like this:

           xmlns:util="clr-namespace:TheNamespace;assembly=TheAssembly"

      (you can omit the assembly part if it”s in the same assembly)

  8. Hi,
    I am using this codeset and trying to sort on first column when listview is loaded. The items do get sorted however the glyph is not shown. You have to click on a column header in order for it to work. Can somebody help me in getting this working?

  9. The solution is as elegant as I’d hope for, to combine it with the enhancement from alainbryden, this is the best solution I’ve googled all over the internet. Bravo!

  10. Thanks Thomas,

    This is an excellent solution and solves my problem perfectly 5 years after you wrote it!

  11. Hello

    How can this be done for a tree List view (tree view)? How to use the command property in ViewModel ??
    I’m stuck any help will be helpful .
    Thank you !!

    1. Which tree list view are you talking about? There is no such control in WPF itself, and there are multiple third-party implementations. I have no idea if my solution could apply to any of them.

  12. Interesting bug – I was using this in a ListView where the total width was greater than the total width of individual columns – meaning part of my “Column Header Row” didn’t contain a column.

    This meant I had a clickable bit of GridViewColumnHeader where the Column property was null, which caused a NullReferenceException to be thrown. The below addition covers this case.

    private static void ColumnHeader_Click(object sender, RoutedEventArgs e)
    {
    var headerClicked = e.OriginalSource as GridViewColumnHeader;
    if (headerClicked == null || headerClicked.Column == null) return;

    ….
    }

  13. Hey Thomas Levesque

    I am use your code for sorting value which enter in textbox

    when i click on header for sorting it sort only Ascending and first textbox will empty can you help me where is problem occur …..

    Great Thank full to you

    1. Hi Sam,
      I don’t have enough detail to guess what’s going on… Post your code somewhere (Stackoverflow for instance)

      1. Hey Thomas Levesque

        Thanks for your replay .I send my code where i sorting text

        I am also place button on header for sorting the textbox content but the same problem arise only sort in Ascending when i click next time not effect on it

        Can you help me to solve this type of problem
        I am great Thank full to you…..

        Grid view Sort is which i use for Sorting
        using System.ComponentModel;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Input;
        using System.Windows.Media;
        using System.Windows.Documents;

        namespace Wpf.Util
        {

        public class GridViewSort
        {
        #region Attached properties

        public static ICommand GetCommand(DependencyObject obj)
        {
        return (ICommand)obj.GetValue(CommandProperty);
        }

        public static void SetCommand(DependencyObject obj, ICommand value)
        {
        obj.SetValue(CommandProperty, value);
        }

        // Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc…
        public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached(
        “Command”,
        typeof(ICommand),
        typeof(GridViewSort),
        new UIPropertyMetadata(
        null,
        (o, e) =>
        {
        ItemsControl listView = o as ItemsControl;
        if (listView != null)
        {
        if (!GetAutoSort(listView)) // Don’t change click handler if AutoSort enabled
        {
        if (e.OldValue != null && e.NewValue == null)
        {
        listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
        }
        if (e.OldValue == null && e.NewValue != null)
        {
        listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
        }
        }
        }
        }
        )
        );

        public static bool GetAutoSort(DependencyObject obj)
        {
        return (bool)obj.GetValue(AutoSortProperty);
        }

        public static void SetAutoSort(DependencyObject obj, bool value)
        {
        obj.SetValue(AutoSortProperty, value);
        }

        // Using a DependencyProperty as the backing store for AutoSort. This enables animation, styling, binding, etc…
        public static readonly DependencyProperty AutoSortProperty =
        DependencyProperty.RegisterAttached(
        “AutoSort”,
        typeof(bool),
        typeof(GridViewSort),
        new UIPropertyMetadata(
        false,
        (o, e) =>
        {
        ListView listView = o as ListView;
        if (listView != null)
        {
        if (GetCommand(listView) == null) // Don’t change click handler if a command is set
        {
        bool oldValue = (bool)e.OldValue;
        bool newValue = (bool)e.NewValue;
        if (oldValue && !newValue)
        {
        listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
        }
        if (!oldValue && newValue)
        {
        listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
        }
        }
        }
        }
        )
        );

        public static string GetPropertyName(DependencyObject obj)
        {
        return (string)obj.GetValue(PropertyNameProperty);
        }

        public static void SetPropertyName(DependencyObject obj, string value)
        {
        obj.SetValue(PropertyNameProperty, value);
        }

        // Using a DependencyProperty as the backing store for PropertyName. This enables animation, styling, binding, etc…
        public static readonly DependencyProperty PropertyNameProperty =
        DependencyProperty.RegisterAttached(
        “PropertyName”,
        typeof(string),
        typeof(GridViewSort),
        new UIPropertyMetadata(null)
        );

        #endregion

        #region Column header click event handler

        private static void ColumnHeader_Click(object sender, RoutedEventArgs e)
        {
        GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
        if (headerClicked != null)
        {
        string propertyName = GetPropertyName(headerClicked.Column);
        if (!string.IsNullOrEmpty(propertyName))
        {
        ListView listView = GetAncestor(headerClicked);
        if (listView != null)
        {
        ICommand command = GetCommand(listView);
        if (command != null)
        {
        if (command.CanExecute(propertyName))
        {
        command.Execute(propertyName);
        }
        }
        else if (GetAutoSort(listView))
        {
        ApplySort(listView.Items, propertyName);
        }
        }
        }
        }
        }

        #endregion

        #region Helper methods

        public static T GetAncestor(DependencyObject reference) where T : DependencyObject
        {
        DependencyObject parent = VisualTreeHelper.GetParent(reference);
        while (!(parent is T))
        {
        parent = VisualTreeHelper.GetParent(parent);
        }
        if (parent != null)
        return (T)parent;
        else
        return null;
        }

        public static void ApplySort(ICollectionView view, string propertyName)
        {
        ListSortDirection direction = ListSortDirection.Ascending;
        if (view.SortDescriptions.Count > 0)
        {
        SortDescription currentSort = view.SortDescriptions[0];
        if (currentSort.PropertyName == propertyName)
        {
        if (currentSort.Direction == ListSortDirection.Ascending)
        direction = ListSortDirection.Descending;
        else
        direction = ListSortDirection.Ascending;
        }
        view.SortDescriptions.Clear();
        }
        if (!string.IsNullOrEmpty(propertyName))
        {
        view.SortDescriptions.Add(new SortDescription(propertyName, direction));
        }
        }

        #endregion
        }
        }

  14. Hello Thomas,

    thank you for your solution. However, I have a question. Is there a way to set this part “util:GridViewSort.PropertyName=”Name”” programmatically from the code behind? I am adding the columns dynamically at runtime and thus cannot set it in XAML.

    1. Hi Nina,

      Sure, anything that can be done in XAML can also be done in code-behind. You can use the GridViewSort.SetPropertyName() to set the attached property:

      GridViewSort.SetPropertyName(theColumnObject, "ThePropertyName");
      
          1. OK… can you put a breakpoint in the ColumnHeader_Click method and check if it’s hit?

          2. And what happens then? Have you tried debugging step by step to see why it failed to sort?

  15. Helped me a lot mate, but I din’t understand completely your code, perhaps can you explain it a little bit?
    thanks anyway xD

  16. Hi Thomas,
    I am new into programming, so could you please provide the complete code for this in wpf xaml platform
    1, there are 3 column in a table (student) columns :Name, Address, Date
    * In form load I want the grid should be sorted last in first
    * In grid the grid column header should be Name, Address, Date
    * In each Grid column Head click it should be sorted accourdingly
    Will you please provide me the exact code and xaml code also

Leave a Reply

Your email address will not be published. Required fields are marked *