Search code examples
wpfsortingdatagrid

How can I apply a custom sort for a WPF DataGrid from a style?


I have a style that I want to apply to a DataGrid. The DataGrid needs to run my custom sort code instead of the default DataGrid sort. The solution that I tried was as follows:

<Style TargetType="{x:Type DataGrid}" x:Key="FilteredDataGrid">
    <EventSetter Event="Sorting" Handler="DataGrid_Sorting"/>
</Style>

And in the code-behind:

private void DataGrid_Sorting(object sender, DataGridSortingEventArgs e) {

    e.Handled = true;

    //Here is where I put the code for my custom sort.

}//DataGrid_Sorting

However, this code doesn't build. It seems to me that because the DataGrid.Sorting event is not a RoutedEvent, it can't be used in an EventSetter.

How can I customize the sorting for any DataGrid that has my style applied?


Solution

  • There is a workaround to provide a routed event when you only have a "normal" event:

    Create an attached property that controls the event forwarding and an attached event that shall replace the original event. In order to do this, create a class DataGridEx (whatever class name you prefer) as a container for the attached property (DataGridEx.EnableSortingEvent) and event (DataGridEx.Sorting).

    Also, create a custom RoutedEventArgs class that forwards the original sorting event args

    public class DataGridExSortingEventArgs : RoutedEventArgs
    {
        public DataGridExSortingEventArgs(RoutedEvent routedEvent, DataGridSortingEventArgs sourceEventArgs) : base(routedEvent)
        {
            SourceEventArgs = sourceEventArgs;
        }
    
        public DataGridSortingEventArgs SourceEventArgs { get; set; }
    }
    
    public static class DataGridEx
    {
        public static bool GetEnableSortingEvent(DependencyObject obj)
        {
            return (bool)obj.GetValue(EnableSortingEventProperty);
        }
    
        public static void SetEnableSortingEvent(DependencyObject obj, bool value)
        {
            obj.SetValue(EnableSortingEventProperty, value);
        }
    
        // Setting this property to true enables the event forwarding from the DataGrid.Sorting event to the DataGridEx.Sorting RoutedEvent
        public static readonly DependencyProperty EnableSortingEventProperty = DependencyProperty.RegisterAttached(
            "EnableSortingEvent",
            typeof(bool),
            typeof(DataGridEx),
            new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnEnableSortingChanged)));
    
        private static void OnEnableSortingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is DataGrid dg)
            {
                if ((bool)e.NewValue)
                    dg.Sorting += Dg_Sorting;
                else
                    dg.Sorting -= Dg_Sorting;
            }
        }
    
        // When DataGrid.Sorting is called and DataGridEx.EnableSortingEvent is true, raise the DataGridEx.Sorting event
        private static void Dg_Sorting(object sender, DataGridSortingEventArgs e)
        {
            if (sender is DataGrid dg && GetEnableSortingEvent(dg))
            {
                dg.RaiseEvent(new DataGridExSortingEventArgs(SortingEvent, e));
            }
        }
    
        // When DataGridEx.EnableSortingEvent is true, the DataGrid.Sorting event will be forwarded to this routed event
        public static readonly RoutedEvent SortingEvent = EventManager.RegisterRoutedEvent(
            "Sorting",
            // only effective on the DataGrid itself
            RoutingStrategy.Direct,
            typeof(RoutedEventHandler),
            typeof(DataGridEx));
    
        public static void AddSortingHandler(DependencyObject d, RoutedEventHandler handler)
        {
            if (d is DataGrid dg)
                dg.AddHandler(SortingEvent, handler);
        }
    
        public static void RemoveSortingHandler(DependencyObject d, RoutedEventHandler handler)
        {
            if (d is DataGrid dg)
                dg.RemoveHandler(SortingEvent, handler);
        }
    }
    

    Now use those in your style (with local being the xmlns for the namespace where DataGridEx is defined):

    <Style TargetType="DataGrid">
        <Setter Property="local:DataGridEx.EnableSortingEvent" Value="True"/>
        <EventSetter Event="local:DataGridEx.Sorting" Handler="DataGrid_Sorting"/>
    </Style>
    

    The handler

    private void DataGrid_Sorting(object sender, RoutedEventArgs e)
    {
        if (e is DataGridExSortingEventArgs args)
        {
            // will prevent datagrid default sorting
            args.SourceEventArgs.Handled = true;
        }
    
        // More stuff
    }
    

    I hope this is what you needed. Had to refresh my memory about attached stuff :)