Thursday, December 7, 2017

Footer grid with custom aggregates

Developing on my DataGrid with footers custom control at http://wpfthoughts.blogspot.com/2017/11/editable-datagrid-with-footers-as.html I decided to implement custom aggregates. Basically this is a way to allow the grid's host to implement custom aggregates by allowing the grid to ask the host what the footer's value should be.

The obvious way to implement this is to create a event the control can raise when it sees a column's aggregate is custom. The page can subscribe to the event and provide a value.

The custom aggregate will be triggered when the column's aggregate has the form "CUSTOM(stringvalue)". The event will use a custom event args that includes an Aggregate property that will contain stringvalue. The event will pass a reference to the grid in the sender argument.

If you follow through the blog entry referenced above you will end up with a solution that displays the page below.


We will alter the aggregate for the CanRead and CanWrite columns to display the text "Found" in the appropriate column footer if any properties can be read or written.

Let's start by defining our custom event args in FooterGrid.cs before the definition of the FooterGrid class.

namespace FooterGrid
{
    public class FooterValueArgs : EventArgs
    {
        public string Aggregate { get; set; }
        public FooterValueArgs(String Aggregate)
        {
            this.Aggregate = Aggregate;
        }
    }

    public class FooterGrid : DataGrid



We need a delegate because we want the event to return the value to be displayed. We could pass this value back in the custom event argument, but I prefer the approach I'm taking here. Let's add that delegate, the event definition, and the event handler just after the declaration of the FooterGrid class.

    public class FooterGrid : DataGrid
    {
        public delegate String FooterValueEventHandler(object sender, FooterValueArgs args);
        public event FooterValueEventHandler FooterValueEvent;
        public String OnFooterValueEvent(FooterValueArgs e)
        {
            if (FooterValueEvent != null)
                return FooterValueEvent(this, e);
            else
                return "";
        }


Now our event is defined we just have to raise it when the column's aggregate starts with CUSTOM. Find CalcFooterItemSource and add a new case. The Column variable simply contains the part of the aggregate between parentheses. ie if the aggregate is "CUSTOM(Age)" then Column contains "Age".

case "CUSTOM":
    sResult = OnFooterValueEvent(new FooterValueArgs(Column));
    break;


That's all we have to do to enhance the FooterGrid. Now we need to consume this new feature.

In MainWindow.xaml we need to tell the footer grid which event handler will process custom aggregates and also create a custom aggregate. Make the following changes...

<cc:FooterGrid AutoGenerateColumns="False" ItemsSource="{Binding PIs}" IsReadOnly="False" HeadersVisibility="Column" ColumnHeaderStyle="{StaticResource Header}" cc:FooterGrid.ColumnFooterStyle="{StaticResource Footer}" FooterValueEvent="FooterGrid_FooterValueEvent">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
        <DataGridTextColumn Header="Type" Binding="{Binding Type}" cc:FooterGrid.Aggregate="Total Custom"/>
        <DataGridTextColumn Header="Custom Attributes" Binding="{Binding CustomAttributeCount}" cc:FooterGrid.Aggregate="SUM(CustomAttributeCount)">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="HorizontalAlignment" Value="Right"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridCheckBoxColumn Header="CanRead" Binding="{Binding CanRead}" cc:FooterGrid.Aggregate="CUSTOM(CanRead)"/>
        <DataGridCheckBoxColumn Header="CanWrite" Binding="{Binding CanWrite}" cc:FooterGrid.Aggregate="CUSTOM(CanWrite)"/>
    </DataGrid.Columns>
</cc:FooterGrid>

Finally we have to write FooterGrid_FooterValueEvent in MainWindow.xaml.cs. Just add the following method in the class.

private string FooterGrid_FooterValueEvent(object sender, FooterGrid.FooterValueArgs args)
{
    FooterGrid.FooterGrid fg = sender as FooterGrid.FooterGrid;
    switch(args.Aggregate.ToUpper())
    {
        case "CANREAD":
            return (fg.ItemsSource as List<cPI>).Find((pi) => pi.CanRead) == null ? "" : "Found";
        case "CANWRITE":
            return (fg.ItemsSource as List<cPI>).Find((pi) => pi.CanWrite) == null ? "" : "Found";
        default:
            return "";
    }
}


Because the entire footer grid is passed as the sender, you can get access to the main grid and footer grid via their template names xMain and xFooter. You can then get to the grids' item sources. The only thing you can't get to is footer values to the right of the footer you are processing because they have not been calculated yet.

The result of our work looks like this.


No comments:

Post a Comment