WPF DataGrid:排序后无法拖拽重排、编辑自动重排问题求助
解决WPF DataGrid排序后拖拽重排&编辑自动回退的问题
我太懂这种踩坑的感觉了——默认的DataGrid排序机制和手动拖拽/编辑的需求天生就有冲突,核心问题出在ICollectionView的自动排序逻辑上:当你通过列标题排序后,视图会保留SortDescriptions,任何导致视图刷新的操作(拖拽修改顺序、编辑项属性)都会触发视图重新应用排序规则,直接把你手动调整的顺序冲掉。
下面是我亲测有效的解决方案,既能保留列标题点击排序的功能,又能让拖拽重排和编辑不受自动排序的干扰:
核心思路
放弃DataGrid默认的ICollectionView自动排序,改为手动接管排序逻辑:
- 用两个集合:一个存原始数据(比如
ObservableCollection<T>),另一个存当前显示的可编辑/拖拽的列表(同样是ObservableCollection<T>) - 点击列标题时,手动对原始数据排序,然后把排序结果替换到显示集合中,同时记录当前的排序列和方向
- 拖拽重排和编辑操作直接作用于显示集合,因为它没有绑定自动排序的视图,所以不会触发自动回退
- 再次点击同一列时,根据记录的排序方向切换,重新执行排序逻辑
具体实现步骤
1. ViewModel 准备
首先在ViewModel里定义必要的属性和方法:
public class YourViewModel : INotifyPropertyChanged { // 原始数据源,保存所有数据 private ObservableCollection<YourItem> _originalItems; public ObservableCollection<YourItem> OriginalItems { get => _originalItems; set { _originalItems = value; OnPropertyChanged(); } } // 当前显示的列表,用于DataGrid绑定,支持拖拽和编辑 private ObservableCollection<YourItem> _displayItems; public ObservableCollection<YourItem> DisplayItems { get => _displayItems; set { _displayItems = value; OnPropertyChanged(); } } // 记录当前排序的列名和方向 private string _currentSortColumn; private ListSortDirection _currentSortDirection = ListSortDirection.Ascending; // 排序方法 public void SortItems(string sortColumn) { // 如果点击的是当前排序列,切换方向 if (_currentSortColumn == sortColumn) { _currentSortDirection = _currentSortDirection == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending; } else { // 新列排序,默认升序 _currentSortColumn = sortColumn; _currentSortDirection = ListSortDirection.Ascending; } // 对原始数据执行排序,这里根据你的实体属性调整 var sortedList = _currentSortDirection == ListSortDirection.Ascending ? OriginalItems.OrderBy(item => GetPropertyValue(item, sortColumn)).ToList() : OriginalItems.OrderByDescending(item => GetPropertyValue(item, sortColumn)).ToList(); // 更新显示集合(注意要替换整个集合,或者清空再添加,保证DataGrid刷新) DisplayItems = new ObservableCollection<YourItem>(sortedList); } // 反射获取实体属性值(如果你的列绑定的是固定属性,也可以直接写判断) private object GetPropertyValue(object obj, string propertyName) { return obj.GetType().GetProperty(propertyName)?.GetValue(obj, null); } // INotifyPropertyChanged 实现 public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
2. DataGrid XAML 配置
绑定DisplayItems,并订阅Sorting事件:
<DataGrid x:Name="YourDataGrid" ItemsSource="{Binding DisplayItems}" Sorting="YourDataGrid_Sorting" CanUserReorderColumns="False" <!-- 这里是列的重排,我们要的是行的拖拽,所以关闭这个 --> CanUserSortColumns="True"> <!-- 你的列定义,比如: --> <DataGridTextColumn Header="名称" Binding="{Binding Name}" SortMemberPath="Name"/> <DataGridTextColumn Header="数值" Binding="{Binding Value}" SortMemberPath="Value"/> <!-- 拖拽行的逻辑可以用WPF原生DragDrop或者第三方库,比如Extended WPF Toolkit --> </DataGrid>
3. Sorting 事件处理
在后台代码里阻止默认排序,调用ViewModel的排序方法:
private void YourDataGrid_Sorting(object sender, DataGridSortingEventArgs e) { // 阻止DataGrid默认的排序逻辑 e.Handled = true; // 获取点击的列的排序字段 var sortColumn = e.Column.SortMemberPath; if (string.IsNullOrEmpty(sortColumn)) return; // 调用ViewModel的排序方法 var vm = DataContext as YourViewModel; vm?.SortItems(sortColumn); // 更新列的排序图标显示(可选,让用户知道当前排序状态) e.Column.SortDirection = vm._currentSortDirection; // 清除其他列的排序图标 foreach (var column in YourDataGrid.Columns) { if (column != e.Column) column.SortDirection = null; } }
4. 拖拽重排的适配
因为现在DisplayItems是普通的ObservableCollection,拖拽重排时直接修改这个集合就行,比如用原生DragDrop的示例:
// 拖拽开始事件 private void YourDataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var row = ItemsControl.ContainerFromElement(YourDataGrid, e.OriginalSource as DependencyObject) as DataGridRow; if (row != null) { DragDrop.DoDragDrop(row, row.Item, DragDropEffects.Move); } } // 拖拽进入事件 private void YourDataGrid_DragEnter(object sender, DragEventArgs e) { if (!e.Data.GetDataPresent(typeof(YourItem)) || sender == e.Source) { e.Effects = DragDropEffects.None; } } // 拖拽放下事件 private void YourDataGrid_Drop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(typeof(YourItem))) { var droppedItem = e.Data.GetData(typeof(YourItem)) as YourItem; var targetItem = YourDataGrid.SelectedItem as YourItem; var vm = DataContext as YourViewModel; if (droppedItem != null && targetItem != null && vm != null) { // 获取两个项的索引 int droppedIndex = vm.DisplayItems.IndexOf(droppedItem); int targetIndex = vm.DisplayItems.IndexOf(targetItem); // 调整顺序 if (droppedIndex != targetIndex) { vm.DisplayItems.Remove(droppedItem); vm.DisplayItems.Insert(targetIndex, droppedItem); } } } }
为什么之前的方法不行?
你之前尝试把排序后的视图复制到新列表,但没处理排序方向切换,是因为没有记录当前的排序状态——上面的方案通过ViewModel保存_currentSortColumn和_currentSortDirection,每次点击列标题时都会判断是否需要切换方向,完美解决了这个问题。
另外,现在DisplayItems是完全独立的可编辑集合,编辑项时只会更新集合中的数据,不会触发任何自动排序,因为没有绑定带SortDescriptions的ICollectionView。
内容的提问来源于stack exchange,提问作者Neil B




