Welcome to the Xceed Community | Help
Community Search  
More Search Options

Tech Side

It's All About the StatRows

Hey guys, in this topic, we will talk about StatRows. Yes that's right! I said StatRows. Although they are easy to implement, they can become tricky to play around with. In this lovely WPF world, we run into some unbelievable requests and features we need to implement with the DataGrid for WPF... I'm sure you all have other wonderful things to implement as well. From time to time, we get different questions about StatRows but there are always some that stick out like a sore thumb. And it is the sore thumbs that we want to fix since we well... don't want them to be sore again!

For my first part about StatRows, I want to talk about adding statistical functions to the GroupHeaderControl. This has become a popular topic where developers wish to have StatCells displayed in the GroupHeaderControl rather than the footers of the groups. Although there will have to be a sacrifice to give up the original look of the GroupHeaderControl, it can be done. The one thing to remember is that we are adding a StatRow with StatCells in them. In order to get fast and efficient results, we need to use them. This helps developers avoid coding exhaustive loops in a Converter that will return a string which will then be bound to the Text property of a TextBlock which in turn will be part of the GroupHeaderControl template

The first step would be to create a Style which targets the GroupHeaderControl. We will be changing the Template of the control to our own ControlTemplate which will be a Grid with a StatRow in there. So let's get the code ready so we can use it!

<Style x:Key="groupHeaderAndStatRowStyle"

                TargetType="{x:Type xcdg:GroupHeaderControl}">

 

            <!--Must set the GroupHeaderControl as scrollable horizontally -->

            <Setter Property="xcdg:TableView.CanScrollHorizontally"

                    Value="True" />

 

            <!-- Avoid transparency to allow Group Value clipping -->

            <Setter Property="Background"

                    Value="White" />

 

            <Setter Property="Template">

               <Setter.Value>

                  <ControlTemplate TargetType="xcdg:GroupHeaderControl">

                     <Grid>

                        <xcdg:StatRow x:Name="statRow"

                                      Background="{TemplateBinding Background}">

 

                           <xcdg:StatCell FieldName="PreviousClose"

                                          ResultPropertyName="PreviousCloseAverage" />

                           <xcdg:StatCell FieldName="Open"

                                          ResultPropertyName="OpenAverage" />

                           <xcdg:StatCell FieldName="Change"

                                          ResultPropertyName="ChangeAverage" />

                           <xcdg:StatCell FieldName="ChangeDiff"

                                          ResultPropertyName="ChangeDiffAverage" />

                           <xcdg:StatCell FieldName="LastTrade"

                                          ResultPropertyName="LastTradeAverage" />

                           <xcdg:StatCell FieldName="LastTradeDiff"

                                          ResultPropertyName="LastTradeDiffAverage" />

                        </xcdg:StatRow>

 

                        <!-- The PassiveLayoutDecorator must not scroll horizontally -->

                        <!-- In the original GroupHeaderControl template, the GroupHeaderControl

                             itself was not scrolling horizontally, now only its content should

                             not scroll to allow the StatRow to correctly scroll -->

                        <xcdg:PassiveLayoutDecorator Axis="Horizontal"

                                                     xcdg:TableView.CanScrollHorizontally="False">

                           <DockPanel>

                              <xcdg:HierarchicalGroupLevelIndicatorPane DockPanel.Dock="Left" />

                              <xcdg:GroupLevelIndicatorPane DockPanel.Dock="Left"

                                                            Indented="False"

                                                            xcdg:GroupLevelIndicatorPane.GroupLevel="{Binding

                                                            RelativeSource={RelativeSource TemplatedParent},

                                                            Path=(xcdg:GroupLevelIndicatorPane.GroupLevel),

                                                            Converter={StaticResource

                                                                            groupHeaderControlGroupLevelConverter},

                                                            ConverterParameter=-1}" />

                              <Border x:Name="mainBorder"

                                      BorderBrush="{TemplateBinding BorderBrush}"

                                      BorderThickness="{TemplateBinding BorderThickness}"

                                      Padding="{TemplateBinding Padding}"

                                      Focusable="True"

                                      FocusVisualStyle="{TemplateBinding FocusVisualStyle}">

                                 <Border.InputBindings>

                                    <KeyBinding Command="{x:Static xcdg:DataGridCommands.ToggleGroupExpansion}"

                                                Key="Space" />

                                    <KeyBinding Command="{x:Static xcdg:DataGridCommands.ExpandGroup}"

                                                Key="Right" />

                                    <KeyBinding Command="{x:Static xcdg:DataGridCommands.ExpandGroup}"

                                                Key="Add" />

                                    <KeyBinding Command="{x:Static xcdg:DataGridCommands.CollapseGroup}"

                                                Key="Left" />

                                    <KeyBinding Command="{x:Static xcdg:DataGridCommands.CollapseGroup}"

                                                Key="Subtract" />

                                    <MouseBinding

                                                Command="{x:Static xcdg:DataGridCommands.ToggleGroupExpansion}"

                                                MouseAction="LeftDoubleClick" />

                                 </Border.InputBindings>

                                 <DockPanel LastChildFill="False">

                                    <ToggleButton DockPanel.Dock="Left"

                                                  OverridesDefaultStyle="True"

                                                  Template="{StaticResource groupExpanderToggleButtonTemplate}"

                                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

                                                  Focusable="False"

                                                  IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Group.IsExpanded}" />

                                    <!-- ContentPresenter in charge of displaying this GroupHeaderControl's Content, which is a Group by default. -->

 

                                    <!-- Replaced Margin by Padding to ensure the border is all over the

                                         StatRow and clips the displayed values correctly -->

                                    <Border Background="{TemplateBinding Background}"

                                            DockPanel.Dock="Left"

                                            Padding="3,0,0,0">

                                       <ContentPresenter Content="{TemplateBinding Content}"

                                                         ContentTemplate="{TemplateBinding ContentTemplate}"

                                                         ContentTemplateSelector="{TemplateBinding

                                                               ContentTemplateSelector}"

                                                         VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

                                                         HorizontalAlignment="Left" />

                                    </Border>

                                 </DockPanel>

                              </Border>

                           </DockPanel>

                        </xcdg:PassiveLayoutDecorator>

 

                     </Grid>

                     <ControlTemplate.Triggers>

                        <Trigger Property="xcdg:DataGridControl.NavigationBehavior"

                                 Value="None">

                           <Setter TargetName="mainBorder"

                                   Property="Focusable"

                                   Value="False" />

                        </Trigger>

                     </ControlTemplate.Triggers>

                  </ControlTemplate>

               </Setter.Value>

            </Setter>

         </Style>

