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

84

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

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

Доменик
источник
В ответ на вышеупомянутый вопрос о том, как заставить утилиту быть распознанной. Добавьте xmlns: util = "clr-namespace: Wpf.Util" в пространство имен вверху документа
xaml
Если возможно .. используйте DataGrid.
Abhijeet Nagre

Ответы:

22

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

Блог Винсента Сибалса

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

Пользовательская сортировка Ли Гао

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

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

Кайл Розендо
источник
111

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

<ListView ItemsSource="{Binding Persons}"
          IsSynchronizedWithCurrentItem="True"
          util:GridViewSort.AutoSort="True">
    <ListView.View>
        <GridView>
            <GridView.Columns>
                <GridViewColumn Header="Name"
                                DisplayMemberBinding="{Binding Name}"
                                util:GridViewSort.PropertyName="Name"/>
                <GridViewColumn Header="First name"
                                DisplayMemberBinding="{Binding FirstName}"
                                util:GridViewSort.PropertyName="FirstName"/>
                <GridViewColumn Header="Date of birth"
                                DisplayMemberBinding="{Binding DateOfBirth}"
                                util:GridViewSort.PropertyName="DateOfBirth"/>
            </GridView.Columns>
        </GridView>
    </ListView.View>
</ListView>
Томас Левеск
источник
8
Спасибо, Томас, ваше решение проблемы сортировки элегантное, простое в использовании и очень гибкое. Другими словами: идеально! Совет для других: 1) используйте обновленную версию, указанную в статье Томаса, и 2) используйте более красивую глифовую версию Alex в комментариях.
Helge Klein
Я новичок в WPF и не совсем понимаю этот "служебный" бит. Что это за ссылка? Изменить: нм ... была эта крошечная ссылка под названием «Просмотр источника», которая не сразу бросилась мне в глаза. Это расширяет исходный код класса
oscilatingcretin
Мне это очень нравится! Я добавляю / удаляю элементы динамически, и это отлично работает без изменения порядка. Но как я могу установить начальное состояние для сортировки? CollectionViewSource.GetDefaultView(MyList.ItemsSource).SortDescriptions.Add(new SortDescription("Number", ListSortDirection.Ascending));не работает.
zee
@zee, он должен работать, но он не будет отображать глиф сортировки ... Я не реализовал способ установки начального порядка, но вы всегда можете попробовать изменить мой код;)
Thomas Levesque
Моя вина - работает отлично! (Я создал новый список вместо очистки, поэтому SortDescriptions был сброшен) Извините
zee
23

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

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

<ListView Name="results" GridViewColumnHeader.Click="results_Click">
    <ListView.View>
        <GridView>
            <GridViewColumn DisplayMemberBinding="{Binding Path=ContactName}">
                <GridViewColumn.Header>
                    <GridViewColumnHeader Content="Contact Name" Padding="5,0,0,0" HorizontalContentAlignment="Left" MinWidth="150" Name="ContactName" />
                </GridViewColumn.Header>
            </GridViewColumn>
            <GridViewColumn DisplayMemberBinding="{Binding Path=PrimaryPhone}">
                <GridViewColumn.Header>
                    <GridViewColumnHeader Content="Contact Number" Padding="5,0,0,0" HorizontalContentAlignment="Left" MinWidth="150" Name="PrimaryPhone"/>
                </GridViewColumn.Header>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</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 вам нужно добавить два DataTemplate, которые вы указали в методе сортировки:

<DataTemplate x:Key="HeaderTemplateArrowUp">
    <DockPanel LastChildFill="True" Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumnHeader}}}">
        <Path x:Name="arrowUp" StrokeThickness="1" Fill="Gray" Data="M 5,10 L 15,10 L 10,5 L 5,10" DockPanel.Dock="Right" Width="20" HorizontalAlignment="Right" Margin="5,0,5,0" SnapsToDevicePixels="True"/>
        <TextBlock Text="{Binding }" />
    </DockPanel>
</DataTemplate>

<DataTemplate x:Key="HeaderTemplateArrowDown">
    <DockPanel LastChildFill="True" Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumnHeader}}}">
        <Path x:Name="arrowDown" StrokeThickness="1" Fill="Gray"  Data="M 5,5 L 10,10 L 15,5 L 5,5" DockPanel.Dock="Right" Width="20" HorizontalAlignment="Right" Margin="5,0,5,0" SnapsToDevicePixels="True"/>
        <TextBlock Text="{Binding }" />
    </DockPanel>
</DataTemplate>

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

