Custom Aggregate Functions
The Telerik UI for WPF suite comes with its own data engine that is used in the RadGridView
control. The engine provides a set of predefined aggregate functions that can be applied over the data - count, sum, max, average, etc.
This article describes how to implement custom aggregate functions by inheriting the EnumerableAggregateFunction
EnumerableSelectorAggregateFunction
classes, and also the generic AggregateFunction<TElement, TResult>
class.
Implementing Custom EnumerableAggregateFunction
The EnumerableAggregateFunction
represents a function that uses aggregate extension methods to return a specific result. Examples of Telerik built-in functions using this class are - CountFunction
, FirstFunction
and LastFunction
.
To implement your own aggregate with custom behavior, inherit the EnumerableAggregateFunction
and override its AggregateMethodName
and ExtensionMethodsType
members.
The ExtensionMethodsType
tells the type of the static class that holds the methods used for aggregation.
The AggregateMethodName
returns the name of the aggregation method from the static class.
Implementing a static class that holds the aggregation functions
public static class ClubModelAggregates
{
public static string SumGoalsPerHalfSeason<TSource>(IEnumerable<Club> clubs)
{
StringBuilder sb = new StringBuilder();
double sum0 = 0, sum1 = 0;
foreach (var club in clubs)
{
if (club.Period != null )
{
sum0 += club.FirstHalfSeasonNumberOfGoals;
sum1 += club.SecondHalfSeasonNumberOfGoals;
}
}
sb.Append("1st Half Season Total Goals: ");
sb.Append(sum0);
sb.Append(" ");
sb.AppendLine();
sb.Append("2nd Half Season Total Goals: ");
sb.Append(sum1);
return sb.ToString();
}
}
Implementing a custom function that calculates the median of the items
public class CustomSumFunction : EnumerableAggregateFunction
{
protected override string AggregateMethodName
{
get { return nameof(ClubModelAggregates.SumGoalsPerHalfSeason); }
}
protected override Type ExtensionMethodsType
{
get
{
return typeof(ClubModelAggregates);
}
}
}
The Club model used to populate the ItemsSource of RadGridView
public class Club : ViewModelBase
{
// other properties here
private double firstHalfSeasonNumberOfGoals;
private double secondHalfSeasonNumberOfGoals;
public double FirstHalfSeasonNumberOfGoals
{
get { return this.firstHalfSeasonNumberOfGoals; }
set { this.firstHalfSeasonNumberOfGoals = value; this.OnPropertyChanged(nameof(FirstHalfSeasonNumberOfGoals)); }
}
public double SecondHalfSeasonNumberOfGoals
{
get { return this.secondHalfSeasonNumberOfGoals; }
set { this.secondHalfSeasonNumberOfGoals = value; this.OnPropertyChanged(nameof(SecondHalfSeasonNumberOfGoals)); }
}
public static ObservableCollection<Club> GetClubs()
{
return new ObservableCollection<Club>()
{
new Club() { FirstHalfSeasonNumberOfGoals = 44, SecondHalfSeasonNumberOfGoals = 39, /* other settings here / },
new Club() { FirstHalfSeasonNumberOfGoals = 52, SecondHalfSeasonNumberOfGoals = 47, / other settings here / },
new Club() { FirstHalfSeasonNumberOfGoals = 29, SecondHalfSeasonNumberOfGoals = 51, / other settings here / },
new Club() { FirstHalfSeasonNumberOfGoals = 33, SecondHalfSeasonNumberOfGoals = 49, / other settings here */ },
};
}
}
Adding the function in the AggregateFunctions collection of RadGridView's columns
<telerik:GridViewDataColumn DataMemberBinding="{Binding MyProperty}">
<telerik:GridViewDataColumn.AggregateFunctions>
<local:CustomSumFunction />
</telerik:GridViewDataColumn.AggregateFunctions>
</telerik:GridViewDataColumn>
Implementing Custom EnumerableSelectorAggregateFunction
The EnumerableSelectorAggregateFunction
represents a function that uses a specific field from the data items in order to apply calculations over the data set. Examples of Telerik built-in functions using this class are - AverageFunction
, MaxFunction
, MinFunction
, SumFunction
.
To implement your own aggregate with custom behavior, inherit the EnumerableSelectorAggregateFunction
and override its AggregateMethodName
and ExtensionMethodsType
members.
The ExtensionMethodsType
tells the type of the static class that holds the methods used for aggregation.
The AggregateMethodName
returns the name of the aggregation method from the static class.
Implementing a static class that holds the aggregation functions
public static class MedianAggregates
{
// Invoked when the SourceFiled of the AggregateFunction points to a property which Type is System.Int32
public static object GetMedian<TSource>(IEnumerable<TSource> items, Func<TSource, int> selector)
{
IEnumerable<int> selectedValues = items.Select(selector).Cast<int>();
return GetMedian(selectedValues.ToArray());
}
// Invoked when the SourceFiled of the AggregateFunction points to a property which Type is System.Double
public static object GetMedian<TSource>(IEnumerable<TSource> items, Func<TSource, double> selector)
{
IEnumerable<double> selectedValues = items.Select(selector).Cast<double>();
return GetMedian(selectedValues.ToArray());
}
// Invoked when the SourceFiled of the AggregateFunction points to a property which Type is System.Decimal
public static object GetMedian<TSource>(IEnumerable<TSource> items, Func<TSource, decimal> selector)
{
IEnumerable<decimal> selectedValues = items.Select(selector).Cast<decimal>();
return GetMedian(selectedValues.ToArray());
}
// Invoked in the following situations:
// - when the SourceFiled of the AggregateFunction is not set
// - when the corresponding data type is not supported (non numeric type)
// - when no method overload for the corresponding Type is defined in this class
public static object GetMedian<TSource, TProperty>(IEnumerable<TSource> items, Func<TSource, TProperty> selector)
{
throw new ArgumentException(string.Format("Please set the SourceField property of the {0} object. The property Type should be numeric (int, double, etc.)", nameof(MedianFunction)));
// or implement custom logic that fetches whatever values you need
}
private static double GetMedian(int[] items)
{
Array.Sort(items);
int mid = items.Length / 2;
if (items.Length % 2 != 0)
{
return items[mid];
}
return items.Length == 2 ? (items[0] + items[1]) / 2d : (items[mid - 1] + items[mid]) / 2d;
}
private static double GetMedian(double[] items)
{
Array.Sort(items);
int mid = items.Length / 2;
if (items.Length % 2 != 0)
{
return items[mid];
}
return items.Length == 2 ? (items[0] + items[1]) / 2d : (items[mid - 1] + items[mid]) / 2d;
}
private static decimal GetMedian(decimal[] items)
{
Array.Sort(items);
int mid = items.Length / 2;
if (items.Length % 2 != 0)
{
return items[mid];
}
return items.Length == 2 ? (items[0] + items[1]) / 2m : (items[mid - 1] + items[mid]) / 2m;
}
}
Implementing a custom function that calculates the median of the items
public class MedianFunction : EnumerableSelectorAggregateFunction
{
protected override string AggregateMethodName
{
get { return nameof(MedianAggregates.GetMedian); }
}
protected override Type ExtensionMethodsType
{
get
{
return typeof(MedianAggregates);
}
}
}
Adding the function in the AggregateFunctions collection of RadGridView's columns
<telerik:GridViewDataColumn DataMemberBinding="{Binding Number}">
<telerik:GridViewDataColumn.AggregateFunctions>
<local:MedianFunction SourceField="Number"/>
</telerik:GridViewDataColumn.AggregateFunctions>
</telerik:GridViewDataColumn>
Using Generic Aggregate Function
To implement a generic aggregate function, you can use the AggregateFunction<TElement, TResult>
class and set its AggregationExpression
property.
Defining custom aggregate function
var customSumFunction = new AggregateFunction<Club, int>()
{
AggregationExpression = clubs => clubs.Select(x => x.StadiumCapacity).Sum()
};
this.gridView.Columns[0].AggregateFunctions.Add(customSumFunction);