在列标题单击时使 WPF ListView/GridView 排序的最佳方法?

Posted

技术标签:

【中文标题】在列标题单击时使 WPF ListView/GridView 排序的最佳方法?【英文标题】:Best way to make WPF ListView/GridView sort on column-header clicking? 【发布时间】:2010-11-02 21:49:23 【问题描述】:

互联网上有 很多 解决方案试图填补 WPF 中这个看似非常基本的遗漏。我真的很困惑什么是“最好的”方式。例如...我希望列标题中有小向上/向下箭头来指示排序方向。显然有 3 种不同的方法可以做到这一点,一些使用代码,一些使用标记,一些使用标记加代码,所有这些看起来都像是一种 hack。

以前有没有人遇到过这个问题,并找到了他们完全满意的解决方案? WPF 中缺少这样一个基本的 WinForms 功能并需要被入侵,这似乎很奇怪。

【问题讨论】:

回答上面关于如何让 util 被识别的问题。将 xmlns:util="clr-namespace:Wpf.Util" 添加到 xaml 文档顶部的命名空间 如果可能的话.. 使用 DataGrid。 【参考方案1】:

我写了一组附加属性来自动排序GridView,你可以查看here。它不处理向上/向下箭头,但可以轻松添加。

<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>

【讨论】:

谢谢 Thomas,您对排序问题的解决方案优雅、易于使用且非常灵活。换句话说:完美!给其他人的提示:1) 使用 Thomas 文章中链接到的更新版本,以及 2) 在 cmets 中使用更漂亮的 Alex 字形版本。 我是 WPF 新手,不太了解这个“util”位。那是指什么?编辑:nm ...有一个名为“查看源代码”的小链接并没有立即跳出来。这扩展了类的源代码 我非常喜欢!我正在动态添加/删除项目,这在不改变顺序的情况下效果很好。但是如何设置排序的初始状态? CollectionViewSource.GetDefaultView(MyList.ItemsSource).SortDescriptions.Add(new SortDescription("Number", ListSortDirection.Ascending)); 不起作用。 @zee,它应该可以工作,但它不会显示排序字形...我还没有实现设置初始顺序的方法,但你总是可以尝试修改我的代码; ) 我的错 - 它运行良好! (我实例化了一个新列表而不是清除,所以 SortDescriptions 被重置)对不起【参考方案2】:

MSDN has an easy way to perform sorting on columns 带有上/下字形。不过,这个例子并不完整——他们没有解释如何为字形使用数据模板。下面是我要使用我的 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>

使用 DockPanel 并将 LastChildFill 设置为 true 将使字形保持在标题右侧,并让标签填充其余空间。我将DockPanel 宽度绑定到GridViewColumnHeaderActualWidth,因为我的列没有宽度,这可以让它们自动适应内容。不过,我确实在列上设置了MinWidths,这样字形就不会掩盖列标题。 TextBlock Text 设置为空绑定,显示标题中指定的列名。

【讨论】:

这没有指定 XAML 中放置 DataTemplates Grid.Resources 的位置? @Mark 这可能帮不了你了,但是模板应该放在根元素的资源中,通常是&lt;Window.Resources&gt;&lt;UserControl.Resources&gt;。高温高压 ;) @CptRobby 嗨.. 对我来说,由于 blcv 没有初始化,它给出了一个空引用异常......那么这段代码如何适用于任何人? @JayNirgudkar 我不是这个的作者,Jared Harley 是。但我可以告诉你,blcv 是他用作 ListView 的 ItemsSource 的东西。你不必做同样的事情。单击他提供的 MSDN 链接以获取处理 ItemsSource 的另一种方法。 MSDN 示例假定headerClicked.Column.Header(即标题文本)等价于(headerClicked.Column.DisplayMemberBinding as Binding).Path.Path(即绑定路径)。对标题文本进行排序不起作用。很奇怪。【参考方案3】:

这完全取决于,如果您使用 WPF 工具包中的 DataGrid,那么有一个内置排序,甚至是非常有用的多列排序。在这里查看更多:

Vincent Sibals Blog

或者,如果您使用不支持排序的其他控件,我建议您使用以下方法:

Li Gao's Custom Sorting

接着是:

Li Gao's Faster Sorting

【讨论】:

【参考方案4】:

我使用 MVVM,所以我创建了一些我自己的附加属性,使用 Thomas 的作为参考。当您单击标题时,它会一次对一列进行排序,在升序和降序之间切换。它使用第一列从一开始就排序。它显示了 Win7/8 风格的字形。

通常,您只需将 main 属性设置为 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;
      
  

【讨论】:

【参考方案5】:

我对微软的方式做了一个改编,我重写了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"

然后在任何包含任意数量SortableListViews 的Window 上,只需添加以下代码:

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

其中Controls 只是您在其中创建SortableListView 控件的命名空间的XAML ID。

所以,这确实可以防止排序端的代码重复,您只需要记住按上述方式处理事件即可。

【讨论】:

我从您的解决方案中汲取灵感并走上了同样的道路,要访问 GridViewColumnHeader.Click 事件,您可以在构造函数中添加一个处理程序。 this.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridViewColumnHeaderClicked));【参考方案6】:

如果您有一个列表视图并将其转换为一个网格视图,您可以通过这样做轻松地使您的网格视图列标题可点击。

        <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。 这让我可以在 ViewModel 中单击所有视图。

我只是添加了这个,以便有多种方法可以完成同一件事。 我没有编写用于在标题中添加箭头按钮的代码,但这将以 XAML 样式完成,您需要重新设计 JanDotNet 在其代码中的整个标题。

【讨论】:

【参考方案7】:

总结现有答案和 cmets 的所有工作部分的解决方案,包括列标题模板:

查看:

<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;
    

【讨论】:

如果你不解释他们在做什么,扔掉数百行代码毫无意义【参考方案8】:

只是想添加另一种简单的方法来对 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;

【讨论】:

【参考方案9】:

经过大量搜索,最后我发现这里很简单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>

【讨论】:

谢谢。加分的话,可以加个解释总结吗?这目前是仅链接和代码(这已经比仅链接更好......)。【参考方案10】:

试试这个:

using System.ComponentModel;
youtItemsControl.Items.SortDescriptions.Add(new SortDescription("yourFavoritePropertyFromItem",ListSortDirection.Ascending);

【讨论】:

以上是关于在列标题单击时使 WPF ListView/GridView 排序的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章

单击时使导航栏折叠/关闭

在按钮单击时使菜单可滚动

WPF 数据网格光标在列调整大小时不会改变

单击时使UITabBar图标不调整大小/缩放?

如何在单选按钮单击时使按钮无效?

C# WPF DataGrid 在列中搜索值,返回行索引