WPF DataGrid: Tri-state Sorting sample


Here is a short sample on how to create a tri-state sorting experience with the WPF DataGrid.  In the current design, when clicking on a column header it will toggle sorting of the column starting from ascending to descending.  Unfortunately you cannot get back to the original state without adding your own custom logic for it.  To get back to the original state you have to set the column.SortDirection back to null and clear out the SortDescriptions that were added when you click on the column header.  In this sample I decided to add one more toggle state so when you click on the column header a third time it goes back to the original state.  I’m adding this functionality to the DataGrid.Sorting event and stop the default sort if the toggle state is going back to the original state.  Here is the code:       



private void DataGrid_Standard_Sorting(object sender, DataGridSortingEventArgs e)


{


  DataGrid dataGrid = sender as DataGrid;


  string sortPropertyName = Helpers.GetSortMemberPath(e.Column);


  if (!string.IsNullOrEmpty(sortPropertyName))


  {


    // sorting is cleared when the previous state is Descending


    if (e.Column.SortDirection.HasValue && e.Column.SortDirection.Value == ListSortDirection.Descending)


    {
      int
index = Helpers.FindSortDescription(dataGrid.Items.SortDescriptions, sortPropertyName);


      if (index != -1)


      {
          e.Column.SortDirection = null;


 


          // remove the sort description


          dataGrid.Items.SortDescriptions.RemoveAt(index);


          dataGrid.Items.Refresh();


 


          if ((Keyboard.Modifiers & ModifierKeys.Shift) != ModifierKeys.Shift)


          {
             // clear any other sort descriptions for the multisorting case


             dataGrid.Items.SortDescriptions.Clear();


             dataGrid.Items.Refresh();
          }


 


          // stop the default sort


          e.Handled = true;


      }
    }
  }


}


 


I also decided to add some extra functionality for displaying the sort order of the column when doing multi-column sorting.  By default when you do multi-column sorting you really have no idea which column is sorted in which order as it only shows that direction triangle.  I just add text to the header but you can always get creative and add all that WPF eye candy.  Here is a screenshot:


sortingOrder


The full sample is here.


Other DataGrid Samples:


ScrollViewer with ToolTip


Custom sorting, column selection, single-click editing


Styling rows and columns based on header conditions


Clipboard Copy

DataGrid_V1_TriStateSortingSample.zip

