I have a very basic DataGrid
with some test data. The data is provided as an ObservableCollection<T>
of Client
objects
, which contains Organization
, FirstName
and LastName
properties. I group items by Organization
and would like to be able to sort by FirstName
and LastName
within specific groups (this part is functioning properly) and also sort the groups themselves by Organization
. That last part is the one I'm having problems with.
If I don't specify any sorting property or if I specify a property in CollectionViewSource.SortDescriptions
(see below), such as PropertyName="Name"
or PropertyName="CompleteNonsense"
, except for PropertyName="Organization"
, it will let me do an initial sort. If you look at the image below, company names are shown out of order, because that's how the collection was initialized. If I click on Organization
header, it will sort it in ascending order, but won't let me do any more sorting after that. If I click on the header again, it'll change the arrow, which indicates the sorting direction, but no actual sorting will occur. This is done the same no matter what sorting property I specify... even a completely arbitrary property, unless it's PropertyName="Organization"
, or if no property is specified.
If I specify sorting on PropertyName="Organization"
, it will start off sorted in ascending order, but won't sort if the header is clicked. So, it appears that that property name does trigger sorting, but only for the first time.
Screenshot:
Here's the XAML
for the DataGrid
:
<DataGrid ItemsSource="{Binding Source={StaticResource GroupedData}}"
AutoGenerateColumns="False" IsReadOnly="True"
GridLinesVisibility="None" HeadersVisibility="Column"
CanUserAddRows="False" CanUserDeleteRows="False"
CanUserResizeRows="False" Background="White">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Margin" Value="10,0,0,0"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<DockPanel Background="LightBlue" DataContext="{Binding Items}">
<TextBlock Text="{Binding Path=Organization}"
Foreground="Blue" Margin="5,0,0,0" Width="100"/>
<TextBlock Text="Employee Count:" Foreground="Blue" Margin="40,0,0,0"/>
<TextBlock Text="{Binding Path=Count}" Foreground="Blue"
Margin="5,0,0,0"/>
</DockPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Organization" Binding="{Binding Organization}"/>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
<DataGridTextColumn Width="*"/>
</DataGrid.Columns>
</DataGrid>
And here's the XAML
for the Grouping
and Sorting
:
<CollectionViewSource Source="{Binding Data}" x:Key="GroupedData">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Organization"/>
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Organization"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
Does anyone know how I can achieve the sorting of groups by Organization
beyond that initial sort?
EDIT:
I kept messing around with this (really need to figure this out) and created a quick code-behind event handler for the headers' Click
event:
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<EventSetter Event="Click" Handler="ColumnHeader_Click"/>
</Style>
</DataGrid.ColumnHeaderStyle>
private void ColumnHeader_Click(object sender, RoutedEventArgs e)
{
var columnHeader = sender as DataGridColumnHeader;
if (columnHeader != null && columnHeader.Content.ToString().Equals("Organization"))
{
if (columnHeader.Column.SortDirection == ListSortDirection.Descending)
{
columnHeader.Column.SortDirection = ListSortDirection.Ascending;
MessageBox.Show("Ascending");
}
else
{
columnHeader.Column.SortDirection = ListSortDirection.Descending;
MessageBox.Show("Descending");
}
}
}
After each click on Organization
, "Descending"
pops up, sorting arrow points downward and after clicking OK
, it immediately reverts back to pointing upward. The actual sorting of the groups never takes place visually. Something keeps it stuck in an Ascending
order.
Note: See the Edit for latest code.
This was a bit tricky, but I was able to figure out an answer. The code below is for a code-behind handler of the Sorting
event, but can be easily adjusted to be placed in a ViewModel
or View
(depending on how you do things).
Code:
<DataGrid Sorting="DataGrid_Sorting" ... > ... </DataGrid>
private void DataGrid_Sorting(object sender, DataGridSortingEventArgs e)
{
var headerName = "Organization";
var column = e.Column;
if (!column.Header.ToString().Equals(headerName))
{
return;
}
var source = (sender as System.Windows.Controls.DataGrid).ItemsSource as ListCollectionView;
if (source == null)
{
return;
}
e.Handled = true;
var sortDirection = column.SortDirection == ListSortDirection.Ascending ?
ListSortDirection.Descending : ListSortDirection.Ascending;
using (source.DeferRefresh())
{
source.SortDescriptions.Clear();
source.SortDescriptions.Add(new SortDescription(headerName, sortDirection));
}
source.Refresh();
column.SortDirection = sortDirection;
}
With the above code, groups themselves get sorted by Organization
and items within each group get sorted by FirstName
and LastName
. Hope this code helps out someone else. I've been searching around all day and it seems like a common problem people face with dealing with groups in a DataGrid
.
The only downside is that when group items get sorted by anything other than grouping property, it resets the ordering of groups to the default. I haven't been able to figure out a solution for that one after trying out a lot of code. If someone finds a solution for that part, I'll gladly give them the "correct answer."
Some of the resources that helped me out:
How to force DataGrid group order in WPF?
WPF Datagrid group and sort
EDIT:
Figured out the last part regarding the inter-group sorting being reset by the intra-group sorting. It's a bit messy, because I haven't had the time to clean it up, but I figured I'd share my code:
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Organization"/>
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Organization" Direction="Ascending"/>
<scm:SortDescription PropertyName="FirstName" Direction="Ascending"/>
<scm:SortDescription PropertyName="LastName" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
private void DataGrid_Sorting(object sender, DataGridSortingEventArgs e)
{
SortGroupedDataGrid("Organization", sender, e);
}
private void SortGroupedDataGrid(string groupHeader, object sender, DataGridSortingEventArgs e)
{
// Get the main ListCollectionView and make sure it's not null.
var source = (sender as System.Windows.Controls.DataGrid).ItemsSource as ListCollectionView;
if (source == null)
{
return;
}
// Mark event as handled, so that automated sorting would not take place.
e.Handled = true;
// Main header which was used for the grouping. I'm only using one, but more can be added.
var headerName = groupHeader;
// Get the column that was being sorted on.
var column = e.Column;
// Check if the column was the same as the one being used for the grouping.
// I remove spaces so that any properties I use match the headers. Regex would probably
// work just as well, but it's an overkill for me at this time.
var isMainHeader = column.Header.ToString().Replace(" ", "").Equals(headerName);
// Because I set the initial sorting for all the properties in the XAML to be
// be sorted in Ascending order, I set these ones to Descending. One is for
// the main column and the other is for the secondary column. This does not account
// for a case where user Shift + Clicks multiple columns to chain sort.
var mainSortDirection = ListSortDirection.Descending;
var secondarySortDirection = ListSortDirection.Descending;
// If this is a main column sort event...
if (isMainHeader)
{
// Check its sorting direction and set it as opposite.
mainSortDirection = column.SortDirection == ListSortDirection.Descending ?
ListSortDirection.Ascending : mainSortDirection;
}
else
{
// ...else, get the sorting direction of the main column, because we want
// it to stay the same, and get the opposite sorting direction for the
// secondary column.
mainSortDirection = source.SortDescriptions[0].Direction;
secondarySortDirection = column.SortDirection == ListSortDirection.Descending ?
ListSortDirection.Ascending : secondarySortDirection;
}
// Defer refreshing of the DataGrid.
using (source.DeferRefresh())
{
// Clear any existing sorts. I've had some issues trying to alter existing ones.
source.SortDescriptions.Clear();
// Since we want main column to either alter if its sort event was called, or
// stay the same if secondary column event was called, we always set it first.
source.SortDescriptions.Add(new SortDescription(headerName, mainSortDirection));
// If this was not a main column event...
if (!isMainHeader)
{
// ...then set sorting for that other column. Since it'll be at index 1,
// after the main one, it'll only sort within each group, as I wanted.
source.SortDescriptions.Add(new SortDescription(column.Header.ToString().Replace(" ", ""), secondarySortDirection));
// Set the header direction as well.
column.SortDirection = secondarySortDirection;
}
else
{
// Otherwise, it's a main event and we want to show the error for its header.
// If you want primary sorting direction to always display, then simply take
// it outside of else scope, so that it's always assigned.
column.SortDirection = mainSortDirection;
}
}
// Now we can refresh and post changes.
source.Refresh();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With