Xceed DataGrid for WPF v7.2 Documentation
Custom statistical function

The following example demonstrates how to create a custom statistical function based on the CountFunction, which will only count the items if they match the specified conditions.

XAML
Copy Code
<Grid xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid"
      xmlns:local="clr-namespace:Xceed.Wpf.Documentation"
      xmlns:s="clr-namespace:System;assembly=mscorlib">
  <Grid.Resources>
     <xcdg:DataGridCollectionViewSource x:Key="cvs_products"
                                        Source="{Binding Source={x:Static Application.Current}, Path=Products}">
        <xcdg:DataGridCollectionViewSource.StatFunctions>
          <local:CountIfFunction ResultPropertyName="CountProductsToOrder"
                                 SourcePropertyName="ReorderLevel,UnitsOnOrder">
             <!-- Only count products which have a ReorderLevel of 5, 10, 15, or 20, and
                  a UnitsOnOrder value of 0. -->
             <local:CountIfFunction.Conditions>
                <s:String>^5$|^10$|^15$|^20$</s:String>
                <s:String>^0$</s:String>
             </local:CountIfFunction.Conditions>
          </local:CountIfFunction>
        </xcdg:DataGridCollectionViewSource.StatFunctions>
        <xcdg:DataGridCollectionViewSource.GroupDescriptions>
           <xcdg:DataGridGroupDescription PropertyName="CategoryID" />
        </xcdg:DataGridCollectionViewSource.GroupDescriptions>
     </xcdg:DataGridCollectionViewSource>
  </Grid.Resources>
  <xcdg:DataGridControl x:Name="OrdersGrid"
                        ItemsSource="{Binding Source={StaticResource cvs_products}}">
     <xcdg:DataGridControl.Columns>
        <xcdg:Column FieldName="ReorderLevel"/>
     </xcdg:DataGridControl.Columns>
     <xcdg:DataGridControl.DefaultGroupConfiguration>
        <xcdg:GroupConfiguration>
           <xcdg:GroupConfiguration.Footers>
              <DataTemplate>
                <xcdg:StatRow Background="Pink">
                   <xcdg:StatCell FieldName="ReorderLevel"
                                  ResultPropertyName="CountProductsToOrder" />                      
                </xcdg:StatRow>  
            </DataTemplate>
           </xcdg:GroupConfiguration.Footers>
        </xcdg:GroupConfiguration>
     </xcdg:DataGridControl.DefaultGroupConfiguration>
  </xcdg:DataGridControl>
