Лучший способ сделать сортировку WPF ListView / GridView при нажатии на заголовок столбца?

В Интернете есть множество решений, пытающихся заполнить это, казалось бы, очень простое упущение из WPF. Я действительно смущен тем, что было бы «лучшим» способом. Например … Я хочу, чтобы в заголовке столбца были маленькие стрелки вверх / вниз, чтобы указать направление сортировки. По-видимому, есть 3 разных способа сделать это, некоторые используют код, некоторые используют разметку, некоторые используют разметку-плюс-код и все выглядят как хак.

Кто-нибудь сталкивался с этой проблемой раньше и нашел решение, в котором они полностью довольны? Кажется странным, что такая базовая функциональность WinForms отсутствует в WPF и должна быть взломана.

Все зависит от того, действительно ли вы используете DataGrid из WPF Toolkit, тогда есть встроенная сортировка, даже сортировка нескольких столбцов, которая очень полезна. Узнайте больше здесь:

Блог Vincent Sibals

Кроме того, если вы используете другой элемент управления, который не поддерживает сортировку, я бы рекомендовал следующие методы:

Индивидуальная сортировка Ли Гао

С последующим:

Более быстрая сортировка Ли Гао

Я написал набор прикрепленных свойств для автоматического сортировки GridView , вы можете проверить его здесь . Он не обрабатывает стрелку вверх / вниз, но ее можно легко добавить.

            

MSDN имеет простой способ выполнить сортировку по столбцам с глифами вверх / вниз. Однако пример не завершен – они не объясняют, как использовать шаблоны данных для глифов. Ниже я познакомился с моим списком ListView. Это работает на .Net 4.

В ListView вы должны указать обработчик событий для запуска щелчка на GridViewColumnHeader. Мой ListView выглядит так:

                 

В своем коде позади настройте код для обработки сортировки:

 // Global objects BindingListCollectionView blcv; GridViewColumnHeader _lastHeaderClicked = null; ListSortDirection _lastDirection = ListSortDirection.Ascending; // Header click event void results_Click(object sender, RoutedEventArgs e) { GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader; ListSortDirection direction; if (headerClicked != null) { if (headerClicked.Role != GridViewColumnHeaderRole.Padding) { if (headerClicked != _lastHeaderClicked) { direction = ListSortDirection.Ascending; } else { if (_lastDirection == ListSortDirection.Ascending) { direction = ListSortDirection.Descending; } else { direction = ListSortDirection.Ascending; } } string header = headerClicked.Column.Header as string; Sort(header, direction); if (direction == ListSortDirection.Ascending) { headerClicked.Column.HeaderTemplate = Resources["HeaderTemplateArrowUp"] as DataTemplate; } else { headerClicked.Column.HeaderTemplate = Resources["HeaderTemplateArrowDown"] as DataTemplate; } // Remove arrow from previously sorted header if (_lastHeaderClicked != null && _lastHeaderClicked != headerClicked) { _lastHeaderClicked.Column.HeaderTemplate = null; } _lastHeaderClicked = headerClicked; _lastDirection = direction; } } // Sort code private void Sort(string sortBy, ListSortDirection direction) { blcv.SortDescriptions.Clear(); SortDescription sd = new SortDescription(sortBy, direction); blcv.SortDescriptions.Add(sd); blcv.Refresh(); } 

А затем в вашем XAML вам нужно добавить два DataTemplates, которые вы указали в методе сортировки:

             

Использование DockPanel с LastChildFill установленное в true, будет поддерживать глиф справа от заголовка и позволить ярлыку заполнить остальную часть пространства. Я DockPanel ширину DockPanel к ActualWidth элемента GridViewColumnHeader потому что мои столбцы не имеют ширины, что позволяет им автоподтверждать содержимое. Однако я установил MinWidth s в столбцах, чтобы глиф не закрывал заголовок столбца. TextBlock Text установлен в пустую привязку, которая отображает имя столбца, указанное в заголовке.

Я использую MVVM, поэтому я создал некоторые встроенные свойства, используя Томас в качестве ссылки. Он сортирует по одному столбцу в то время, когда вы нажимаете на заголовок, переключаетесь между восходящим и нисходящим. Он сортируется с самого начала, используя первый столбец. И он показывает глифы стиля Win7 / 8.

Обычно все, что вам нужно сделать, это установить для основного свойства значение true (но вы должны явно объявить GridViewColumnHeaders):

                 

Если вы хотите сортировать по другому свойству, чем на дисплее, вы должны заявить, что:

    

