.netwpfxamlmvvm

Sorting on datagrid column with binded data and converter


I'm trying to sort data in datagrid, but when I click on the header of the column which has binding with the converter, nothing happens. I use MVVM pattern. Example is attached below. In the example, the grid shows column (Type) which displays type of person and therefore I use converter (class TypeValueConverter). When I use this converter, the grid doesn't sort column Type.

<Window x:Class="GridSort.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:GridSort"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False">
            <DataGrid.Resources>
                <my:TypeValueConverter x:Key="typeConverter" />
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding FirstName}" Header="FirstName" />
                <DataGridTextColumn Binding="{Binding Surname}" Header="Surname" />
                <DataGridTextColumn Binding="{Binding Converter={StaticResource ResourceKey=typeConverter}}" Header="Type" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
public class ViewModel
{
    private ICollection<Person> people;
    public ICollection<Person> People
    {
        get
        {
            if (this.people == null)
            {
                this.people = new List<Person>();
                this.people.Add(new Student() { FirstName = "Charles", Surname = "Simons" });
                this.people.Add(new Student() { FirstName = "Jake", Surname = "Baron" });
                this.people.Add(new Teacher() { FirstName = "John", Surname = "Jackson" });
                this.people.Add(new Student() { FirstName = "Patricia", Surname = "Phillips" });
                this.people.Add(new Student() { FirstName = "Martin", Surname = "Weber" });
            }

            return this.people;
        }
    }
}

public class TypeValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value == null)
            {
                return DependencyProperty.UnsetValue;
            }

            return value.GetType().Name;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

public abstract class Person
    {
        public string FirstName
        {
            get;
            set;
        }

        public string Surname
        {
            get;
            set;
        }
    }

    public class Student : Person
    {
    }

    public class Teacher : Person
    {
    }

Solution

  • I resolved this issue myself :)

    I created new behavior for grid with attached property UseBindingToSort. When I set this property to true then event Sorting on grid is subscribed. After grid fires event sorting I use custom comparer with IValueConverter which is defined in binding. Solution is below:

    Change on view

    <DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False"  my:GridSortingBehavior.UseBindingToSort="True">
    

    New behavior with attached property:

    public static class GridSortingBehavior
        {
            public static readonly DependencyProperty UseBindingToSortProperty = DependencyProperty.RegisterAttached("UseBindingToSort", typeof(bool), typeof(GridSortingBehavior), new PropertyMetadata(new PropertyChangedCallback(GridSortPropertyChanged)));
    
            public static void SetUseBindingToSort(DependencyObject element, bool value)
            {
                element.SetValue(UseBindingToSortProperty, value);
            }
    
            private static void GridSortPropertyChanged(DependencyObject elem, DependencyPropertyChangedEventArgs e)
            {
                DataGrid grid = elem as DataGrid;
                if (grid != null){
                    if ((bool)e.NewValue)
                    {
                        grid.Sorting += new DataGridSortingEventHandler(grid_Sorting);
                    }
                    else
                    {
                        grid.Sorting -= new DataGridSortingEventHandler(grid_Sorting);
                    }
                }
            }
    
            static void grid_Sorting(object sender, DataGridSortingEventArgs e)
            {
                DataGridTextColumn clm = e.Column as DataGridTextColumn;
                if (clm != null)
                {
                    DataGrid grid = ((DataGrid)sender);
                    IValueConverter converter = null;
                    if (clm.Binding != null)
                    {
                        Binding binding = clm.Binding as Binding;
                        if (binding.Converter != null)
                        {
                            converter = binding.Converter;
                        }
                    }
    
                    if (converter != null)
                    {
                        e.Handled = true;
                        ListSortDirection direction = (clm.SortDirection != ListSortDirection.Ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending;
                        clm.SortDirection = direction;
                        ListCollectionView lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(grid.ItemsSource);
                        lcv.CustomSort = new ComparerWithComparer(converter, direction);
                    }
                }
            }
    

    And finally my custom comparer:

    class ComparerWithComparer : IComparer
        {
            private System.Windows.Data.IValueConverter converter;
            private System.ComponentModel.ListSortDirection direction;
    
    
            public ComparerWithComparer(System.Windows.Data.IValueConverter converter, System.ComponentModel.ListSortDirection direction)
            {
                this.converter = converter;
                this.direction = direction;
            }
    
            public int Compare(object x, object y)
            {
                object transx = this.converter.Convert(x, typeof(string), null, System.Threading.Thread.CurrentThread.CurrentCulture);
                object transy = this.converter.Convert(y, typeof(string), null, System.Threading.Thread.CurrentThread.CurrentCulture);
                if (direction== System.ComponentModel.ListSortDirection.Ascending){
                    return Comparer.Default.Compare(transx, transy);
                }
                else
                {
                    return Comparer.Default.Compare(transx, transy) * (-1);
                }
            }
        }
    

    And that's all.