</Grid>
VB.NET
Copy Code
Imports System
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized
Imports System.Text.RegularExpressions
Imports Xceed.Wpf.DataGrid.Stats
Namespace Xceed.Wpf.Documentation
  ' This statistical function derives from CumulativeStatFunction because it can
  ' accumulate "partial" results. For instance, results of sub-group. This allows
  ' for better performance.
  Public Class CountIfFunction
    Inherits CumulativeStatFunction
    ' A parameterless constructor is necessary to use the class in XAML.
    Public Sub New()
      MyBase.New()
      m_conditions = New ObservableCollection(Of String)()
      AddHandler m_conditions.CollectionChanged, AddressOf m_conditions_CollectionChanged
    End Sub
    ' Initialize a new instance of the CountIfFunction specifying the ResultPropertyName
    ' and the SourcePropertyName.
    Public Sub New(ByVal resultPropertyName As String, ByVal sourcePropertyNames As String)
      MyBase.New(resultPropertyName, sourcePropertyNames)
      m_conditions = New ObservableCollection(Of String)()
      AddHandler m_conditions.CollectionChanged, AddressOf m_conditions_CollectionChanged
    End Sub
    ' Each condition applies to the values of the corresponding source property name
    ' (e.g., the first condition applies to the values of the first source property name).
    ' Gets the conditions that will be applied to the various values.
    Public ReadOnly Property Conditions() As ObservableCollection(Of String)
      Get
        Return m_conditions
      End Get
    End Property
    ' When the grid needs to create temporary CountIfFunction instances for its
    ' calculation, this method will be called. Be sure to initialize everything
    ' having an impact on the result here.
    Protected Overrides Sub InitializeFromInstance(ByVal source As StatFunction)
      MyBase.InitializeFromInstance(source)
      For Each condition As String In (CType(source, CountIfFunction)).Conditions
        Me.Conditions.Add(condition)
      Next condition
    End Sub
    ' Validate the CountIf statistical function to make sure that it is capable
    ' to calculate its result. In our case, we need to make sure that a ResultPropertyName
    ' has been specified and that we have the same number of source property names
    ' as conditions.
    Protected Overrides Sub Validate()
      If (Me.ResultPropertyName Is Nothing) OrElse
         (Me.ResultPropertyName = String.Empty) OrElse
         (m_conditions.Count <> Me.SourcePropertyName.Split(","c).Length) Then
        Throw New InvalidOperationException()
      End If
    End Sub
    ' This method will be called when a new calculation is about to begin.
    Protected Overrides Sub Reset()
      m_count = 0
    End Sub
    ' This method will be called for each data item that is part of the set (a group or
    ' the grid).
    Protected Overrides Sub Accumulate(ByVal values As Object())
      Dim i As Integer = 0
      Do While i < m_conditions.Count
        ' As soon as one condition does not match is associated value, we simply
        ' return without having done the accumulation (the count increment).
        If (Not Regex.IsMatch(values(i).ToString(), m_conditions(i))) Then
          Return
        End If
        i += 1
      Loop
      ' The compilation configuration will cause this line to throw
      ' if an OverflowException occurs.
      m_count += 1
    End Sub
    ' This method will be called when calculating the result of a group having
    ' sub-groups. Obviously, it will be called once for each sub-group.
    Protected Overrides Sub AccumulateChildResult(ByVal childResult As StatResult)
      m_count += Convert.ToInt64(childResult.Value)
    End Sub
    ' This method should return the result calculated so far.
    Protected Overrides Function GetResult() As StatResult
      Return New StatResult(m_count)
    End Function
    ' The addition of the Conditions property, which influences the result of the
    ' statistical function, the CountIf function requires us to override IsEquivalent
    ' and GetEquivalenceKey to return a new key when 2 instances are compared.
    Protected Overrides Function IsEquivalent(ByVal statFunction As StatFunction) As Boolean
      Dim countIfFunction As CountIfFunction = TryCast(statFunction, CountIfFunction)
      If countIfFunction Is Nothing Then
        Return False
      End If
      If m_conditions.Count <> countIfFunction.Conditions.Count Then
        Return False
      End If
      Dim i As Integer = 0
      Do While i < m_conditions.Count
        If m_conditions(i) <> countIfFunction.Conditions(i) Then
          Return False
        End If
        i += 1
      Loop
      Return MyBase.IsEquivalent(statFunction)
    End Function
    Protected Overrides Function GetEquivalenceKey() As Integer
      Dim hashCode As Integer = MyBase.GetEquivalenceKey()
      Dim i As Integer = 0
      Do While i < m_conditions.Count
        hashCode = hashCode Xor m_conditions(i).GetHashCode()
        i += 1
      Loop
      Return hashCode
    End Function
    ' Do not allow the Conditions property to be changed if the statistical function has
    ' been sealed (i.e, assigned to the DataGridCollectionView.StatFunctions property).
    Private Sub m_conditions_CollectionChanged(ByVal sender As Object, ByVal e As NotifyCollectionChangedEventArgs)
      Me.CheckSealed()
    End Sub
    Private m_conditions As ObservableCollection(Of String)
    Private m_count As Long
  End Class
