This is a migrated thread and some comments may be shown as answers.

Grid Group Header - GetChildItems returning inconsistent results

3 Answers 147 Views
Grid
This is a migrated thread and some comments may be shown as answers.
tlp
Top achievements
Rank 1
tlp asked on 22 Jul 2014, 08:01 PM
Hello,

I have a grid in which I'm generating multi-line grand and group totals using the grid PreRender event. Initially, both the root header and footer grid items are obtained using the following::

GridItem[] rootHeaderItemArray = this.MasterTableView.GetItems(GridItemType.GroupHeader).Where(item => !item.GroupIndex.Contains(GROUP_INDEX_DELIMITER)).ToArray();
            GridItem[] rootFooterItemArray = this.MasterTableView.GetItems(GridItemType.GroupFooter).Where(item => !item.GroupIndex.Contains(GROUP_INDEX_DELIMITER)).ToArray();

The root header items are used to initiate a recursion method thus allowing traversal of all group headers and footers. Within the recursion method the following call is made:

GridItem[] headerGridItemChildArray = gridGroupHeaderItem.GetChildItems();

It has been noticed that the returned grid item array provided by GetChildItems is inconsistent. If a rebind (explicit or implicit) is triggered, the returned items by GetChildItems are consistent. If a rebind is not initiated (i.e., due to a custom grid item command which does not cause a rebind although the grid still triggers the ItemCreated event but not the ItemDataBound event) the items returned by the GetChildItems are different when compared to the items returned when a rebind occurs.

Additionally, the call to obtain the root items is also inconsistent depending if a rebind occurred. The call to obtain the group header root items is always correct but the call to obtain the group footer root items will not return any results if a rebind did not occur. In this scenario, it seems that the group footer root items end up being children of the root header items. Even if the GroupIndex property is used for the group header and footer root items (when a rebind does not occur), it is noticed that they are different and thus a group header cannot be associated to a group footer.

This is causing an issue since there is no way to properly traverse the group header and footer items and thus associate group footer items with group header items. It is understood that if an explicit rebind is used, then the problem is resolved. This is undesirable since it introduces overhead in retrieving the data again. 


Any help in resolving this issue is appreciated.

Thanks.



3 Answers, 1 is accepted

Sort by
0
Viktor Tachev
Telerik team
answered on 25 Jul 2014, 01:23 PM
Hello,

Please note that the ItemDataBound event is fired initially and after that it is triggered if the grid is rebound. If the logic you are implemented is in ItemDataBound you would need to call Rebind() in order for it to work as expected.

Another approach you could use is to loop through the RadGrid's DataItems on PreRender and making the calculations there.

Regards,
Viktor Tachev
Telerik
 

Check out the Telerik Platform - the only platform that combines a rich set of UI tools with powerful cloud services to develop web, hybrid and native mobile apps.

 
0
tlp
Top achievements
Rank 1
answered on 25 Jul 2014, 05:20 PM
Hello Victor,

Thanks for your response.

In regards to using the ItemDataBound event, we are purposely avoiding it's use due to the overhead in explicitly calling the Rebind() and thus reloading the data. You are correct in that if the logic is placed in the ItemDataBound event and a Rebind() is called, everything works as expected.

As the initial post states, we are using the PreRender event. Everything works fine except when a explicit Rebind() is not used. For example, we have a grid command item template in which we use custom commands. When some of the commands are used we purposely do not issue a rebind since we do not want to incur the overhead of loading the data again. When this happens, the ItemCreated and PreRender events are triggered but the ItemDataBound event is not triggered.

Since our logic is in the PreRender event we were expecting that it would work properly (when the rebind is not called). As a result, we are finding that GetChildItems produces different results if a rebind does not occur when compared to when a rebind does occur. Also, if you look at the GroupIndex for the GroupHeaderItem when a rebind occurs and when one does not, you will notice that the values are the same. When you do the same for a GroupFooterItem, you will notice that the values are not the same. Due to this discrepancy, there is no consistent way to associate a group header item with a group footer item when a rebind is not issued. We need this association since information is required to be shared between a group header and footer item.

