Friday, June 11, 2021

Weird requirement, cool solution

The Infragistics XamDataGrid has some powerful Summary features which allow you to use common or custom aggregate features and format and display them in various locations in the grid. But I thought one of our users had defeated them with this request.

"I want to total all the values in a child band and display the result under a totally unrelated column in the parent band".

There are two problems with this requirement..

  1. We want to display data from one band in the summary for a different band
  2. We want to display data from one column in the summary for a different column
We can solve the first problem by adding a read-only property to the parent data source that returns the sum of the values from its children. When we sum those values we will have the value we want to display.

We can solve the second problem by using the PositionFieldName property of the SummaryDefinition.

Create a new Visual Studio solution called ExternalSummaryCalculation. It will be a WPF Application (Code) using C#. Make the MainWindow.xaml look like this. You can see we simply create two bands in the "normal" XamDataGrid way. The top band has a hidden field that contains the total of the child values for each parent row. It also has a SummaryDefinition that sums this hidden column and puts the results under the Description column.

<Window x:Class="ExternalSummaryCalculation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:igDP="http://infragistics.com/DataPresenter"
        xmlns:local="clr-namespace:ExternalSummaryCalculation"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <igDP:XamDataGrid DataSource="{Binding Items}">
        <igDP:XamDataGrid.FieldLayoutSettings>
            <igDP:FieldLayoutSettings AutoGenerateFields="False"/>
        </igDP:XamDataGrid.FieldLayoutSettings>
        <igDP:XamDataGrid.FieldSettings>
            <igDP:FieldSettings SummaryDisplayArea="BottomFixed" AllowSummaries="False" SummaryUIType="SingleSelect"/>
        </igDP:XamDataGrid.FieldSettings>
        <igDP:XamDataGrid.FieldLayouts>
            <igDP:FieldLayout Key="Parent">
                <igDP:TextField Label="Description" Name="Description"/>
                <igDP:NumericField Name="ChildTotalValue" Visibility="Collapsed"/>
                <igDP:Field Name="Children"/>
                <igDP:FieldLayout.SummaryDefinitions>
                    <igDP:SummaryDefinition SourceFieldName="ChildTotalValue" Calculator="Sum" PositionFieldName="Description" />
                </igDP:FieldLayout.SummaryDefinitions>
            </igDP:FieldLayout>
            <igDP:FieldLayout Key="Child" ParentFieldName="Children" ParentFieldLayoutKey="Parent">
                <igDP:NumericField Name="Value"/>
            </igDP:FieldLayout>
        </igDP:XamDataGrid.FieldLayouts>
    </igDP:XamDataGrid>
</Window>

The MainWindow.xaml.vb code except that changes to cChild.value (as the user edits) need to cause INotifyPropertyChanged to be raised for the parent's TotalChildValue property. One way to do this is to hold a reference to the parent in the child. This is not considered good practice but is pragmatic. Another option would be to have the MainWindow implement a method that can do this via reflection but that would over-complicate this example.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
 
namespace ExternalSummaryCalculation
{
    public partial class MainWindow : Window
    {
        public class cParent : INotifyPropertyChanged
        {
            public string Description { get; set; }
            public decimal ChildTotalValue
            {
                get { return Children.Sum(c => c.Value); }
            }
            public List<cChild> Children { get; set; } = new List<cChild>();
 
            public event PropertyChangedEventHandler PropertyChanged;
            public void NotifyPropertyChanged(String name)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
 
            // This method is here to simplify initialization - you won't need it
            public cParent AddChild(decimal value)
            {
                Children.Add(new cChild(this, value));
                return this;
            }
        }
 
        public class cChild
        {
            public cChild(cParent parent, decimal value)
            {
                this.parent = parent;
                this.Value = value;
            }
 
            private cParent parent;
 
            private decimal _Value = 0;
            public decimal Value
            {
                get { return _Value; }
                set
                {
                    _Value = value;
                    parent.NotifyPropertyChanged("ChildTotalValue");
                }
            } 
        }
 
        public List<cParent> Items { get; set; } = new List<cParent>()
        {
            new cParent() {Description="Parent 1" }.AddChild(1).AddChild(2),
            new cParent() {Description="Parent 2" }.AddChild(3).AddChild(4)
        };
 
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

If you run the application and expand the rows you can see the Summary working.


If you modify one of the values you can see the total being recalculated via the line parent.NotifyPropertyChanged("ChildTotalValue");



No comments:

Post a Comment