Comments (16)

  1. Overview The DataGrid uses a set of DataGridColumns to describe how to display its data just like a GridView

  2. If you haven’t already, you can download the binaries and source for the DataGrid here . DataGridComboBoxColumn

  3. _gdc says:

    Hello,

    I need to use the DataGrid with a class that implements IEnumerable<>,

    I don’t want to subclass ObservableCollection<>,

    but I have problems doing this.

    Using your example, I change your class:

    public class People : ObservableCollection<Person>

    with this new (where I use _peopleList to keep the data):

    public class People : IEnumerable<Person>

    {

     private List<Person> _peopleList;

     private Random random = new Random();

     public People()

     {

       CreateGenericPersonData(1);

     }

     public People(int multiplier)

     {

       CreateGenericPersonData(multiplier);

     }

     private void CreateGenericPersonData(int multiplier)

     {

       _peopleList = new List<Person>();

       if (multiplier > 0)

       {

         for (int i = 0; i < multiplier; i++)

         {

           _peopleList.Add(new Person("George", "Washington", GenerateRandomDate()));

           _peopleList.Add(new Person("John", "Adams", GenerateRandomDate()));

           _peopleList.Add(new Person("Thomas", "Jefferson", GenerateRandomDate()));

           //…

           _peopleList.Add(new Person("George", "W.", "Bush", GenerateRandomDate()));

         }

       }

     }

     private DateTime GenerateRandomDate()

     {

       int randInt = random.Next();

       return new DateTime(Convert.ToInt64(randInt));

     }

     #region IEnumerable<Person> Members

     public IEnumerator<Person> GetEnumerator()

     {

       return _peopleList.GetEnumerator();

     }

     #endregion

     #region IEnumerable Members

     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

     {

       return _peopleList.GetEnumerator();

     }

     #endregion

    }

    If I try this, I receive the exception:

    Cannot create instance of ‘DataGridBasicSample_Demo’ defined in assembly  …

    Where am I wrong?

    Thank you

  4. vinsibal says:

    _gdc,

    In my example I am getting the view from the ItemsSource and expect a ListCollectionView.  Since you have changed it to IEnumerable<T> it is no longer a ListCollectionView.  In the constructor of my sample, try removing the lines related to getting the CollectionViewSource.  It should work after that.

    -Vince

  5. Karl says:

    Started playing with the Datagrid in a WPF app runing on a desktop.

    I have notice that when I drag the scroll button on some machines, there is a pretty chunky lag. The content redraw updates in a unpleasant, laggy way.

    There were only 500 items in the grid…

  6. vinsibal says:

    Karl,

    Would you mind posting the specs for the machine that you find that it is lagging.

    Thanks,

    Vince

  7. Karl says:

    Opps never mind. Scrolling works fine.

    The issue is that if I set my ItemSource to an custom

    ObservableCollection<T>

    T must define a public string property named "Id" or the binding will throw a bad path and silently swallow the exception on all the rows.

    Which absolutely kills the performance of scroll….

    You get this in the output window of the debugger.

    System.Windows.Data Error: 39 :

    BindingExpression path error: ‘Id’ property not found on ‘object’ ”RowProxy’ (HashCode=11295760)’.

    BindingExpression:Path=Id; DataItem=’RowProxy’ (HashCode=11295760);

    target element is ‘DataGridRow’ (Name=”);

    target property is ‘Header’ (type ‘Object’)

  8. RobertT says:

    Hi Vincent!

    Will you be so kind and help me with a following scenario? =)

    Imagine I have a DataGrid bound with ObservableCollection. I want to prevent user from editing existing rows and enable only to add new rows and edit’em right in a place or delete new or existing rows. How can I achieve such a scenario?

    Best regards,

    RobertT

  9. vinsibal says:

    RobertT,

    One possible solution is to listen to the DataGrid.BeginningEdit event and cancel the BeginEdit if the row they are trying to edit is an existing row.  Deleting is a separate operation so it will work as normal with this solution.

  10. As you might have heard, .NET Framework 3.5 SP1 and Visual Studio 2008 SP1 are out today! There are a

  11. In the v1 release (and CTP) of the WPF DataGrid there will be support for Clipboard.Copy but no support

  12. Priya says:

    Hi, can v populate DataGrid dynamically. If yes, can u post a sample on the same using DataGridTextColumn() and DataGridRow() (similar to how we add in DataTable) Or is there other way of doing it cos am stuck with it.

    Thanks in Advance.

  13. vinsibal says:

    Priya,

    DataGrid can be populated dynamically, but not isn the same way as the DataTable.  The DataGrid is bound to an ItemsSource so as items are added and removed the DataGrid will update as necessary.  Could you expand on your scenario a bit and hopefully I can provide further assistance.

  14. Bharti says:

    Hello,

     I am using WPF toolokit’s data grid, and inthat grid I have a column for checkbox which I add as below:

    <toolkit:DataGridTemplateColumn >

                           <toolkit:DataGridTemplateColumn.CellTemplate>

                               <DataTemplate>

                                   <CheckBox Click="OnUsedClicked"  

                                           IsChecked="{Binding Path=Used}"

                                            />

                               </DataTemplate>

                           </toolkit:DataGridTemplateColumn.CellTemplate>

                       </toolkit:DataGridTemplateColumn>

    I would like to sort on all columns of my grid, so to the grid defination I added

    CanUserSortColumns="True"  to my grid.

    On doing this the rest of the columns get sorted but not the checkbox column.By checkbox column sorting I mean that all checked boxes(rows) to be together and all unchecked boxes(rows) to be together. I do understand there might be multiple checked so what basis needs to be used, I have a time column , I can use that along with it.

    i.e sorting on Checkbox + Time

    this would lead to all say unchecked together , based on time as the second parameter.

    I am not sure if this is possible, if yes how?

    Regards

    Barti

    I would like that

  15. Thomas says:

    I’m curious how to shrink the column back to its original size after the sort description is removed.  For example, click the Cake column 3 times.  When the header goes back to "Cake", the column doesn’t shrink.

    Thanks for a super helpful blogpost.