Search code examples
c#wpfwpfdatagrid

Forcing a secondary sort on a DataGrid


Is there an approach for programatically applying WPF DataGrid sorting based on multiple properties?

I have a multi-column DataGrid which contains a single column which I always want to be in alphabetical order within groupings of values when other columns are sorted.

All custom sort implementations I've seen use a IComparer, but this only provides the values in the single column it's associated with.

For example, if we have the following two columns of unsorted data:

  • A 1
  • D 2
  • C 2
  • B 1

If we apply sorting to the second column, the first column becomes arbitrarily sorted within each grouping:

  • A 1
  • B 1
  • D 2
  • C 2

What I am looking for is for within any groups of identical values in the second column, the first column is alphabetised:

  • A 1
  • B 1
  • C 2
  • D 2

Is there an alternative to IComparer which exposes the full model?


Solution

  • Have you looked into SortMemberPath?

    <DataGrid.Columns>
        <DataGridTextColumn Header="Some Caption"  Width="200"
            CanUserSort="True"
            Binding="{Binding SomeDataField}" 
            SortMemberPath="OtherNonShowingField"/>
    </DataGrid.Columns>
    

    So if you are listing data from a List or from a DataTable via its DataColumn reference, you could always add another field or column that has the combined A + B parts in that field. So even if you don't show the column, it can be used as the basis of a sort.

    Since I do not know of another way to utilize the SortMemberPath to do both an ascending AND descending in the same, I wrote a function that you can try to use in your code. I called it NegativeString, thus a reversal of alphabetical order and numbers. Anything else is "as-is".

    private static string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static string LowerCase = "abcdefghijklmnoprqstuvwxyz";
    private static string Numbers = "0123456789";
    public string NegativeString( string incoming )
    {
        // take incoming string, put into a character array
        var inB = incoming.ToCharArray();
        var outStr = "";
        for( var i= 0; i < inB.Length; i++ )
        {
            // So, if I Look for "A" in the upper-case string, it is found
            // in position 0, so I want the character 0 from the end
            // which is the letter Z and vice-versa.
            var atPos = UpperCase.IndexOf(inB[i]);
            if (atPos > -1)
                outStr += UpperCase.Substring(UpperCase.Length - atPos - 1, 1);
            else
            {
                // if not upper case, then look for lower
                atPos = LowerCase.IndexOf(inB[i]);
                if (atPos > -1)
                    outStr += LowerCase.Substring(LowerCase.Length - atPos - 1, 1);
                else
                {
                    // if not lower, try numbers
                    atPos = Numbers.IndexOf(inB[i]);
                    if (atPos > -1)
                        outStr += Numbers.Substring(Numbers.Length - atPos - 1, 1);
                    else
                        // anything else comes along as-is
                        outStr += inB[i];
                }
            }
        }
    
        return outStr;
    }
    

    So, a new property on your class could be something like

    public string YourMultiSortProperty
    {  get { return NormalField + NegativeString( OtherField ); }}