Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 255
Domenic Avatar asked Jun 15 '09 00:06

Domenic


People also ask

How do you sort data in GridView by clicking column header?

Start by adding a GridView to your web page. To make the columns sortable, you need to set the GridView's property AllowSorting = “true” and OnSorting = “OnSorting”. SortExpression property will hold the name of the column you want to sort.

Can user sort columns WPF?

WPF DataGrid (SfDataGrid) allows you to sort the data against one or more columns either in ascending or descending order. The sorting can be performed by clicking a column header. You can enable/disable the sorting for all the columns in DataGrid by using DataGrid. AllowSorting property.

What is GridView WPF?

A GridView is a control that displays data items in rows and columns. Actually a ListView displays data. By default, it contains a GridView.


2 Answers

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> 
like image 153
Thomas Levesque Avatar answered Sep 25 '22 23:09

Thomas Levesque


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.

like image 43
Jared Harley Avatar answered Sep 22 '22 23:09

Jared Harley