Thursday, August 9, 2018

XamDataGrid different headings on child grids

This post is based on a posting on the Infragistics forum.

This requirement is to have different headers on different child grids of a XamDataGrid. It's a difficult requirement to meet for any data bound grid and the XamDataGrid solution is no worse or better than other grids I've done this for.

In this scenario I have a grid of financial documents and each document has multiple financial accounts. Each document uses one of two account structures. Each account structure has multiple account sections, each of which has it's own name. When we are displaying accounts for a document that uses account structure 1, we must use labels for account structure 1.

I define two account structures each of which contains a list of section headings. I also define two documents that use these account structures and an account for each document.

Start a new WPF project and call it XamDataGridAltBindings.


Let's start with simple XAML that does not have bound labels for the child rows. We'll add them later.


<Window x:Class="XamDataGridAltBindings.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:local="clr-namespace:XamDataGridAltBindings"
    xmlns:igDP="http://infragistics.com/DataPresenter"
        mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <FrameworkElement x:Key="ProxyElement"/>
        <local:AccountStructureConverter x:Key="AccountStructureConverter"/>
    </Window.Resources>
    <Grid>
        <ContentControl Visibility="Collapsed" Content="{StaticResource ProxyElement}"/>
        <igDP:XamDataGrid DataSource="{Binding Documents}">
            <igDP:XamDataGrid.FieldLayoutSettings>
                <igDP:FieldLayoutSettings AutoGenerateFields="False" ExpansionIndicatorDisplayMode="Always"/>
            </igDP:XamDataGrid.FieldLayoutSettings>
            <igDP:XamDataGrid.FieldLayouts>
                <igDP:FieldLayout Key="Documents">
                    <igDP:FieldLayout.Fields>
                        <igDP:TextField Label="Document" Name="DocumentName" Width="*"/>
                        <igDP:Field Label="Accounts" Name="Accounts"/>
                    </igDP:FieldLayout.Fields>
                </igDP:FieldLayout>
                <igDP:FieldLayout ParentFieldLayoutKey="Documents" ParentFieldName="Accounts">
                    <igDP:FieldLayout.Settings>
                        <igDP:FieldLayoutSettings AutoGenerateFields="False" ExpansionIndicatorDisplayMode="Never"/>
                    </igDP:FieldLayout.Settings>
                    <igDP:FieldLayout.Fields>
                        <igDP:TextField Name="Value1"/>
                    </igDP:FieldLayout.Fields>
                </igDP:FieldLayout>
            </igDP:XamDataGrid.FieldLayouts>
        </igDP:XamDataGrid>
    </Grid>
</Window>

Here's the code behind. I'm not using INotifyPropertyChanged so I can focus on the binding.


using System;
using System.Collections.Generic;
using System.Windows;

namespace XamDataGridAltBindings
{
    public partial class MainWindow : Window
    {
        public class cDocument
        {
            public String DocumentName { get; set; }
            public cAccountStructure AccountStructure { get; set; }
            public List<cAccount> Accounts { get; set; }
            public cDocument(String DocumentName, cAccountStructure AccountStructure)
            {
                this.DocumentName = DocumentName;
                this.AccountStructure = AccountStructure;
                this.Accounts = new List<cAccount>();
            }
        }

        public class cAccountStructure
        {
            public String Name { get; set; }
            public List<String> Heading { get; set; }
            public cAccountStructure(String Name, List<String> Heading)
            {
                this.Name = Name;
                this.Heading = Heading;
            }
        }

        public class cAccount
        {
            public String Value1 { get; set; }
            public String Value2 { get; set; }
            public String Value3 { get; set; }

            public cAccount(String Value1, String Value2, String Value3)
            {
                this.Value1 = Value1;
                this.Value2 = Value2;
                this.Value3 = Value3;
            }
        }

        public List<cDocument> Documents { get; set; }
        public cAccountStructure AccountStructure1;
        public cAccountStructure AccountStructure2;

        public MainWindow()
        {
            AccountStructure1 = new cAccountStructure("K12",new List<String> { "Fu","Rs","Y" });
            AccountStructure2 = new cAccountStructure("College", new List<String> { "Fu","Ls","Si" });

            Documents = new List<cDocument>() { new cDocument("Document 1 K12", AccountStructure1) , new cDocument("Document 2 College", AccountStructure2) };
            Documents[0].Accounts.Add(new cAccount("01", "0000", "0"));
            Documents[1].Accounts.Add(new cAccount("12", "20", "9022"));
            InitializeComponent();
        }
    }
}

At this point we can see the default label for a column is the path - which is an interesting decision on the part of Infragistics.

The default column label is the path of the column.
Now we need to write some XAML to dig down into the LabelPresenter. Replace the XAML line

<igDP:TextField Name="Value1"/>

with this XAML that allows us to bind the label explicitly...


<igDP:TextField Name="Value1">
    <igDP:TextField.Settings>
        <igDP:FieldSettings>
            <igDP:FieldSettings.LabelPresenterStyle>
                <Style TargetType="igDP:LabelPresenter">
                    <Setter Property="ContentTemplate">
                         <Setter.Value>
                             <DataTemplate>
                                 <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type igDP:LabelPresenter}}, Path=(igDP:HeaderPresenter.RecordManager).ParentDataRecord, Converter={StaticResource AccountStructureConverter}, ConverterParameter=0}" />
                              </DataTemplate>
                          </Setter.Value>
                      </Setter>
                  </Style>
              </igDP:FieldSettings.LabelPresenterStyle>
          </igDP:FieldSettings>
      </igDP:TextField.Settings>
 </igDP:TextField>

We pass the parent's data record and a parameter to a converter and display whatever is returned as the label of the column. Add a class to the project called "Converters".


Now replace the code with this. The DataRecord passed to us contains our Document in its DataItem property. Our Document contains a reference to the AccountStructure for the document. We can use the parameter to find the correct label for the data column.

using Infragistics.Windows.DataPresenter;
using System;
using System.Windows.Data;
using static XamDataGridAltBindings.MainWindow;

namespace XamDataGridAltBindings
{
    public class AccountStructureConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value == null) return string.Empty;

            DataRecord parentRecord = value as DataRecord;
            cDocument Document = parentRecord.DataItem as cDocument;
            return Document.AccountStructure.Heading[int.Parse((String)parameter)];
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

When we run the solution now (with the other two account sections added) we see we have bound labels that differ from child to child.



No comments:

Post a Comment