我有一个绑定到CollectionViewSource的WPF DataGrid,它封装了一个ObservableCollection。此CollectionViewSource有两个主要目标:
1)按T的特定属性对每个项目进行分组。我在GroupDescription中使用ValueConverter来获取我想要的分组行为。
2)通过a)主要对组名称(如上定义)和b)各个组项目对网格进行排序。我是通过将自定义IComparer附加到CollectionViewSource的'CustomSort'属性来实现的。
这在大多数情况下都很有效,但是只要单击列标题,就会覆盖排序逻辑。我不想禁用排序,但是我想知道是否可以为特定列分配自定义排序顺序?
为了使事情更加清晰,假设用户点击“ColumnA” - 此时,我的CustomSorter封装的排序逻辑被覆盖,DataGrid现在按该属性排序。我宁愿颠倒CustomSorter的逻辑,而不是按所选属性进行排序。
答案 0 :(得分:26)
我创建了一些处理此问题的附加属性。我希望这对某人来说很方便!
首先 - 定向比较器的简单接口。这扩展了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's 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;
}
}
要使用它,请在XAML中实现ICustomComparer(带无参数构造函数):
<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>
答案 1 :(得分:4)
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属性。一个小小的研究让我得到答案here。
无论如何,这是一个很好的答案,谢谢。
答案 2 :(得分:4)
这是一种方式:
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>
答案 3 :(得分:2)
我是通过覆盖OnSorting事件并自己实现它来完成的。
http://msdn.microsoft.com/en-us/library/system.windows.controls.datagrid.onsorting.aspx
这基本上意味着重新排序ListCollectionView。
对不起它的答案不太深入。
答案 4 :(得分:1)
我更改了@ trilson86的回答,因此您只需要为整个DataGrid提供一个自定义排序器类。
首先是界面:
public interface ICustomSorter : IComparer
{
ListSortDirection SortDirection { get; set; }
string SortMemberPath { get; set; }
}
接下来,Bevaviour类以这样的方式定义CustomSorterProperty,您可以直接在DataGrid上使用它,而不是在DateGridRow上。在HandleCustomSorting()中,CustomSorter的属性SortMemberPath填充了单击列中的实际值,您可以在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's 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>
答案 5 :(得分:1)
这个答案与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;
}
}
}
}
这应该与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>
答案 6 :(得分:1)
我从这个问题中学到了很多,在这里我分享了一种解决方法。
符合mvvm时,此方法不会修改xaml。
我需要将文件路径加载到DataGrid,可以将其排序为Windows资源管理器。
有一些文件:
E:\ Test \ Item_1.txt
E:\ Test \ Item_04.txt
E:\ Test \ Item_5.txt
E:\ Test \ Item_10.txt
注意,我已通过Windows资源管理器按文件名排序。
我认为您已经发现Explorer所使用的文件排序不是简单的字符串排序。
它使用 shlwapi.dll
中的win32 api StrCmpLogicalW
我们需要为已排序的属性实现 IComparable (非通用)接口。
为了减少代码量,我使用了Prism.Mvvm.BindableBase(一个INotifyPropertyChanged实现)。
像这样的代码:
/// <summary>
/// Data Model
/// </summary>
public class ListItemModel : BindableBase
{
private FilePath filePath;
public FilePath FilePath
{
get { return filePath; }
set { SetProperty(ref filePath, value); }
}
/// Other properties.
/// ....
}
/// <summary>
/// wrapper of filepath
/// </summary>
public class FilePath : IComparable
{
private string filePath;
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern int StrCmpLogicalW(string psz1, string psz2);
public FilePath(string filePath)
{
this.filePath = filePath;
}
/// <summary>
/// Implicit type conversion.
/// </summary>
/// <param name="x"></param>
public static implicit operator string(FilePath x)
{
return x.filePath;
}
public static implicit operator FilePath(string x)
{
return new FilePath(x);
}
/// <summary>
/// override for datagrid display.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return filePath;
}
/// <summary>
/// Implement the interface IComparable for Custom sorting.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public int CompareTo(object obj)
{
if (obj is FilePath other)
return StrCmpLogicalW(filePath, other.filePath);
return 1;
}
}
XAML代码:
<!-- Items is ObservableCollection<ListItemModel> -->
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="File" Binding="{Binding FilePath}" />
</DataGrid.Columns>
</DataGrid>
总而言之,我封装了原始的字符串属性,
您还可以封装自定义属性以进行排序,
只需覆盖 ToString 进行显示,并实现 CompareTo 进行排序。
答案 7 :(得分:0)
这里有一些@ 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 :(得分:0)
<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 :(得分:-2)
如果您以编程方式添加列,则可以使用它。
dg_show.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("val1", System.ComponentModel.ListSortDirection.Descending));
&#34; VAL1&#34;这是我们添加的列的绑定路径,您还可以使用另一行作为第二种排序。喜欢这个。
dg_show.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("val2", System.ComponentModel.ListSortDirection.Ascending));