Provided is sample code (markup and code behind) which will allow you to reproduce the issue. In the sample, drag "Col1" or "Col2" to create the required group by (causes the implicit rebind). You will notice that multi-line group totals are created based on the DataType column. Click on the AJAX postback button and you will now notice that the multi-line totals are no longer created (rebind is not issued but PreRender event is still triggered). The problem is that within the PreRender event, GetItems for the rootFooterItemArray is now empty and GetChildItems within the method GridGroupItemTraverse returns different results when a rebind is issued.

Please note, we do not want to issue a explicit rebind.


<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default5.aspx.cs" Inherits="Default5" %>
 
<!DOCTYPE html>
 
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="ButtonTest" runat="server" Text="AJAX Postback" />
        <br /><br />
        <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>       
    </div>
     <telerik:RadScriptManager runat="server" ID="RadScriptManager1" />
    <telerik:RadAjaxManager runat="server" ID="RadAjaxManager1" />
    </form>
</body>
</html>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
 
using Telerik.Web.UI;
 
public partial class Default5 : System.Web.UI.Page
{
    private const int VALUE_COL_COUNT = 3;
    protected RadGrid GridSample { get; set; }
 
    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
 
        this.InitializeGrid();
    }
 
    protected void Page_Load(object sender, EventArgs e)
    {
        this.InitializeAjaxManager();
    }
 
    private void InitializeAjaxManager()
    {
        RadAjaxManager ajaxManager = RadAjaxManager.GetCurrent(Page);
 
        if (ajaxManager == null)
            return;
 
        ajaxManager.AjaxSettings.AddAjaxSetting(GridSample, GridSample);
        ajaxManager.AjaxSettings.AddAjaxSetting(ButtonTest, GridSample);
    }
 
    private void InitializeGrid()
    {
        GridBoundColumn boundColumn;
 
        GridSample = new RadGrid();
 
        // Set required event handlers.
        GridSample.NeedDataSource += new GridNeedDataSourceEventHandler(GridSample_NeedDataSource);
        GridSample.PreRender += new EventHandler(GridSample_PreRender);
         
        // Set grid properties.
        GridSample.ID = "GridSample";
        GridSample.AllowSorting = true;
        GridSample.AllowFilteringByColumn = true;
        GridSample.AutoGenerateColumns = false;              
 
        // Column Grouping
        GridSample.ClientSettings.AllowDragToGroup = true;
        GridSample.ShowGroupPanel = true;
 
        GridSample.GroupingSettings.CaseSensitive = false;
 
        GridSample.MasterTableView.ShowGroupFooter = true;
        GridSample.MasterTableView.CommandItemDisplay = GridCommandItemDisplay.None;
        GridSample.MasterTableView.DataMember = "SampleData";
 
        // *** Grid columns. ***
        boundColumn = new GridBoundColumn();
        GridSample.MasterTableView.Columns.Add(boundColumn);
        boundColumn.UniqueName = "Col1";
        boundColumn.DataField = "Col1";
        boundColumn.HeaderText = "Col1";
 
        boundColumn = new GridBoundColumn();
        GridSample.MasterTableView.Columns.Add(boundColumn);
        boundColumn.UniqueName = "Col2";
        boundColumn.DataField = "Col2";
        boundColumn.HeaderText = "Col2";
 
        boundColumn = new GridBoundColumn();
        GridSample.MasterTableView.Columns.Add(boundColumn);
        boundColumn.UniqueName = "DataType";
        boundColumn.DataField = "DataType";
        boundColumn.HeaderText = "DataType";
 
 
        boundColumn = new GridBoundColumn();
        GridSample.MasterTableView.Columns.Add(boundColumn);
        boundColumn.UniqueName = "Value1";
        boundColumn.DataField = "Value1";
        boundColumn.HeaderText = "Value1";
        boundColumn.DataFormatString = "{0:#,###.00}";
        boundColumn.Aggregate = GridAggregateFunction.Sum;
        boundColumn.DataType = Type.GetType("System.Decimal", false);
        boundColumn.HeaderStyle.HorizontalAlign = HorizontalAlign.Right;
        boundColumn.FooterStyle.HorizontalAlign = HorizontalAlign.Right;               
        boundColumn.ItemStyle.HorizontalAlign = HorizontalAlign.Right;
 
        boundColumn = new GridBoundColumn();
        GridSample.MasterTableView.Columns.Add(boundColumn);
        boundColumn.UniqueName = "Value2";
        boundColumn.DataField = "Value2";
        boundColumn.HeaderText = "Value2";
        boundColumn.DataFormatString = "{0:#,###.00}";
        boundColumn.Aggregate = GridAggregateFunction.Sum;
        boundColumn.DataType = Type.GetType("System.Decimal", false);
        boundColumn.HeaderStyle.HorizontalAlign = HorizontalAlign.Right;
        boundColumn.FooterStyle.HorizontalAlign = HorizontalAlign.Right;
        boundColumn.ItemStyle.HorizontalAlign = HorizontalAlign.Right;
 
        boundColumn = new GridBoundColumn();
        GridSample.MasterTableView.Columns.Add(boundColumn);
        boundColumn.UniqueName = "Value3";
        boundColumn.DataField = "Value3";
        boundColumn.HeaderText = "Value3";
        boundColumn.DataFormatString = "{0:#,###.00}";
        boundColumn.Aggregate = GridAggregateFunction.Sum;
        boundColumn.DataType = Type.GetType("System.Decimal", false);
        boundColumn.HeaderStyle.HorizontalAlign = HorizontalAlign.Right;
        boundColumn.FooterStyle.HorizontalAlign = HorizontalAlign.Right;
        boundColumn.ItemStyle.HorizontalAlign = HorizontalAlign.Right;
 
 
        PlaceHolder1.Controls.Add(GridSample);
    }   
 
    void GridSample_NeedDataSource(object sender, GridNeedDataSourceEventArgs e)
    {
        GridSample.DataSource = this.GetSampleData();
    }
 
    void GridSample_PreRender(object sender, EventArgs e)
    {
        // Obtain root items.
        GridItem[] rootHeaderItemArray = GridSample.MasterTableView.GetItems(GridItemType.GroupHeader).Where(item => !item.GroupIndex.Contains("_")).ToArray();
        GridItem[] rootFooterItemArray = GridSample.MasterTableView.GetItems(GridItemType.GroupFooter).Where(item => !item.GroupIndex.Contains("_")).ToArray();
 
        for (int i = 0; i < rootHeaderItemArray.Length; i++)
        {
            // Traverse each root item obtaining the associated group totals.
            Dictionary<string, List<decimal>> dataTypeGroupTotalDictionary = this.GridGroupItemTraverse((rootHeaderItemArray[i] as GridGroupHeaderItem));
 
            // Handle the root footer items associated with the root header items.
            if (rootFooterItemArray.Length > 0)
            {
                this.SetGridGroupTotals(dataTypeGroupTotalDictionary, (rootHeaderItemArray[i] as GridGroupHeaderItem), (rootFooterItemArray[i] as GridGroupFooterItem));
            }
        }
    }
 
    private Dictionary<string, List<decimal>> GridGroupItemTraverse(GridGroupHeaderItem gridGroupHeaderItem)
    {
        GridGroupHeaderItem siblingGridGroupHeaderItem = null;
        Dictionary<string, List<decimal>> dataTypeGroupTotalDictionary = new Dictionary<string, List<decimal>>();
        Dictionary<string, List<decimal>> dataTypeChildGroupTotalDictionary = null;
 
        // Obtain the children items for the provided group header item.
        GridItem[] headerGridItemChildArray = gridGroupHeaderItem.GetChildItems();
 
        for (int i = 0; i < headerGridItemChildArray.Length; i++)
        {
            GridItem gridItem = headerGridItemChildArray[i];
 
            // For header items, continue the traversal obtaining the group totals at each level.
            if (gridItem is GridGroupHeaderItem)
            {
                siblingGridGroupHeaderItem = gridItem as GridGroupHeaderItem;
                dataTypeChildGroupTotalDictionary = this.GridGroupItemTraverse(siblingGridGroupHeaderItem);
 
                // Include the child group totals into the current level totals.
                if (dataTypeChildGroupTotalDictionary != null)
                {
                    // For each group total row, create the necessary footer row/item and set the required footer values.
                    foreach (KeyValuePair<string, List<decimal>> keyValuePair in dataTypeChildGroupTotalDictionary)
                    {
 
                        if (!dataTypeGroupTotalDictionary.ContainsKey(keyValuePair.Key))
                        {
                            List<decimal> totalList = new List<decimal>();
                            totalList.AddRange(Enumerable.Repeat((decimal)0, VALUE_COL_COUNT));
                            dataTypeGroupTotalDictionary.Add(keyValuePair.Key, totalList);
                        }
 
                        // Set the total values.
                        for (int j = 0; j < keyValuePair.Value.Count; j++)
                        {
                            dataTypeGroupTotalDictionary[keyValuePair.Key][j] += keyValuePair.Value[j];
                        }
 
                    }
                }
 
            }
            else if (gridItem is GridGroupFooterItem)
            {
                // For footer items, set the group totals.
                this.SetGridGroupTotals(dataTypeChildGroupTotalDictionary, siblingGridGroupHeaderItem, (gridItem as GridGroupFooterItem));
            }
            else if (gridItem is GridDataItem)
            {
                // For data items, determine the totals based on the group total key.
                GridDataItem gridDataItem = gridItem as GridDataItem;
                string key = gridDataItem["DataType"].Text;
 
                // Process the row values.
                for (int j = 0; j < VALUE_COL_COUNT; j++)
                {
                    if (!dataTypeGroupTotalDictionary.ContainsKey(key))
                    {
                        List<decimal> totalList = new List<decimal>();
                        totalList.AddRange(Enumerable.Repeat((decimal)0, VALUE_COL_COUNT));
                        totalList[j] = decimal.Parse(gridDataItem["Value" + (j + 1).ToString()].Text);
                        dataTypeGroupTotalDictionary.Add(key, totalList);
                    }
                    else
                    {
                        dataTypeGroupTotalDictionary[key][j] += decimal.Parse(gridDataItem["Value" + (j + 1).ToString()].Text);
                    }
                }
            }
        }
 
        return dataTypeGroupTotalDictionary;
    }
 
    private void SetGridGroupTotals(Dictionary<string, List<decimal>> dataTypeGroupTotalDictionary, GridGroupHeaderItem gridGroupHeaderItem, GridGroupFooterItem gridGroupFooterItem)
    {
        // Ensure that totals have been provided to display.
        if (dataTypeGroupTotalDictionary == null)
            return;
 
        // Obtain the parent item for the footer and set the initial total footer item.
        Control parentControl = gridGroupFooterItem.Parent;
        GridGroupFooterItem gridFooterItemGroupTotal = gridGroupFooterItem;
 
        // For each group total row, create the necessary footer row/item and set the required footer values.
        foreach (KeyValuePair<string, List<decimal>> keyValuePair in dataTypeGroupTotalDictionary)
        {
             
            string groupText = gridGroupHeaderItem.DataCell.Text + " - " + keyValuePair.Key;
            gridFooterItemGroupTotal[GridSample.MasterTableView.Columns[0].UniqueName].Controls.Add(new LiteralControl(groupText + "<br/>"));               
 
            // Set the style for the total text.
            gridFooterItemGroupTotal[GridSample.MasterTableView.Columns[0].UniqueName].Style.Add(HtmlTextWriterStyle.FontWeight, "bold");
            gridFooterItemGroupTotal[GridSample.MasterTableView.Columns[0].UniqueName].Style.Add(HtmlTextWriterStyle.WhiteSpace, "nowrap");           
 
            // Set the total values.
            for (int i = 0; i < keyValuePair.Value.Count; i++)
            {
                string colName = "Value" + (i + 1).ToString();
                LiteralControl literialControl = new LiteralControl(keyValuePair.Value[i].ToString("#,###.00") + "<br/>");
                gridGroupFooterItem[colName].Controls.Add(literialControl);
            }
        }                   
    }
 
     
    private List<SampleData> GetSampleData()
    {
 
        List<SampleData> list = new List<SampleData>();
 
        list.Add(new SampleData("a", "a", "$", 1, 4, 1));
        list.Add(new SampleData("a", "a", "$", 1, 4, 1));
        list.Add(new SampleData("a", "a", "Hrs", 1, 4, 1));
        list.Add(new SampleData("a", "a", "Hrs", 1, 4, 1));
 
        list.Add(new SampleData("a", "b", "$", 2, 3, 1));
        list.Add(new SampleData("a", "b", "$", 2, 3, 1));
        list.Add(new SampleData("a", "b", "Hrs", 2, 3, 1));
        list.Add(new SampleData("a", "b", "Hrs", 2, 3, 1));
 
        list.Add(new SampleData("b", "a", "$", 2, 3, 2));
        list.Add(new SampleData("b", "a", "$", 2, 3, 2));
        list.Add(new SampleData("b", "a", "Hrs", 2, 3, 2));
        list.Add(new SampleData("b", "a", "Hrs", 2, 3, 2));
 
        list.Add(new SampleData("b", "b", "$", 3, 2, 2));
        list.Add(new SampleData("b", "b", "$", 3, 2, 2));
        list.Add(new SampleData("b", "b", "Hrs", 3, 2, 2));
        list.Add(new SampleData("b", "b", "Hrs", 3, 2, 2));
 
        list.Add(new SampleData("c", "a", "$", 3, 2, 3));
        list.Add(new SampleData("c", "a", "$", 3, 2, 3));
        list.Add(new SampleData("c", "a", "Hrs", 3, 2, 3));
        list.Add(new SampleData("c", "a", "Hrs", 3, 2, 3));
 
        list.Add(new SampleData("c", "b", "$", 4, 1, 3));
        list.Add(new SampleData("c", "b", "$", 4, 1, 3));
        list.Add(new SampleData("c", "b", "Hrs", 4, 1, 3));
        list.Add(new SampleData("c", "b", "Hrs", 4, 1, 3));
 
        return list;
 
    }
 
 
    private class SampleData
    {
 
        public SampleData() { }
 
        public SampleData(string col1, string col2, string dataType, decimal value1, decimal value2, decimal value3 )
        {
            Col1 = col1;
            Col2 = col2;
            DataType = dataType;
 
            Value1 = value1;
            Value2 = value2;
            Value3 = value3;
        }
 
        public string Col1 { get; set; }
        public string Col2 { get; set; }
        public string DataType { get; set; }
        public decimal Value1 { get; set; }
        public decimal Value2 { get; set; }
        public decimal Value3 { get; set; }
    }
}
 