Джаред Харли
источник
1
Это не указывает, где в XAML разместить DataTemplates Grid.Resources?
4
@Mark Скорее всего, уже слишком поздно, чтобы помочь вам, но шаблоны должны быть помещены в ресурсы корневого элемента, обычно <Window.Resources>или <UserControl.Resources>. HTHS;)
CptRobby 04
1
@CptRobby привет .. для меня, поскольку blcv не инициализирован, он дает исключение нулевой ссылки ... так как этот код будет работать для кого-либо?
Джей Ниргудкар
@JayNirgudkar Я не автор этого, Джаред Харли. Но я могу вам сказать, что blcv - это то, что он использовал в качестве ItemsSource для своего ListView. Тебе не нужно делать то же самое. Щелкните ссылку MSDN, которую он предоставил, чтобы узнать альтернативный способ работы с ItemsSource.
CptRobby
2
В примере MSDN предполагается, что headerClicked.Column.Header(это текст заголовка) эквивалентно (headerClicked.Column.DisplayMemberBinding as Binding).Path.Path(что является путем привязки). Сортировка по тексту заголовка не работает. Очень странно.
Крис
5

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

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

<Window xmlns:local="clr-namespace:MyProjectNamespace">
  <Grid>
    <ListView local:App.EnableGridViewSort="True" ItemsSource="{Binding LVItems}">
      <ListView.View>
        <GridView>
          <GridViewColumn DisplayMemberBinding="{Binding Property1}">
            <GridViewColumnHeader Content="Prop 1" />
          </GridViewColumn>
          <GridViewColumn DisplayMemberBinding="{Binding Property2}">
            <GridViewColumnHeader Content="Prop 2" />
          </GridViewColumn>
        </GridView>
      </ListView.View>
    </ListView>
  </Grid>
<Window>

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

<GridViewColumn DisplayMemberBinding="{Binding Property3}"
                local:App.GridViewSortPropertyName="Property4">
    <GridViewColumnHeader Content="Prop 3" />
</GridViewColumn>

Вот код для прикрепленных свойств, я люблю полениться и помещаю их в предоставленный 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<ListView>(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<T>(DependencyObject source) 
          where T : DependencyObject
      {
          return VisualUpwardSearch(source, x => x is T) as T;
      }

      public static DependencyObject VisualUpwardSearch(
                          DependencyObject source, Predicate<DependencyObject> 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;
      }
  }
}
Шон Холл
источник
3

Я адаптировал способ 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, содержащего любое количество SortableListViews, просто добавьте следующий код:

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

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

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

Грег
источник
1
Я черпал вдохновение из вашего решения и пошел по тому же пути, чтобы получить доступ к событию GridViewColumnHeader.Click, вы можете добавить обработчик в конструктор. this.AddHandler (GridViewColumnHeader.ClickEvent, новый RoutedEventHandler (GridViewColumnHeaderClicked));
Деррик Меллер,
3

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

        <Style TargetType="GridViewColumnHeader">
            <Setter Property="Command" Value="{Binding CommandOrderBy}"/>
            <Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Self},Path=Content}"/>
        </Style>

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

    public DelegateCommand CommandOrderBy { get { return new DelegateCommand(Delegated_CommandOrderBy); } }

    private void Delegated_CommandOrderBy(object obj)
    {
        throw new NotImplementedException();
    }

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

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

Новая пчела
источник
0

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

Посмотреть:

<ListView x:Class="MyNamspace.MyListView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             ItemsSource="{Binding Items}"
             GridViewColumnHeader.Click="ListViewColumnHeaderClick">
    <ListView.Resources>

        <Style TargetType="Grid" x:Key="HeaderGridStyle">
            <Setter Property="Height" Value="20" />
        </Style>

        <Style TargetType="TextBlock" x:Key="HeaderTextBlockStyle">
            <Setter Property="Margin" Value="5,0,0,0" />
            <Setter Property="VerticalAlignment" Value="Center" />
        </Style>

        <Style TargetType="Path" x:Key="HeaderPathStyle">
            <Setter Property="StrokeThickness" Value="1" />
            <Setter Property="Fill" Value="Gray" />
            <Setter Property="Width" Value="20" />
            <Setter Property="HorizontalAlignment" Value="Center" />
            <Setter Property="Margin" Value="5,0,5,0" />
            <Setter Property="SnapsToDevicePixels" Value="True" />
        </Style>

        <DataTemplate x:Key="HeaderTemplateDefault">
            <Grid Style="{StaticResource HeaderGridStyle}">
                <TextBlock Text="{Binding }" Style="{StaticResource HeaderTextBlockStyle}" />
            </Grid>
        </DataTemplate>

        <DataTemplate x:Key="HeaderTemplateArrowUp">
            <Grid Style="{StaticResource HeaderGridStyle}">
                <Path Data="M 7,3 L 13,3 L 10,0 L 7,3" Style="{StaticResource HeaderPathStyle}" />
                <TextBlock Text="{Binding }" Style="{StaticResource HeaderTextBlockStyle}" />
            </Grid>
        </DataTemplate>

        <DataTemplate x:Key="HeaderTemplateArrowDown">
            <Grid Style="{StaticResource HeaderGridStyle}">
                <Path Data="M 7,0 L 10,3 L 13,0 L 7,0"  Style="{StaticResource HeaderPathStyle}" />
                <TextBlock Text="{Binding }" Style="{StaticResource HeaderTextBlockStyle}" />
            </Grid>
        </DataTemplate>

    </ListView.Resources>

    <ListView.View>
        <GridView ColumnHeaderTemplate="{StaticResource HeaderTemplateDefault}">

            <GridViewColumn Header="Name" DisplayMemberBinding="{Binding NameProperty}" />
            <GridViewColumn Header="Type" Width="45" DisplayMemberBinding="{Binding TypeProperty}"/>

            <!-- ... -->

        </GridView>
    </ListView.View>
