wpfmvvmdata-bindingdatagrid

How to sort DataGridTextColumn by displayed, converted value, not bound source property value?


How to sort WPF DataGridTextColumn by displayed, converted value, not bound source property value? Now it's sorted by integer value in row viewmodel, not displayed text returned by Converter. I use MVVM.

Here is an example by request. This however is general question. I could put MmsClass.Name in class representing the row. But I need proper sorting everywhere, not only here.

Class for a row:

public class MaintenanceDataItem
{
    public MaintenanceDataItem(int classId, Type objectType, object value, IEnumerable<MmsData> rows)
    {
        ClassId = classId;
        TypeOfObject = objectType;
        Value = value;
        ObjectIds = new List<int>();
        MmsDataRows = rows;
    }

    public int ClassId { get; private set; }
    // rest of the properrties omitted
}

converter:

public class MmsClassToNameConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        MmsClass mmsClass;
        if (MmsClasses.Instance.TryGetValue((int) value, out mmsClass))
        {
            return mmsClass.Name;
        }
        return value.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        return value.Equals(true) ? parameter : Binding.DoNothing;
    }
}

column in xaml:

<DataGridTextColumn Header="{StaticResource ResourceKey=MmsStrCondClass}" 
                    Binding="{Binding ClassId, 
                              Converter={StaticResource mmsclasstonameconverter}}" 
                    Width="*">
    <DataGridTextColumn.ElementStyle>
        <Style TargetType="{x:Type TextBlock}"
               BasedOn="{StaticResource {x:Type TextBlock}}">
            <Setter Property="TextWrapping" Value="NoWrap" />
            <Setter Property="TextTrimming" Value="CharacterEllipsis"/>
        </Style>
    </DataGridTextColumn.ElementStyle>
</DataGridTextColumn>

I really thought that default sorting would be displayed value. Using converters makes not so much sense for datagridcolumn if this is not easily solved.


Solution

  • Unfortunately this is not a trivial task. As @Maverik correctly pointed out, DataGrid sorts on the underlying data, not what a converter spits out. To do this, you'll need to Sort yourself. Start by creating a class with a property to use your custom sorters, and another to define the sorter to use on a given column:

        public static ICustomSorter GetCustomSorter(DependencyObject obj)
        {
            return (ICustomSorter)obj.GetValue(CustomSorterProperty);
        }
    
        public static void SetCustomSorter(DependencyObject obj, ICustomSorter value)
        {
            obj.SetValue(CustomSorterProperty, value);
        }
    
        // Using a DependencyProperty as the backing store for CustomSorter.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CustomSorterProperty =
            DependencyProperty.RegisterAttached("CustomSorter", typeof(ICustomSorter), typeof(CustomSortBehavior), new PropertyMetadata(null));
    
        public static bool GetAllowCustomSort(DependencyObject obj)
        {
            return (bool)obj.GetValue(AllowCustomSortProperty);
        }
    
        public static void SetAllowCustomSort(DependencyObject obj, bool value)
        {
            obj.SetValue(AllowCustomSortProperty, value);
        }
    
        // Using a DependencyProperty as the backing store for AllowCustomSort.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AllowCustomSortProperty =
            DependencyProperty.RegisterAttached("AllowCustomSort", typeof(bool), typeof(CustomSortBehavior), new PropertyMetadata(false, AllowCustomSortChanged));
    

    ICustomSorter is a very simple interface:

    public interface ICustomSorter : IComparer
    {
        ListSortDirection SortDirection { get; set; }
    
        string SortMemberPath { get; set; }
    }
    

    Now you need to implement the custom sort from "AllowCustomSort":

        private static void AllowCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DataGrid control = d as DataGrid;
            {
                var oldAllow = (bool)e.OldValue;
                var newAllow = (bool)e.NewValue;
    
                if (!oldAllow && newAllow)
                {
                    control.Sorting += HandleCustomSorting;
                }
                else
                {
                    control.Sorting -= HandleCustomSorting;
                }
            }
        }
    
        private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)
        {
            //Check if we should even be using custom sorting
            DataGrid dataGrid = sender as DataGrid;
            if (dataGrid != null && GetAllowCustomSort(dataGrid))
            {
                //Make sure we have a source we can sort
                ListCollectionView itemsSource = dataGrid.ItemsSource as ListCollectionView;
                if (itemsSource != null)
                {
                    ICustomSorter columnSorter = GetCustomSorter(e.Column);
    
                    //Only do our own sort if a sorter was defined
                    if (columnSorter != null)
                    {
                        ListSortDirection nextSortDirection = e.Column.SortDirection == ListSortDirection.Ascending ?
                                                              ListSortDirection.Descending :
                                                              ListSortDirection.Ascending;
                        e.Column.SortDirection = columnSorter.SortDirection = nextSortDirection;
                        columnSorter.SortMemberPath = e.Column.SortMemberPath;
                        itemsSource.CustomSort = columnSorter;
    
                        //We've handled the sort, don't let the DataGrid mess with us
                        e.Handled = true;
                    }
                }
            }
        }
    

    This is just wiring up the Sorting event and then handling it by calling the provided ICustomSorter to sort the collection.

    In your XAML, you create an instance of an implemented ICustomSorter and use the attached properties like so:

                <DataGridTextColumn Header="Column1" Binding="{Binding Column1, Converter={StaticResource Column1Converter}}" IsReadOnly="True"
                                    util:CustomSortBehavior.CustomSorter="{StaticResource Column1Comparer}"/>
    

    Its painful, and you have to custom sort all your converted values, but it does allow you to do this in a DataGrid.