0
Viktor Tachev
Telerik team
answered on 30 Jul 2014, 12:31 PM
Hello,

Thank you for sending a sample code. I used it to build a sample page and was able to replicate the behavior you describe. After debugging I noticed that the GetItems() method returns the header and footer items, however after they are additionally refined, there are no returned results. After removing the .Where() method the footers were always visible.

void GridSample_PreRender(object sender, EventArgs e)
{
    // Obtain root items.
    GridItem[] rootHeaderItemArray = GridSample.MasterTableView.GetItems(GridItemType.GroupHeader).ToArray();
    GridItem[] rootFooterItemArray = GridSample.MasterTableView.GetItems(GridItemType.GroupFooter).ToArray();
 
    for (int i = 0; i < rootHeaderItemArray.Length; i++)
    {
        // Traverse each root item obtaining the associated group totals.
        Dictionary<string, List<decimal>> dataTypeGroupTotalDictionary = this.GridGroupItemTraverse((rootHeaderItemArray[i] as GridGroupHeaderItem));
 
        // Handle the root footer items associated with the root header items.
        if (rootFooterItemArray.Length > 0)
        {
            this.SetGridGroupTotals(dataTypeGroupTotalDictionary, (rootHeaderItemArray[i] as GridGroupHeaderItem), (rootFooterItemArray[i] as GridGroupFooterItem));
        }
    }
 
  
}

