Best way to make WPF ListView/GridView sort on column-header clicking?

WpfGridviewSortingListview

Wpf Problem Overview


There are lots of solutions on the internet attempting to fill this seemingly very-basic omission from WPF. I'm really confused as to what would be the "best" way. For example... I want there to be little up/down arrows in the column header to indicate sort direction. There are apparently like 3 different ways to do this, some using code, some using markup, some using markup-plus-code, and all seeming rather like a hack.

Has anyone run into this problem before, and found a solution they are completely happy with? It seems bizarre that such a basic WinForms piece of functionality is missing from WPF and needs to be hacked in.

Wpf Solutions


Solution 1 - Wpf

I wrote a set of attached properties to automatically sort a GridView, you can check it out here. It doesn't handle the up/down arrow, but it could easily be added.

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

Solution 2 - Wpf

MSDN has an easy way to perform sorting on columns with up/down glyphs. The example isn't complete, though - they don't explain how to use the data templates for the glyphs. Below is what I got to work with my ListView. This works on .Net 4.

In your ListView, you have to specify an event handler to fire for a click on the GridViewColumnHeader. My ListView looks like this:

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

In your code behind, set up the code to handle the sorting:

// 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();
}

And then in your XAML, you need to add two DataTemplates that you specified in the sorting method:

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

Using the DockPanel with LastChildFill set to true will keep the glyph on the right of the header and let the label fill the rest of the space. I bound the DockPanel width to the ActualWidth of the GridViewColumnHeader because my columns have no width, which lets them autofit to the content. I did set MinWidths on the columns, though, so that the glyph doesn't cover up the column title. The TextBlock Text is set to an empty binding which displays the column name specified in the header.

Solution 3 - Wpf

It all depends really, if you're using the DataGrid from the WPF Toolkit then there is a built in sort, even a multi-column sort which is very useful. Check more out here:

Vincent Sibals Blog

Alternatively, if you're using a different control that doesn't support sorting, i'd recommend the following methods:

Li Gao's Custom Sorting

Followed by:

Li Gao's Faster Sorting

Solution 4 - Wpf

I use MVVM, so I created some attached properties of my own, using Thomas's as a reference. It does sorting on one column at a time when you click on the header, toggling between Ascending and Descending. It sorts from the very beginning using the first column. And it shows Win7/8 style glyphs.

Normally, all you have to do is set the main property to true (but you have to explicitly declare the 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>

If you want to sort on a different property than the display, than you have to declare that:

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

Here's the code for the attached properties, I like to be lazy and put them in the provided 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;
      }
  }
}

Solution 5 - Wpf

I made an adaptation of the Microsoft way, where I override the ListView control to make a 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();
        }
    }

The line ((Binding)clickedHeader.Column.DisplayMemberBinding).Path.Path bit handles the cases where your column names are not the same as their binding paths, which the Microsoft method does not do.

I wanted to intercept the GridViewColumnHeader.Click event so that I wouldn't have to think about it anymore, but I couldn't find a way to to do. As a result I add the following in XAML for every SortableListView:

GridViewColumnHeader.Click="SortableListViewColumnHeaderClicked"

And then on any Window that contains any number of SortableListViews, just add the following code:

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

Where Controls is just the XAML ID for the namespace in which you made the SortableListView control.

So, this does prevent code duplication on the sorting side, you just need to remember to handle the event as above.

Solution 6 - Wpf

If you have a listview and turn it into a gridview you can easily make your gridview columns headers clickable by doing this.

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

Then just set a delegate command in your code.

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

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

Im going to assume you all know how to make the ICommand DelegateCommand here. this allowed me to keep all my View clicking in the ViewModel.

I only added this so that there is multiple ways to accomplish the same thing. I did not write code for adding arrow buttons in the header, but that would be done in XAML style, you would need to redesign the entire header which JanDotNet has in their code.

Solution 7 - Wpf

Solution that summarizes all working parts of existing answers and comments including column header templates:

View:

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

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

Solution 8 - Wpf

Just wanted to add another simple way someone can sort the the 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;
}

Solution 9 - Wpf

After search alot, finaly i found simple here 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));
}

Class:

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>

Solution 10 - Wpf

Try this:

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDomenicView Question on Stackoverflow
Solution 1 - WpfThomas LevesqueView Answer on Stackoverflow
Solution 2 - WpfJared HarleyView Answer on Stackoverflow
Solution 3 - WpfKyle RosendoView Answer on Stackoverflow
Solution 4 - WpfSean HallView Answer on Stackoverflow
Solution 5 - WpfGregView Answer on Stackoverflow
Solution 6 - WpfNew BeeView Answer on Stackoverflow
Solution 7 - WpfJanDotNetView Answer on Stackoverflow
Solution 8 - WpfIntegratedHenView Answer on Stackoverflow
Solution 9 - WpfTrÆ°Æ¡ng Quốc KhánhView Answer on Stackoverflow
Solution 10 - Wpfali.radView Answer on Stackoverflow