End Namespace
C#
Copy Code
using System;
using Xceed.Wpf.DataGrid.Stats;
using System.Text.RegularExpressions;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace Xceed.Wpf.Documentation
{
 // This statistical function derives from CumulativeStatFunction because it can
 // accumulate "partial" results. For instance, results of sub-group. This allows
 // for better performance.
 public class CountIfFunction : CumulativeStatFunction
 {
   // A parameterless constructor is necessary to use the class in XAML.
   public CountIfFunction()
     : base()
   {
     m_conditions = new ObservableCollection<string>();
     m_conditions.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler( m_conditions_CollectionChanged );
   }
   // Initialize a new instance of the CountIfFunction specifying the ResultPropertyName
   // and the SourcePropertyName.
   public CountIfFunction( string resultPropertyName, string sourcePropertyNames )
     : base( resultPropertyName, sourcePropertyNames )
   {
     m_conditions = new ObservableCollection<string>();
     m_conditions.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler( m_conditions_CollectionChanged );
   }
   // Each condition applies to the values of the corresponding source property name
   // (e.g., the first condition applies to the values of the first source property name).
   // Gets the conditions that will be applied to the various values.
   public ObservableCollection<string> Conditions
   {
     get
     {
       return m_conditions;
     }
   }
   // When the grid needs to create temporary CountIfFunction instances for its
   // calculation, this method will be called. Be sure to initialize everything
   // having an impact on the result here.
   protected override void InitializeFromInstance( StatFunction source )
   {
     base.InitializeFromInstance( source );
     foreach( string condition in ( ( CountIfFunction )source ).Conditions )
       this.Conditions.Add( condition );
   }
   // Validate the CountIf statistical function to make sure that it is capable
   // to calculate its result. In our case, we need to make sure that a ResultPropertyName
   // has been specified and that we have the same number of source property names
   // as conditions.
   protected override void Validate()
   {
     if( ( string.IsNullOrEmpty( ResultPropertyName ) ) ||
         ( m_conditions.Count != this.SourcePropertyName.Split( ',' ).Length ) )
     {
       throw new InvalidOperationException();
     }
   }
   // This method will be called when a new calculation is about to begin.
   protected override void Reset()
   {
     m_count = 0;
   }
   // This method will be called for each data item that is part of the set (a group or
   // the grid).
   protected override void Accumulate( object[] values )
   {
     for( int i = 0; i < m_conditions.Count; i++ )
     {
       // As soon as one condition does not match is associated value, we simply
       // return without having done the accumulation (the count increment).
       if( !Regex.IsMatch( values[ i ].ToString(), m_conditions[ i ] ) )
         return;
     }
     // In case of an overflow, we want to stop the calculation and report the error.
     checked
     {
       m_count++;
     }
   }
   // This method will be called when calculating the result of a group having
   // sub-groups. Obviously, it will be called once for each sub-group.
   protected override void AccumulateChildResult( StatResult childResult )
   {
     checked
     {
       m_count += Convert.ToInt64( childResult.Value );
     }
   }
   // This method should return the result calculated so far.
   protected override StatResult GetResult()
   {
     return new StatResult( m_count );
   }
   // The addition of the Conditions property, which influences the result of the
   // statistical function, the CountIf function requires us to override IsEquivalent
   // and GetEquivalenceKey to return a new key when 2 instances are compared.
   protected override bool IsEquivalent( StatFunction statFunction )
   {
     CountIfFunction countIfFunction = statFunction as CountIfFunction;
     if( countIfFunction == null )
       return false;
     if( m_conditions.Count != countIfFunction.Conditions.Count )
       return false;
     for( int i = 0; i < m_conditions.Count; i++ )
     {
       if( m_conditions[ i ] != countIfFunction.Conditions[ i ] )
         return false;
     }
     return base.IsEquivalent( statFunction );
   }
   protected override int GetEquivalenceKey()
   {
     int hashCode = base.GetEquivalenceKey();
     for( int i = 0; i < m_conditions.Count; i++ )
       hashCode ^= m_conditions[ i ].GetHashCode();
     return hashCode;
   }
   // Do not allow the Conditions property to be changed if the statistical function has
   // been sealed (i.e., assigned to the DataGridCollectionView.StatFunctions property).
   private void m_conditions_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
   {
     this.CheckSealed();
   }
   private ObservableCollection<string> m_conditions;
   private long m_count;
 }
}