5
votes

I have a DataGrid (on the View) bound to an ObservableCollection (on the ViewModel).

To this ObservableCollection, I am programmatically adding a "magic row", since the selected item will be used to sort another collection elsewhere, and I add this dummy row to "clear" the filtering.

Currently, when the View loads, my magic string always appears on top (since I insert it at index 0), but as soon as I click on the header to change sorting order, the rows rearrange alphabetically and my magic row is lost in the rubble.

What I want is that the magic row to always appear on top, even when I click on the header to change the sorting order.

ViewModel:

// (...)

public ObservableCollection<FilteringItem> ItemTypes
{
    get
    {
        var result = new ObservableCollection<FilteringItem>(_setups.Select(n => n.ItemType)
                                                             .Distinct()
                                                             .Select(s => new FilteringItem(s, s))
                                                             .OrderBy(v => v.Name));


        // Magic Happens Here: adding a "magic" row to remove filtering
        // (that is, "allow all") when used by some filtering method elsewhere;
        string allString = String.Format("All ({0})", result.Count);
        var filteringAll = new FilteringItem(allString, "");

        result.Insert(0, filteringAll);

        return result;
    }

}
// (...)

public class FilteringItem
{
    public string Name { get; private set; }
    public string Value { get; private set; }

    public FilteringItem(string name, string val)
    {
        Name = name;
        Value = val;
    }
}    

View:

<DataGrid ItemsSource="{Binding ItemTypes}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Tipo" Width="*" Binding="{Binding Name}"/>
    </DataGrid.Columns>
</DataGrid>
1

1 Answers

1
votes

I've got it to work thanks to this blog post.

The "secret" was to intercept the Sorting event (setting e.Handled to true), then casting the DataGrid.ItemsSource to ListCollectionView, and then assigning a custom IComparer to ListCollectionView.CustomSort property.

Then, while sorting, you somehow identify your fixed row (by null value, in my case), and make it always go to the top (which in turn depends on ListSortDirection).

public partial class ViewContainingDataGrid : UserControl
{
    public ViewContainingDataGrid()
    {
        this.InitializeComponent();
    }

    private void datagrid_Sorting(object sender, DataGridSortingEventArgs e)
    {
        e.Handled = true;
        SmartSort(sender, e.Column);
    }

    private void SmartSort(object sender, DataGridColumn column)
    {
        ListSortDirection direction = (column.SortDirection != ListSortDirection.Ascending) ?
                             ListSortDirection.Ascending : ListSortDirection.Descending;

        column.SortDirection = direction;
        var dg = sender as DataGrid;
        var lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(dg.ItemsSource);
        lcv.CustomSort = new SmartSorter(direction);
    }

}

public class SmartSorter : IComparer
{
    public ListSortDirection Direction { get; private set; }

    public SmartSorter(ListSortDirection direction)
    {
        Direction = direction;
    }


    public int Compare(object x, object y)
    {
        string valorx = x as string;
        string valory = y as string;

        int comparison = valorx.CompareTo(valory);
        if (Direction == ListSortDirection.Descending)
            comparison *= -1;

        // Override the sorting altogether when you find the special row value
        if (String.IsNullOrWhiteSpace(valorx))
            comparison = -1;
        else
        if (String.IsNullOrWhiteSpace(valory))
            comparison = 1;

        return comparison;
    }
}