Вот код для прикрепленных свойств, мне нравится быть ленивым и помещать их в предоставленный App.xaml.cs:

 using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Data. using System.Windows.Media; using System.Windows.Media.Media3D; namespace MyProjectNamespace { public partial class App : Application { #region GridViewSort public static DependencyProperty GridViewSortPropertyNameProperty = DependencyProperty.RegisterAttached( "GridViewSortPropertyName", typeof(string), typeof(App), new UIPropertyMetadata(null) ); public static string GetGridViewSortPropertyName(GridViewColumn gvc) { return (string)gvc.GetValue(GridViewSortPropertyNameProperty); } public static void SetGridViewSortPropertyName(GridViewColumn gvc, string n) { gvc.SetValue(GridViewSortPropertyNameProperty, n); } public static DependencyProperty CurrentSortColumnProperty = DependencyProperty.RegisterAttached( "CurrentSortColumn", typeof(GridViewColumn), typeof(App), new UIPropertyMetadata( null, new PropertyChangedCallback(CurrentSortColumnChanged) ) ); public static GridViewColumn GetCurrentSortColumn(GridView gv) { return (GridViewColumn)gv.GetValue(CurrentSortColumnProperty); } public static void SetCurrentSortColumn(GridView gv, GridViewColumn value) { gv.SetValue(CurrentSortColumnProperty, value); } public static void CurrentSortColumnChanged( object sender, DependencyPropertyChangedEventArgs e) { GridViewColumn gvcOld = e.OldValue as GridViewColumn; if (gvcOld != null) { CurrentSortColumnSetGlyph(gvcOld, null); } } public static void CurrentSortColumnSetGlyph(GridViewColumn gvc, ListView lv) { ListSortDirection lsd; Brush brush; if (lv == null) { lsd = ListSortDirection.Ascending; brush = Brushes.Transparent; } else { SortDescriptionCollection sdc = lv.Items.SortDescriptions; if (sdc == null || sdc.Count < 1) return; lsd = sdc[0].Direction; brush = Brushes.Gray; } FrameworkElementFactory fefGlyph = new FrameworkElementFactory(typeof(Path)); fefGlyph.Name = "arrow"; fefGlyph.SetValue(Path.StrokeThicknessProperty, 1.0); fefGlyph.SetValue(Path.FillProperty, brush); fefGlyph.SetValue(StackPanel.HorizontalAlignmentProperty, HorizontalAlignment.Center); int s = 4; if (lsd == ListSortDirection.Ascending) { PathFigure pf = new PathFigure(); pf.IsClosed = true; pf.StartPoint = new Point(0, s); pf.Segments.Add(new LineSegment(new Point(s * 2, s), false)); pf.Segments.Add(new LineSegment(new Point(s, 0), false)); PathGeometry pg = new PathGeometry(); pg.Figures.Add(pf); fefGlyph.SetValue(Path.DataProperty, pg); } else { PathFigure pf = new PathFigure(); pf.IsClosed = true; pf.StartPoint = new Point(0, 0); pf.Segments.Add(new LineSegment(new Point(s, s), false)); pf.Segments.Add(new LineSegment(new Point(s * 2, 0), false)); PathGeometry pg = new PathGeometry(); pg.Figures.Add(pf); fefGlyph.SetValue(Path.DataProperty, pg); } FrameworkElementFactory fefTextBlock = new FrameworkElementFactory(typeof(TextBlock)); fefTextBlock.SetValue(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Center); fefTextBlock.SetValue(TextBlock.TextProperty, new Binding()); FrameworkElementFactory fefDockPanel = new FrameworkElementFactory(typeof(StackPanel)); fefDockPanel.SetValue(StackPanel.OrientationProperty, Orientation.Vertical); fefDockPanel.AppendChild(fefGlyph); fefDockPanel.AppendChild(fefTextBlock); DataTemplate dt = new DataTemplate(typeof(GridViewColumn)); dt.VisualTree = fefDockPanel; gvc.HeaderTemplate = dt; } public static DependencyProperty EnableGridViewSortProperty = DependencyProperty.RegisterAttached( "EnableGridViewSort", typeof(bool), typeof(App), new UIPropertyMetadata( false, new PropertyChangedCallback(EnableGridViewSortChanged) ) ); public static bool GetEnableGridViewSort(ListView lv) { return (bool)lv.GetValue(EnableGridViewSortProperty); } public static void SetEnableGridViewSort(ListView lv, bool value) { lv.SetValue(EnableGridViewSortProperty, value); } public static void EnableGridViewSortChanged( object sender, DependencyPropertyChangedEventArgs e) { ListView lv = sender as ListView; if (lv == null) return; if (!(e.NewValue is bool)) return; bool enableGridViewSort = (bool)e.NewValue; if (enableGridViewSort) { lv.AddHandler( GridViewColumnHeader.ClickEvent, new RoutedEventHandler(EnableGridViewSortGVHClicked) ); if (lv.View == null) { lv.Loaded += new RoutedEventHandler(EnableGridViewSortLVLoaded); } else { EnableGridViewSortLVInitialize(lv); } } else { lv.RemoveHandler( GridViewColumnHeader.ClickEvent, new RoutedEventHandler(EnableGridViewSortGVHClicked) ); } } public static void EnableGridViewSortLVLoaded(object sender, RoutedEventArgs e) { ListView lv = e.Source as ListView; EnableGridViewSortLVInitialize(lv); lv.Loaded -= new RoutedEventHandler(EnableGridViewSortLVLoaded); } public static void EnableGridViewSortLVInitialize(ListView lv) { GridView gv = lv.View as GridView; if (gv == null) return; bool first = true; foreach (GridViewColumn gvc in gv.Columns) { if (first) { EnableGridViewSortApplySort(lv, gv, gvc); first = false; } else { CurrentSortColumnSetGlyph(gvc, null); } } } public static void EnableGridViewSortGVHClicked( object sender, RoutedEventArgs e) { GridViewColumnHeader gvch = e.OriginalSource as GridViewColumnHeader; if (gvch == null) return; GridViewColumn gvc = gvch.Column; if(gvc == null) return; ListView lv = VisualUpwardSearch(gvch); if (lv == null) return; GridView gv = lv.View as GridView; if (gv == null) return; EnableGridViewSortApplySort(lv, gv, gvc); } public static void EnableGridViewSortApplySort( ListView lv, GridView gv, GridViewColumn gvc) { bool isEnabled = GetEnableGridViewSort(lv); if (!isEnabled) return; string propertyName = GetGridViewSortPropertyName(gvc); if (string.IsNullOrEmpty(propertyName)) { Binding b = gvc.DisplayMemberBinding as Binding; if (b != null && b.Path != null) { propertyName = b.Path.Path; } if (string.IsNullOrEmpty(propertyName)) return; } ApplySort(lv.Items, propertyName); SetCurrentSortColumn(gv, gvc); CurrentSortColumnSetGlyph(gvc, lv); } public static void ApplySort(ICollectionView view, string propertyName) { if (string.IsNullOrEmpty(propertyName)) return; ListSortDirection lsd = ListSortDirection.Ascending; if (view.SortDescriptions.Count > 0) { SortDescription sd = view.SortDescriptions[0]; if (sd.PropertyName.Equals(propertyName)) { if (sd.Direction == ListSortDirection.Ascending) { lsd = ListSortDirection.Descending; } else { lsd = ListSortDirection.Ascending; } } view.SortDescriptions.Clear(); } view.SortDescriptions.Add(new SortDescription(propertyName, lsd)); } #endregion public static T VisualUpwardSearch(DependencyObject source) where T : DependencyObject { return VisualUpwardSearch(source, x => x is T) as T; } public static DependencyObject VisualUpwardSearch( DependencyObject source, Predicate match) { DependencyObject returnVal = source; while (returnVal != null && !match(returnVal)) { DependencyObject tempReturnVal = null; if (returnVal is Visual || returnVal is Visual3D) { tempReturnVal = VisualTreeHelper.GetParent(returnVal); } if (tempReturnVal == null) { returnVal = LogicalTreeHelper.GetParent(returnVal); } else { returnVal = tempReturnVal; } } return returnVal; } } } 

Я сделал адаптацию Microsoft, где я переопределяю элемент управления ListView чтобы создать SortableListView :

 public partial class SortableListView : ListView { private GridViewColumnHeader lastHeaderClicked = null; private ListSortDirection lastDirection = ListSortDirection.Ascending; public void GridViewColumnHeaderClicked(GridViewColumnHeader clickedHeader) { ListSortDirection direction; if (clickedHeader != null) { if (clickedHeader.Role != GridViewColumnHeaderRole.Padding) { if (clickedHeader != lastHeaderClicked) { direction = ListSortDirection.Ascending; } else { if (lastDirection == ListSortDirection.Ascending) { direction = ListSortDirection.Descending; } else { direction = ListSortDirection.Ascending; } } string sortString = ((Binding)clickedHeader.Column.DisplayMemberBinding).Path.Path; Sort(sortString, direction); lastHeaderClicked = clickedHeader; lastDirection = direction; } } } private void Sort(string sortBy, ListSortDirection direction) { ICollectionView dataView = CollectionViewSource.GetDefaultView(this.ItemsSource != null ? this.ItemsSource : this.Items); dataView.SortDescriptions.Clear(); SortDescription sD = new SortDescription(sortBy, direction); dataView.SortDescriptions.Add(sD); dataView.Refresh(); } } 

Строка ((Binding)clickedHeader.Column.DisplayMemberBinding).Path.Path обрабатывает случаи, когда имена столбцов не совпадают с их путями связывания, которые метод Microsoft не делает.

Я хотел перехватить событие GridViewColumnHeader.Click чтобы мне больше не пришлось об этом думать, но я не мог найти способ сделать это. В результате я добавляю следующее в XAML для каждого SortableListView :

 GridViewColumnHeader.Click="SortableListViewColumnHeaderClicked" 

И затем в любом Window которое содержит любое количество SortableListView s, просто добавьте следующий код:

 private void SortableListViewColumnHeaderClicked(object sender, RoutedEventArgs e) { ((Controls.SortableListView)sender).GridViewColumnHeaderClicked(e.OriginalSource as GridViewColumnHeader); } 

Где Controls – это просто XAML ID для пространства имен, в котором вы SortableListView элемент SortableListView .

Таким образом, это предотвращает дублирование кода на стороне сортировки, вам просто нужно запомнить обработку события, как указано выше.

Решение, которое суммирует все рабочие части существующих ответов и комментариев, включая шаблоны заголовков столбцов:

Посмотреть:

                                

Код Behinde:

 public partial class MyListView : ListView { GridViewColumnHeader _lastHeaderClicked = null; public MyListView() { InitializeComponent(); } private void ListViewColumnHeaderClick(object sender, RoutedEventArgs e) { GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader; if (headerClicked == null) return; if (headerClicked.Role == GridViewColumnHeaderRole.Padding) return; var sortingColumn = (headerClicked.Column.DisplayMemberBinding as Binding)?.Path?.Path; if (sortingColumn == null) return; var direction = ApplySort(Items, sortingColumn); if (direction == ListSortDirection.Ascending) { headerClicked.Column.HeaderTemplate = Resources["HeaderTemplateArrowUp"] as DataTemplate; } else { headerClicked.Column.HeaderTemplate = Resources["HeaderTemplateArrowDown"] as DataTemplate; } // Remove arrow from previously sorted header if (_lastHeaderClicked != null && _lastHeaderClicked != headerClicked) { _lastHeaderClicked.Column.HeaderTemplate = Resources["HeaderTemplateDefault"] as DataTemplate; } _lastHeaderClicked = headerClicked; } public static ListSortDirection ApplySort(ICollectionView view, string propertyName) { ListSortDirection direction = ListSortDirection.Ascending; if (view.SortDescriptions.Count > 0) { SortDescription currentSort = view.SortDescriptions[0]; if (currentSort.PropertyName == propertyName) { if (currentSort.Direction == ListSortDirection.Ascending) direction = ListSortDirection.Descending; else direction = ListSortDirection.Ascending; } view.SortDescriptions.Clear(); } if (!string.IsNullOrEmpty(propertyName)) { view.SortDescriptions.Add(new SortDescription(propertyName, direction)); } return direction; } } 

Если у вас есть listview и превратить его в gridview, вы можете легко сделать ваши заголовки столбцов gridview доступными, выполнив это.

   

Затем просто установите в свой код команду делегата.

  public DelegateCommand CommandOrderBy { get { return new DelegateCommand(Delegated_CommandOrderBy); } } private void Delegated_CommandOrderBy(object obj) { throw new NotImplementedException(); } 

Я собираюсь предположить, что вы все знаете, как сделать ICommand DelegateCommand здесь. это позволило мне сохранить все мои клики в ViewModel.

Я добавил только это, чтобы было несколько способов выполнить одно и то же. Я не писал код для добавления кнопок со стрелками в заголовке, но это было бы сделано в стиле XAML, вам нужно будет перепроектировать весь заголовок, который JanDotNet имеет в своем коде.

Попробуй это:

 using System.ComponentModel; youtItemsControl.Items.SortDescriptions.Add(new SortDescription("yourFavoritePropertyFromItem",ListSortDirection.Ascending); 
  • Пересечение и объединение ArrayLists в Java
  • указатель указателя в связанном списке добавить
  • Android: пользовательский разделитель (или даже элемент) в ListView, зависящий от содержимого элемента
  • Преобразование списка в * args при вызове функции
  • Как реализовать держатель вида?
  • Как удалить элемент из списка?
  • Как сортировать список по свойству в объекте
  • Как удалить повторяющиеся элементы из ArrayList?
  • Найдите пересечение двух вложенных списков?
  • Что именно делает метод .join ()?
  • Суммируйте список матриц
  • Давайте будем гением компьютера.