</ListView>

Кодовое поведение:

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;
    }
}
JanDotNet
источник
1
Выбрасывание сотен строк кода ничего не значит, если вы не объясните, что они делают
schizoid04
0

Просто хотел добавить еще один простой способ сортировки WPF ListView

void SortListView(ListView listView)
{
    IEnumerable listView_items = listView.Items.SourceCollection;
    List<MY_ITEM_CLASS> listView_items_to_list = listView_items.Cast<MY_ITEM_CLASS>().ToList();

    Comparer<MY_ITEM_CLASS> scoreComparer = Comparer<MY_ITEM_CLASS>.Create((first, second) => first.COLUMN_NAME.CompareTo(second.COLUMN_NAME));

    listView_items_to_list.Sort(scoreComparer);
    listView.ItemsSource = null;
    listView.Items.Clear();
    listView.ItemsSource = listView_items_to_list;
}
IntegratedHen
источник
0

После большого количества поисков, наконец, я нашел здесь просто https://www.wpf-tutorial.com/listview-control/listview-how-to-column-sorting/

private GridViewColumnHeader listViewSortCol = null;
private SortAdorner listViewSortAdorner = null;
private void GridViewColumnHeader_Click(object sender, RoutedEventArgs e)
{
  GridViewColumnHeader column = (sender as GridViewColumnHeader);
  string sortBy = column.Tag.ToString();
  if (listViewSortCol != null)
  {
    AdornerLayer.GetAdornerLayer(listViewSortCol).Remove(listViewSortAdorner);
    yourListView.Items.SortDescriptions.Clear();
  }

  ListSortDirection newDir = ListSortDirection.Ascending;
  if (listViewSortCol == column && listViewSortAdorner.Direction == newDir)
    newDir = ListSortDirection.Descending;

  listViewSortCol = column;
  listViewSortAdorner = new SortAdorner(listViewSortCol, newDir);
  AdornerLayer.GetAdornerLayer(listViewSortCol).Add(listViewSortAdorner);
  yourListView.Items.SortDescriptions.Add(new SortDescription(sortBy, newDir));
}

Класс:

public class SortAdorner : Adorner
{
    private static Geometry ascGeometry =
        Geometry.Parse("M 0 4 L 3.5 0 L 7 4 Z");

    private static Geometry descGeometry =
        Geometry.Parse("M 0 0 L 3.5 4 L 7 0 Z");

    public ListSortDirection Direction { get; private set; }

    public SortAdorner(UIElement element, ListSortDirection dir)
        : base(element)
    {
        this.Direction = dir;
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        if(AdornedElement.RenderSize.Width < 20)
            return;

        TranslateTransform transform = new TranslateTransform
            (
                AdornedElement.RenderSize.Width - 15,
                (AdornedElement.RenderSize.Height - 5) / 2
            );
        drawingContext.PushTransform(transform);

        Geometry geometry = ascGeometry;
        if(this.Direction == ListSortDirection.Descending)
            geometry = descGeometry;
        drawingContext.DrawGeometry(Brushes.Black, null, geometry);

        drawingContext.Pop();
    }
}

Xaml

<GridViewColumn Width="250">
  <GridViewColumn.Header>
    <GridViewColumnHeader Tag="Name" Click="GridViewColumnHeader_Click">Name</GridViewColumnHeader>
  </GridViewColumn.Header>
  <GridViewColumn.CellTemplate>
    <DataTemplate>
        <TextBlock Text="{Binding Name}" ToolTip="{Binding Name}"/>
    </DataTemplate>
  </GridViewColumn.CellTemplate>
</GridViewColumn>
Транг Куок Хан
источник
1
Благодарю. Не могли бы вы добавить краткое объяснение для дополнительных баллов? В настоящее время это только ссылка и код (что уже лучше, чем только ссылка ...).
Юннош
-2

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

using System.ComponentModel;
youtItemsControl.Items.SortDescriptions.Add(new SortDescription("yourFavoritePropertyFromItem",ListSortDirection.Ascending);
ali.rad
источник