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;

 }

}