diff --git a/Gu.Wpf.DataGrid2D.Demo/Gu.Wpf.DataGrid2D.Demo.csproj b/Gu.Wpf.DataGrid2D.Demo/Gu.Wpf.DataGrid2D.Demo.csproj
index b38b9d7..d929dc1 100644
--- a/Gu.Wpf.DataGrid2D.Demo/Gu.Wpf.DataGrid2D.Demo.csproj
+++ b/Gu.Wpf.DataGrid2D.Demo/Gu.Wpf.DataGrid2D.Demo.csproj
@@ -111,6 +111,13 @@
Array2DView.xaml
+
+ ThreeStateSortView.xaml
+
+
+ ThreeStateSortView.xaml
+
+
TransposedView.xaml
@@ -166,6 +173,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
diff --git a/Gu.Wpf.DataGrid2D.Demo/MainWindow.xaml b/Gu.Wpf.DataGrid2D.Demo/MainWindow.xaml
index 930cb6b..4012b6d 100644
--- a/Gu.Wpf.DataGrid2D.Demo/MainWindow.xaml
+++ b/Gu.Wpf.DataGrid2D.Demo/MainWindow.xaml
@@ -41,5 +41,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortItem.cs b/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortItem.cs
new file mode 100644
index 0000000..15f6b99
--- /dev/null
+++ b/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortItem.cs
@@ -0,0 +1,58 @@
+namespace Gu.Wpf.DataGrid2D.Demo
+{
+ using System.ComponentModel;
+ using System.Runtime.CompilerServices;
+ using JetBrains.Annotations;
+
+ public class ThreeStateSortItem : INotifyPropertyChanged
+ {
+ private string stringValue;
+ private int intValue;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public string StringValue
+ {
+ get
+ {
+ return this.stringValue;
+ }
+
+ set
+ {
+ if (value == this.stringValue)
+ {
+ return;
+ }
+
+ this.stringValue = value;
+ this.OnPropertyChanged();
+ }
+ }
+
+ public int IntValue
+ {
+ get
+ {
+ return this.intValue;
+ }
+
+ set
+ {
+ if (value == this.intValue)
+ {
+ return;
+ }
+
+ this.intValue = value;
+ this.OnPropertyChanged();
+ }
+ }
+
+ [NotifyPropertyChangedInvocator]
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortView.xaml b/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortView.xaml
new file mode 100644
index 0000000..d04092a
--- /dev/null
+++ b/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortView.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortView.xaml.cs b/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortView.xaml.cs
new file mode 100644
index 0000000..ca77fe1
--- /dev/null
+++ b/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortView.xaml.cs
@@ -0,0 +1,15 @@
+namespace Gu.Wpf.DataGrid2D.Demo
+{
+ using System.Windows.Controls;
+
+ ///
+ /// Interaction logic for ThreeStateSortView.xaml
+ ///
+ public partial class ThreeStateSortView : UserControl
+ {
+ public ThreeStateSortView()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
diff --git a/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortViewModel.cs b/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortViewModel.cs
new file mode 100644
index 0000000..c5a7df6
--- /dev/null
+++ b/Gu.Wpf.DataGrid2D.Demo/ThreeStateSortViewModel.cs
@@ -0,0 +1,20 @@
+namespace Gu.Wpf.DataGrid2D.Demo
+{
+ using System.Collections.ObjectModel;
+
+ public class ThreeStateSortViewModel
+ {
+ public ThreeStateSortViewModel()
+ {
+ this.Items = new ObservableCollection
+ {
+ new ThreeStateSortItem {StringValue = "a", IntValue = 1},
+ new ThreeStateSortItem {StringValue = "c", IntValue = 4},
+ new ThreeStateSortItem {StringValue = "d", IntValue = 2},
+ new ThreeStateSortItem {StringValue = "b", IntValue = 3},
+ };
+ }
+
+ public ObservableCollection Items { get; }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Wpf.DataGrid2D/Gu.Wpf.DataGrid2D.csproj b/Gu.Wpf.DataGrid2D/Gu.Wpf.DataGrid2D.csproj
index fbce9d3..80fc201 100644
--- a/Gu.Wpf.DataGrid2D/Gu.Wpf.DataGrid2D.csproj
+++ b/Gu.Wpf.DataGrid2D/Gu.Wpf.DataGrid2D.csproj
@@ -66,6 +66,7 @@
+
diff --git a/Gu.Wpf.DataGrid2D/Sort.cs b/Gu.Wpf.DataGrid2D/Sort.cs
new file mode 100644
index 0000000..017cab7
--- /dev/null
+++ b/Gu.Wpf.DataGrid2D/Sort.cs
@@ -0,0 +1,94 @@
+namespace Gu.Wpf.DataGrid2D
+{
+ using System;
+ using System.ComponentModel;
+ using System.Windows;
+ using System.Windows.Controls;
+ using System.Windows.Input;
+
+ public class Sort
+ {
+ public static readonly DependencyProperty ThreeStateProperty = DependencyProperty.RegisterAttached(
+ "ThreeState",
+ typeof(bool),
+ typeof(Sort),
+ new PropertyMetadata(default(bool), OnThreeStateChanged));
+
+ private static readonly DependencyProperty SubscriptionProperty = DependencyProperty.RegisterAttached(
+ "Subscription",
+ typeof(SortingSubscription),
+ typeof(Sort),
+ new PropertyMetadata(default(SortingSubscription)));
+
+ public static void SetThreeState(DependencyObject element, bool value)
+ {
+ element.SetValue(ThreeStateProperty, value);
+ }
+
+ public static bool GetThreeState(DependencyObject element)
+ {
+ return (bool)element.GetValue(ThreeStateProperty);
+ }
+
+ private static void OnThreeStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if ((bool)e.NewValue)
+ {
+ d.SetCurrentValue(SubscriptionProperty, new SortingSubscription((DataGrid)d));
+ }
+ else
+ {
+ (d.GetValue(SubscriptionProperty) as IDisposable)?.Dispose();
+ d.ClearValue(SubscriptionProperty);
+ }
+ }
+
+ private static void OnDataGridSorting(object sender, DataGridSortingEventArgs e)
+ {
+ var dataGrid = (DataGrid)sender;
+ var sortPropertyName = e.Column.SortMemberPath;
+ if (!string.IsNullOrEmpty(sortPropertyName))
+ {
+ // sorting is cleared when the previous state is Descending
+ if (e.Column.SortDirection.HasValue && e.Column.SortDirection.Value == ListSortDirection.Descending)
+ {
+ e.Column.SetCurrentValue(DataGridColumn.SortDirectionProperty, null);
+ if ((Keyboard.Modifiers & ModifierKeys.Shift) != ModifierKeys.Shift)
+ {
+ var sortDescriptions = dataGrid.Items.SortDescriptions;
+ for (var i = sortDescriptions.Count - 1; i >= 0; i--)
+ {
+ if (sortDescriptions[i].PropertyName == sortPropertyName)
+ {
+ sortDescriptions.RemoveAt(i);
+ }
+ }
+ }
+ else
+ {
+ dataGrid.Items.SortDescriptions.Clear();
+ }
+
+ dataGrid.Items.Refresh();
+ e.Handled = true;
+ }
+ }
+ }
+
+ private sealed class SortingSubscription : IDisposable
+ {
+ private readonly DataGrid dataGrid;
+
+ public SortingSubscription(DataGrid dataGrid)
+ {
+ this.dataGrid = dataGrid;
+ this.dataGrid.Sorting += OnDataGridSorting;
+ }
+
+ public void Dispose()
+ {
+ this.dataGrid.Sorting -= OnDataGridSorting;
+ }
+ }
+ }
+}