Лучший способ сделать сортировку WPF ListView / GridView при нажатии на заголовок столбца?
В Интернете есть множество решений, пытающихся заполнить это, казалось бы, очень простое упущение из WPF. Я действительно смущен тем, что было бы «лучшим» способом. Например … Я хочу, чтобы в заголовке столбца были маленькие стрелки вверх / вниз, чтобы указать направление сортировки. По-видимому, есть 3 разных способа сделать это, некоторые используют код, некоторые используют разметку, некоторые используют разметку-плюс-код и все выглядят как хак.
Кто-нибудь сталкивался с этой проблемой раньше и нашел решение, в котором они полностью довольны? Кажется странным, что такая базовая функциональность WinForms отсутствует в WPF и должна быть взломана.
- Как преобразовать строку ключа, разделенного пробелами, пары значений уникальных слов в dict
- Android: макет xml для списка с различными элементами
- Эффективное пересечение двух List в Java?
- Преобразование списка кортежей в словарь
- Как подсчитать количество вхождений элементов в списке в Prolog
- Прокрутите список в WPF в определенную строку
- Заменить значения в списке с помощью Python
- Вызов метода активности из адаптера
- Метод подсчета вхождений в список
- WPF ListView Неактивный цвет выделения
- Как сортировать список по свойству в объекте
- указатель указателя в связанном списке добавить
- Как инициализировать List для заданного размера (в отличие от емкости)?
Все зависит от того, действительно ли вы используете 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);