WPF DataGrid CustomSort для каждого столбца
У меня есть привязка DataGrid WPF к CollectionViewSource, который инкапсулирует ObservableCollection. Этот CollectionViewSource имеет две основные цели:
1) Чтобы сгруппировать каждый элемент по определенному свойству T. Я использую ValueConverter в GroupDescription, чтобы получить поведение группировки, которое я хочу.
2) Сортировка сетки: a) в первую очередь имя группы (как определено выше) и b) отдельные элементы группы. Я достигаю этого, присоединяя пользовательский IComparer к свойству CustomSort CollectionViewSource.
Это работает отлично по большей части, однако, как только щелкнет заголовок столбца, логика сортировки переопределена. Я не хочу отключать сортировку, однако мне было интересно, можно ли назначить настраиваемый порядок сортировки для определенного столбца?
Чтобы сделать вещи более ясными, предположим, что пользователь нажимает "ColumnA" - на данный момент логика сортировки, инкапсулированная моим CustomSorter, переопределяется, и DataGrid теперь сортируется по этому свойству. Вместо сортировки по выбранному свойству я хотел бы вместо этого изменить логику CustomSorter.
Ответы
Ответ 1
Я создал пару прикрепленных свойств, которые обрабатывают эту проблему. Надеюсь, это кому-то поможет!
Сначала - простой интерфейс для вашего направленного компаратора. Это расширяет IComparer, но дает нам еще одно свойство (SortDirection). Ваша реализация должна использовать это для определения правильного упорядочения элементов (которые в противном случае были бы потеряны).
public interface ICustomSorter : IComparer
{
ListSortDirection SortDirection { get; set; }
}
Далее приведенное поведение - это делает две вещи: 1) указывает сетке использовать пользовательскую логику сортировки (AllowCustomSort = true) и b) дает нам возможность установить эту логику на уровне столбца.
public class CustomSortBehaviour
{
public static readonly DependencyProperty CustomSorterProperty =
DependencyProperty.RegisterAttached("CustomSorter", typeof(ICustomSorter), typeof(CustomSortBehavior));
public static ICustomSorter GetCustomSorter(DataGridColumn gridColumn)
{
return (ICustomSorter)gridColumn.GetValue(CustomSorterProperty);
}
public static void SetCustomSorter(DataGridColumn gridColumn, ICustomSorter value)
{
gridColumn.SetValue(CustomSorterProperty, value);
}
public static readonly DependencyProperty AllowCustomSortProperty =
DependencyProperty.RegisterAttached("AllowCustomSort", typeof(bool),
typeof(CustomSortBehavior), new UIPropertyMetadata(false, OnAllowCustomSortChanged));
public static bool GetAllowCustomSort(DataGrid grid)
{
return (bool)grid.GetValue(AllowCustomSortProperty);
}
public static void SetAllowCustomSort(DataGrid grid, bool value)
{
grid.SetValue(AllowCustomSortProperty, value);
}
private static void OnAllowCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var existing = d as DataGrid;
if (existing == null) return;
var oldAllow = (bool)e.OldValue;
var newAllow = (bool)e.NewValue;
if (!oldAllow && newAllow)
{
existing.Sorting += HandleCustomSorting;
}
else
{
existing.Sorting -= HandleCustomSorting;
}
}
private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)
{
var dataGrid = sender as DataGrid;
if (dataGrid == null || !GetAllowCustomSort(dataGrid)) return;
var listColView = dataGrid.ItemsSource as ListCollectionView;
if (listColView == null)
throw new Exception("The DataGrid ItemsSource property must be of type, ListCollectionView");
// Sanity check
var sorter = GetCustomSorter(e.Column);
if (sorter == null) return;
// The guts.
e.Handled = true;
var direction = (e.Column.SortDirection != ListSortDirection.Ascending)
? ListSortDirection.Ascending
: ListSortDirection.Descending;
e.Column.SortDirection = sorter.SortDirection = direction;
listColView.CustomSort = sorter;
}
}
Чтобы использовать его, выполните ICustomComparer (с конструктором без параметров) и в вашем XAML:
<UserControl.Resources>
<converters:MyComparer x:Key="MyComparer"/>
<!-- add more if you need them -->
</UserControl.Resources>
<DataGrid behaviours:CustomSortBehaviour.AllowCustomSort="True" ItemsSource="{Binding MyListCollectionView}">
<DataGrid.Columns>
<DataGridTextColumn Header="Test" Binding="{Binding MyValue}" behaviours:CustomSortBehaviour.CustomSorter="{StaticResource MyComparer}" />
</DataGrid.Columns>
</DataGrid>
Ответ 2
Ответ, полученный trilson86, превосходный. Однако третий параметр в двух объявлениях DependencyProperty неверен. Вместо DataGrid и DataGridColumn они должны быть CustomSortBehaviour, как таковые:
public static readonly DependencyProperty AllowCustomSortProperty =
DependencyProperty.RegisterAttached("AllowCustomSort",
typeof(bool),
typeof(CustomSortBehaviour), // <- Here
new UIPropertyMetadata(false, OnAllowCustomSortChanged));
public static readonly DependencyProperty CustomSorterProperty =
DependencyProperty.RegisterAttached("CustomSorter",
typeof(ICustomSorter),
typeof(CustomSortBehaviour)); // <- Here
Я продолжал получать предупреждение о том, что свойство AllowCustomSort уже зарегистрировано. Небольшое исследование привело меня к отвечу здесь.
Во всяком случае, это отличный ответ, так что спасибо.
Ответ 3
Вот один из способов:
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
public static class DataGridSort
{
public static readonly DependencyProperty ComparerProperty = DependencyProperty.RegisterAttached(
"Comparer",
typeof(IComparer),
typeof(DataGridSort),
new PropertyMetadata(
default(IComparer),
OnComparerChanged));
private static readonly DependencyProperty ColumnComparerProperty = DependencyProperty.RegisterAttached(
"ColumnComparer",
typeof(ColumnComparer),
typeof(DataGridSort),
new PropertyMetadata(default(ColumnComparer)));
private static readonly DependencyProperty PreviousComparerProperty = DependencyProperty.RegisterAttached(
"PreviousComparer",
typeof(IComparer),
typeof(DataGridSort),
new PropertyMetadata(default(IComparer)));
public static readonly DependencyProperty UseCustomSortProperty = DependencyProperty.RegisterAttached(
"UseCustomSort",
typeof(bool),
typeof(DataGridSort),
new PropertyMetadata(default(bool), OnUseCustomSortChanged));
public static void SetComparer(DataGridColumn element, IComparer value)
{
element.SetValue(ComparerProperty, value);
}
public static IComparer GetComparer(DataGridColumn element)
{
return (IComparer)element.GetValue(ComparerProperty);
}
public static void SetUseCustomSort(DependencyObject element, bool value)
{
element.SetValue(UseCustomSortProperty, value);
}
public static bool GetUseCustomSort(DependencyObject element)
{
return (bool)element.GetValue(UseCustomSortProperty);
}
private static void OnComparerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var column = (DataGridColumn)d;
var columnComparer = new ColumnComparer((IComparer)e.NewValue, column);
column.SetValue(ColumnComparerProperty, columnComparer);
}
private static void OnUseCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dataGrid = (DataGrid)d;
if ((bool)e.NewValue)
{
WeakEventManager<DataGrid, DataGridSortingEventArgs>.AddHandler(dataGrid, nameof(dataGrid.Sorting), OnDataGridSorting);
}
else
{
WeakEventManager<DataGrid, DataGridSortingEventArgs>.RemoveHandler(dataGrid, nameof(dataGrid.Sorting), OnDataGridSorting);
}
}
private static void OnDataGridSorting(object sender, DataGridSortingEventArgs e)
{
var column = e.Column;
var columnComparer = (ColumnComparer)column.GetValue(ColumnComparerProperty);
var dataGrid = (DataGrid)sender;
var view = CollectionViewSource.GetDefaultView(dataGrid.ItemsSource) as ListCollectionView;
if (view == null)
{
return;
}
if (columnComparer == null)
{
view.CustomSort = (IComparer)dataGrid.GetValue(PreviousComparerProperty);
}
else
{
if (!(view.CustomSort is ColumnComparer))
{
dataGrid.SetValue(PreviousComparerProperty, view.CustomSort);
}
switch (column.SortDirection)
{
case ListSortDirection.Ascending:
column.SortDirection = ListSortDirection.Descending;
view.CustomSort = columnComparer.Descending;
break;
case null:
case ListSortDirection.Descending:
column.SortDirection = ListSortDirection.Ascending;
view.CustomSort = columnComparer.Ascending;
break;
default:
throw new ArgumentOutOfRangeException();
}
e.Handled = true;
}
}
private class ColumnComparer : IComparer
{
private readonly IComparer valueComparer;
private readonly DataGridColumn column;
private readonly InvertedComparer inverted;
public ColumnComparer(IComparer valueComparer, DataGridColumn column)
{
this.valueComparer = valueComparer;
this.column = column;
inverted = new InvertedComparer(this);
}
public IComparer Ascending => this;
public IComparer Descending => inverted;
int IComparer.Compare(object x, object y)
{
if (x == y)
{
return 0;
}
if (x == null)
{
return -1;
}
if (y == null)
{
return 1;
}
// this can perhaps be a bit slow
// Not adding caching yet.
var xProp = x.GetType().GetProperty(column.SortMemberPath);
var xValue = xProp.GetValue(x);
var yProp = x.GetType().GetProperty(column.SortMemberPath);
var yValue = yProp.GetValue(y);
return valueComparer.Compare(xValue, yValue);
}
private class InvertedComparer : IComparer
{
private readonly IComparer comparer;
public InvertedComparer(IComparer comparer)
{
this.comparer = comparer;
}
public int Compare(object x, object y)
{
return comparer.Compare(y, x);
}
}
}
}
Использование:
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding DataItems}"
local:DataGridSort.UseCustomSort="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Key}"
Header="Key"
local:DataGridSort.Comparer="{x:Static local:StringLengthComparer.Default}" />
<DataGridTextColumn Binding="{Binding Value}" Header="Value" />
</DataGrid.Columns>
</DataGrid>
Ответ 4
Я сделал это, переопределив событие OnSorting и выполнив его сам.
http://msdn.microsoft.com/en-us/library/system.windows.controls.datagrid.onsorting.aspx
В основном это означало повторную сортировку ListCollectionView.
Извините, что не слишком подробно ответил.
Ответ 5
Я изменил ответ @trilson86 так, что вам нужен только один настраиваемый класс сортировщика для всего DataGrid.
Сначала интерфейс:
public interface ICustomSorter : IComparer
{
ListSortDirection SortDirection { get; set; }
string SortMemberPath { get; set; }
}
Далее класс Bevaviour, который определяет CustomSorterProperty таким образом, что вы можете использовать его непосредственно в DataGrid, а не в DateGridRow. В HandleCustomSorting() свойство SortMemberPath из CustomSorter ist заполнено фактическим значением из столбца с щелчком, вы можете использовать это значение в своем CustomSorter для сортировки против нужного столбца.
public class CustomSortBehaviour
{
#region Fields and Constants
public static readonly DependencyProperty CustomSorterProperty =
DependencyProperty.RegisterAttached("CustomSorter", typeof (ICustomSorter), typeof (CustomSortBehaviour));
public static readonly DependencyProperty AllowCustomSortProperty =
DependencyProperty.RegisterAttached("AllowCustomSort",
typeof (bool),
typeof (CustomSortBehaviour),
new UIPropertyMetadata(false, OnAllowCustomSortChanged));
#endregion
#region public Methods
public static bool GetAllowCustomSort(DataGrid grid)
{
return (bool) grid.GetValue(AllowCustomSortProperty);
}
public static ICustomSorter GetCustomSorter(DataGrid grid)
{
return (ICustomSorter)grid.GetValue(CustomSorterProperty);
}
public static void SetAllowCustomSort(DataGrid grid, bool value)
{
grid.SetValue(AllowCustomSortProperty, value);
}
public static void SetCustomSorter(DataGrid grid, ICustomSorter value)
{
grid.SetValue(CustomSorterProperty, value);
}
#endregion
#region nonpublic Methods
private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)
{
var dataGrid = sender as DataGrid;
if (dataGrid == null || !GetAllowCustomSort(dataGrid))
{
return;
}
var listColView = dataGrid.ItemsSource as ListCollectionView;
if (listColView == null)
{
throw new Exception("The DataGrid ItemsSource property must be of type, ListCollectionView");
}
// Sanity check
var sorter = GetCustomSorter(dataGrid);
if (sorter == null)
{
return;
}
// The guts.
e.Handled = true;
var direction = (e.Column.SortDirection != ListSortDirection.Ascending)
? ListSortDirection.Ascending
: ListSortDirection.Descending;
e.Column.SortDirection = sorter.SortDirection = direction;
sorter.SortMemberPath = e.Column.SortMemberPath;
listColView.CustomSort = sorter;
}
private static void OnAllowCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var existing = d as DataGrid;
if (existing == null)
{
return;
}
var oldAllow = (bool) e.OldValue;
var newAllow = (bool) e.NewValue;
if (!oldAllow && newAllow)
{
existing.Sorting += HandleCustomSorting;
}
else
{
existing.Sorting -= HandleCustomSorting;
}
}
#endregion
}
Вы можете использовать его в XAML следующим образом:
<Window x:Class="..."
xmlns:sorter="clr-namespace:...Sorting"
...
>
<Window.Resources>
<sorter:CustomSorter x:Key="MySorter"/>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding ...}"
sorter:CustomSortBehaviour.AllowCustomSort="True"
sorter:CustomSortBehaviour.CustomSorter="{StaticResource MySorter}" >
<DataGrid.Columns>
<DataGridTextColumn Header="Column 1" Binding="{Binding Column1}"/>
<DataGridTextColumn Header="Column 2" Binding="{Binding Column2}"/>
<DataGridTextColumn Header="Column 3" Binding="{Binding Column3}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Ответ 6
Этот ответ очень похож на решение trilson86 - он был основан на нем - но он учитывает SortMemberPath
таким образом, что значения, переданные вашему компаньону, являются фактическими значениями столбца, а не строками, Это значительно облегчает повторное использование ваших сортировщиков. Кроме того, это устраняет необходимость в пользовательском интерфейсе сортировки.
DataGridSortBehavior.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace YourNamespace
{
public class DataGridSortBehavior
{
public static IComparer GetSorter(DataGridColumn column)
{
return (IComparer)column.GetValue(SorterProperty);
}
public static void SetSorter(DataGridColumn column, IComparer value)
{
column.SetValue(SorterProperty, value);
}
public static bool GetAllowCustomSort(DataGrid grid)
{
return (bool)grid.GetValue(AllowCustomSortProperty);
}
public static void SetAllowCustomSort(DataGrid grid, bool value)
{
grid.SetValue(AllowCustomSortProperty, value);
}
public static readonly DependencyProperty SorterProperty = DependencyProperty.RegisterAttached("Sorter", typeof(IComparer),
typeof(DataGridSortBehavior));
public static readonly DependencyProperty AllowCustomSortProperty = DependencyProperty.RegisterAttached("AllowCustomSort", typeof(bool),
typeof(DataGridSortBehavior), new UIPropertyMetadata(false, OnAllowCustomSortChanged));
private static void OnAllowCustomSortChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var grid = (DataGrid)obj;
bool oldAllow = (bool)e.OldValue;
bool newAllow = (bool)e.NewValue;
if (!oldAllow && newAllow)
{
grid.Sorting += HandleCustomSorting;
}
else
{
grid.Sorting -= HandleCustomSorting;
}
}
public static bool ApplySort(DataGrid grid, DataGridColumn column)
{
IComparer sorter = GetSorter(column);
if (sorter == null)
{
return false;
}
var listCollectionView = CollectionViewSource.GetDefaultView(grid.ItemsSource) as ListCollectionView;
if (listCollectionView == null)
{
throw new Exception("The ICollectionView associated with the DataGrid must be of type, ListCollectionView");
}
listCollectionView.CustomSort = new DataGridSortComparer(sorter, column.SortDirection ?? ListSortDirection.Ascending, column.SortMemberPath);
return true;
}
private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)
{
IComparer sorter = GetSorter(e.Column);
if (sorter == null)
{
return;
}
var grid = (DataGrid)sender;
e.Column.SortDirection = e.Column.SortDirection == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
if (ApplySort(grid, e.Column))
{
e.Handled = true;
}
}
private class DataGridSortComparer : IComparer
{
private IComparer comparer;
private ListSortDirection sortDirection;
private string propertyName;
private PropertyInfo property;
public DataGridSortComparer(IComparer comparer, ListSortDirection sortDirection, string propertyName)
{
this.comparer = comparer;
this.sortDirection = sortDirection;
this.propertyName = propertyName;
}
public int Compare(object x, object y)
{
PropertyInfo property = this.property ?? (this.property = x.GetType().GetProperty(propertyName));
object value1 = property.GetValue(x);
object value2 = property.GetValue(y);
int result = comparer.Compare(value1, value2);
if (sortDirection == ListSortDirection.Descending)
{
result = -result;
}
return result;
}
}
}
}
Ваш Xaml
Это должно выглядеть так же, как и решение trilson86:
<UserControl.Resources>
<converters:MyComparer x:Key="MyComparer"/>
</UserControl.Resources>
<DataGrid behaviours:DataGridSortBehavior.AllowCustomSort="True" ItemsSource="{Binding MyListCollectionView}">
<DataGrid.Columns>
<DataGridTextColumn Header="Test" Binding="{Binding MyValue}" behaviours:DataGridSortBehavior.Sorter="{StaticResource MyComparer}" />
</DataGrid.Columns>
</DataGrid>
Ответ 7
вот какое расширение для @trilson86 ICustomeSorter
что делает Сортиров более общим для использования
NumericComparer на основе этого ресурса
http://www.codeproject.com/Articles/11016/Numeric-String-Sort-in-C
public class GenericNumericComparer : ICustomSorter
{
private PropertyInfo _propertyInfo;
private Type _objectType;
public string SortMemberPath { get; set; }
private readonly NumericComparer _comparer = new NumericComparer();
public Type ObjectType
{
get { return _objectType; }
set
{
_objectType = value;
if (_objectType != null) _propertyInfo = ObjectType.GetProperty(SortMemberPath);
}
}
private int CompareHelper(object x, object y)
{
if (_propertyInfo != null)
{
var value1 = _propertyInfo.GetValue(x);
var value2 = _propertyInfo.GetValue(y);
return _comparer.Compare(value1, value2);
}
return 0;
}
public int Compare(object x, object y)
{
var i = CompareHelper(x, y);
if (SortDirection == ListSortDirection.Ascending)
return i;
return i*-1;
}
public ListSortDirection SortDirection { get; set; }
}
Ответ 8
DataGrid CustomSorting
<DataGrid attached:DataGridHelpers.UseCustomSort="True" ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn attached:DataGridHelpers.CustomSorterType="{x:Type comparers:StrLogicalComparer}" Binding="{Binding CodeText}" Header="Code" />
<DataGridTextColumn Header="Number" Binding="{Binding Number}" />
</DataGrid.Columns>
</DataGrid>
Поддержка вложенных свойств
Ответ 9
вы можете использовать это, если вы добавляете столбцы программно.
dg_show.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("val1", System.ComponentModel.ListSortDirection.Descending));
"val1" здесь является связующим путем для столбца, который мы добавили, и вы также можете использовать другую строку в качестве второго сортировки. как этот.
dg_show.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("val2", System.ComponentModel.ListSortDirection.Ascending));