New to Telerik UI for WinFormsStart a free 30-day trial

Custom Group Comparer

Updated on Apr 22, 2026

RadPivotGrid uses the GroupComparer class to determine the order of groups within rows and columns. The two built-in comparers, GroupNameComparer and GrandTotalComparer, cover common scenarios such as sorting groups alphabetically or by their aggregate totals. When neither fits your requirements, you can create a custom comparer by inheriting the GroupComparer base class.

Understanding the GroupComparer Class

The GroupComparer abstract class resides in the Telerik.Pivot.Core namespace. It exposes a single abstract method that you must override:

C#
public abstract int CompareGroups(IAggregateResultProvider results, IGroup left, IGroup right, PivotAxis axis);

The parameters provide the following information:

  • results—Gives access to the current aggregate values through GetAggregateResult and the root groups through the Root property.
  • left and right—The two IGroup instances being compared. Each group exposes a Name property (the group key), a Level, and a reference to its Parent group.
  • axis—Indicates whether the groups belong to PivotAxis.Rows or PivotAxis.Columns.

The method returns a signed integer following the standard comparison contract—a negative value when left precedes right, zero when they are equal, and a positive value when left follows right.

Because GroupComparer inherits from Cloneable, you must also override CreateInstanceCore and CloneCore.

Creating a Custom GroupComparer

The following example demonstrates a custom comparer that orders groups by the length of their names. Groups with shorter names appear first.

Defining the Custom Comparer

C#
public class NameLengthComparer : GroupComparer
{
    public override int CompareGroups(IAggregateResultProvider results, IGroup left, IGroup right, PivotAxis axis)
    {
        string leftName = left.Name != null ? left.Name.ToString() : string.Empty;
        string rightName = right.Name != null ? right.Name.ToString() : string.Empty;

        return leftName.Length.CompareTo(rightName.Length);
    }

    protected override Cloneable CreateInstanceCore()
    {
        return new NameLengthComparer();
    }

    protected override void CloneCore(Cloneable source)
    {
    }
}

Assigning the Custom Comparer

Set the GroupComparer property on the target PropertyGroupDescription or DateTimeGroupDescription. The SortOrder property controls whether the comparer result is applied in ascending or descending direction.

Using the Custom Comparer

C#
PropertyGroupDescription groupDescription = (PropertyGroupDescription)this.radPivotGrid1.RowGroupDescriptions[0];
groupDescription.GroupComparer = new NameLengthComparer();
groupDescription.SortOrder = Telerik.Pivot.Core.SortOrder.Ascending;
this.radPivotGrid1.ReloadData();

Using Aggregate Values in a Custom Comparer

A custom comparer can also use aggregate results. The following example compares groups based on the aggregate value at a specific index, falling back to group name comparison when the aggregate values are equal.

Defining a Comparer with Aggregate Access

C#
public class AggregateWithFallbackComparer : GroupComparer
{
    private int aggregateIndex;

    public int AggregateIndex
    {
        get { return this.aggregateIndex; }
        set
        {
            if (this.aggregateIndex != value)
            {
                this.aggregateIndex = value;
                this.OnPropertyChanged("AggregateIndex");
            }
        }
    }

    public override int CompareGroups(IAggregateResultProvider results, IGroup left, IGroup right, PivotAxis axis)
    {
        Coordinate leftCoordinate;
        Coordinate rightCoordinate;

        if (axis == PivotAxis.Rows)
        {
            leftCoordinate = new Coordinate(left, results.Root.ColumnGroup);
            rightCoordinate = new Coordinate(right, results.Root.ColumnGroup);
        }
        else
        {
            leftCoordinate = new Coordinate(results.Root.RowGroup, left);
            rightCoordinate = new Coordinate(results.Root.RowGroup, right);
        }

        AggregateValue leftAggregate = results.GetAggregateResult(this.AggregateIndex, leftCoordinate);
        AggregateValue rightAggregate = results.GetAggregateResult(this.AggregateIndex, rightCoordinate);

        object leftValue = leftAggregate != null ? leftAggregate.GetValue() : null;
        object rightValue = rightAggregate != null ? rightAggregate.GetValue() : null;

        if (leftValue is AggregateError)
        {
            leftValue = null;
        }

        if (rightValue is AggregateError)
        {
            rightValue = null;
        }

        int result = System.Collections.Comparer.Default.Compare(leftValue, rightValue);

        if (result == 0)
        {
            IComparable leftName = left.Name as IComparable;
            if (leftName != null && right.Name != null)
            {
                return leftName.CompareTo(right.Name);
            }
        }

        return result;
    }

    protected override Cloneable CreateInstanceCore()
    {
        return new AggregateWithFallbackComparer();
    }

    protected override void CloneCore(Cloneable source)
    {
        AggregateWithFallbackComparer comparer = source as AggregateWithFallbackComparer;
        if (comparer != null)
        {
            this.AggregateIndex = comparer.AggregateIndex;
        }
    }
}

When the comparer exposes additional properties (such as AggregateIndex), copy their values in the CloneCore override. This ensures the comparer state is preserved when the pivot engine clones group descriptions internally.

See Also