I had just wanted to mention that along with the StatRows being in the ControlTemplate, we are simply redoing the ControlTemplate of the GroupHeaderControl. Jenny (those of you who don't know her, she writes the "Don't Panic" Blog on our website) had posted a sample application that shows you how to achieve this. Everything but the StatRow elements in the above XAML was just copy/pasted from our templates for the GroupHeaderControl.

So far, this has been a great start where we now know how to add statistical functions to the GroupHeaderControl. So this brings me to my next and last topic (for this blog) about StatRows... Building custom statistical functions.

One interesting and popular statistic that developers usually seek is to display the average for a Column which has a TimeSpan data type. This is just a scenario that I picked, which I found became popular, but again, you can use this for any scenario where you need an average. If the AverageFunction is not sufficient enough for you, possibly because you have a certain data type where you wish to calculate it in another way, you can certainly use this as a reference.

The first thing we must do is create our own class, let's say ‘DurationAvgFunction' for simplicity. We must inherit from StatFunction and override the following members:

-          void Reset()

-          void Accumulate(object[] values) - leave blank since we will be using Prerequisites

-          bool RequiresAccumulation - return False since we will be using Prerequisites

-          StatFunctions[] PrerequisiteFunctions - ReadOnly

-          void InitializePrerequisites(StatResult[] prerequisiteValues)

-          StatResult GetResult()

The PrerequisiteFunctions property is extremely important and which it will require us to return an array with a Length of 2. The array will contain the sum of the duration (using our very own DurationSumFunction) as well as the count (the number of items required to calculate the average). We will return the value of a StatFunction array so later on we can extract the two values (sum and count).

protected override StatFunction[] PrerequisiteFunctions

{

    get

    {

       StatFunction[] prerequisites = m_prerequisites;

 

       if (prerequisites == null)

       {

         prerequisites = new StatFunction[]

         {

           new DurationSumFunction( "DurationAvgFunction.Sum", this.SourcePropertyName ),

           new CountFunction( "DurationAvgFunction.Count", this.SourcePropertyName )

         };

 

         if (this.IsSealed)

         {

           m_prerequisites = prerequisites;

         }

       }

    return prerequisites;

    }

}

In the InitializePrerequisites method, we will just initialize 2 local variables which will hold the values of the sum and count function.

protected  override  void InitializePrerequisites(StatResult[] prerequisiteValues)

{

  if (prerequisiteValues.Length != 2)

  {

     throw new InvalidOperationException("The prerequisites initializers do not match the PrerequisiteFunctions");

  }

  else

  {

     durationSum = (TimeSpan)prerequisiteValues[0].Value;

     durationCount = (long)prerequisiteValues[1].Value;

  }

}

The next important piece is the GetResult function which will return the StatResult. All we need to do here is to return the Average (sum / count) as a StatResult.

protected override StatResult GetResult()

{

  TimeSpan durationAvg = new TimeSpan();

 

  if (durationCount != null && durationSum != null)

  {

     if ((long)durationCount > 0)

     {

        durationAvg = TimeSpan.FromSeconds(((TimeSpan)durationSum).TotalSeconds / (long)durationCount);

     }

  }

 

  return new StatResult(durationAvg);

}

As we always do, here are the links to the sample applications that I used to test the above code. Didn't think I could actually do this with my eyes closed did you?

Prerequisite Functions
StatRow in GroupHeaderControl

I hope you enjoyed this one, but if you haven't, then I hope it helped you in some way. I will conclude this post by saying "thank you!" to all the readers and for your support! Marc - out.

Published December 7, 2011 4:29 PM by Marc [Xceed]

Comments

No Comments
Anonymous comments are disabled
Contact | Site Map | Reviews | Legal Terms of Use | Trademarks | Privacy Statement Copyright 2011 Xceed Software Inc.