On a side note, please have in mind that when you are creating the RadGrid on Page_Init you should add the columns to the respective GridTableView after setting their properties.

private void InitializeGrid()
{
    GridSample = new RadGrid();
 
    // Set required event handlers.
    GridSample.NeedDataSource += new GridNeedDataSourceEventHandler(GridSample_NeedDataSource);
    GridSample.PreRender += new EventHandler(GridSample_PreRender);
 
    // Set grid properties.
    GridSample.ID = "GridSample";
    GridSample.AllowSorting = true;
    GridSample.AllowFilteringByColumn = true;
    GridSample.AutoGenerateColumns = false;
    GridSample.ShowFooter = true;
 
    // Column Grouping
    GridSample.ClientSettings.AllowDragToGroup = true;
    GridSample.ShowGroupPanel = true;
 
    GridSample.GroupingSettings.CaseSensitive = false;
 
    GridSample.MasterTableView.ShowGroupFooter = true;
    GridSample.MasterTableView.CommandItemDisplay = GridCommandItemDisplay.None;
    GridSample.MasterTableView.DataMember = "SampleData";
 
    // *** Grid columns. ***
    GridBoundColumn boundColumn;
    boundColumn = new GridBoundColumn();
    boundColumn.UniqueName = "Col1";
    boundColumn.DataField = "Col1";
    boundColumn.HeaderText = "Col1";
    GridSample.MasterTableView.Columns.Add(boundColumn);
 
    boundColumn = new GridBoundColumn();
    boundColumn.UniqueName = "Col2";
    boundColumn.DataField = "Col2";
    boundColumn.HeaderText = "Col2";
    GridSample.MasterTableView.Columns.Add(boundColumn);
 
    boundColumn = new GridBoundColumn();
    boundColumn.UniqueName = "DataType";
    boundColumn.DataField = "DataType";
    boundColumn.HeaderText = "DataType";
    GridSample.MasterTableView.Columns.Add(boundColumn);
 
 
    boundColumn = new GridBoundColumn();
    boundColumn.UniqueName = "Value1";
    boundColumn.DataField = "Value1";
    boundColumn.HeaderText = "Value1";
    boundColumn.DataFormatString = "{0:#,###.00}";
    boundColumn.Aggregate = GridAggregateFunction.Sum;
    boundColumn.DataType = Type.GetType("System.Decimal", false);
    boundColumn.HeaderStyle.HorizontalAlign = HorizontalAlign.Right;
    boundColumn.FooterStyle.HorizontalAlign = HorizontalAlign.Right;
    boundColumn.ItemStyle.HorizontalAlign = HorizontalAlign.Right;
    GridSample.MasterTableView.Columns.Add(boundColumn);
 
    boundColumn = new GridBoundColumn();
    boundColumn.UniqueName = "Value2";
    boundColumn.DataField = "Value2";
    boundColumn.HeaderText = "Value2";
    boundColumn.DataFormatString = "{0:#,###.00}";
    boundColumn.Aggregate = GridAggregateFunction.Sum;
    boundColumn.DataType = Type.GetType("System.Decimal", false);
    boundColumn.HeaderStyle.HorizontalAlign = HorizontalAlign.Right;
    boundColumn.FooterStyle.HorizontalAlign = HorizontalAlign.Right;
    boundColumn.ItemStyle.HorizontalAlign = HorizontalAlign.Right;
    GridSample.MasterTableView.Columns.Add(boundColumn);
 
    boundColumn = new GridBoundColumn();
    boundColumn.UniqueName = "Value3";
    boundColumn.DataField = "Value3";
    boundColumn.HeaderText = "Value3";
    boundColumn.DataFormatString = "{0:#,###.00}";
    boundColumn.Aggregate = GridAggregateFunction.Sum;
    boundColumn.DataType = Type.GetType("System.Decimal", false);
    boundColumn.HeaderStyle.HorizontalAlign = HorizontalAlign.Right;
    boundColumn.FooterStyle.HorizontalAlign = HorizontalAlign.Right;
    boundColumn.ItemStyle.HorizontalAlign = HorizontalAlign.Right;
    GridSample.MasterTableView.Columns.Add(boundColumn);
 
 
    PlaceHolder1.Controls.Add(GridSample);
}


The approach is also illustrated in this article.

For convenience I am also attaching the sample page I used for testing.

Regards,
Viktor Tachev
Telerik
 

Check out the Telerik Platform - the only platform that combines a rich set of UI tools with powerful cloud services to develop web, hybrid and native mobile apps.

 
Tags
Grid
Asked by
tlp
Top achievements
Rank 1
Answers by
Viktor Tachev
Telerik team
tlp
Top achievements
Rank 1
Share this question
or