I'm having a bit of a problem with data binding on a hierarchical grid. I think I may have painted myself into a corner a bit by some of the choices I made along the way, but I'm hoping you guys can either tell me how to make this work or propose a different way of implementing it.
The basic problem I have is that the data objects I'm using are very complex and don't really fit into the normal mechanisms for data binding since they consist of field collections which vary from object to object (so I can't use accessor binding), and are of complex types (so I can't use data sets directly).
The way I got around this was to use Template columns which accessed the objects directly from the source. Unfortunately when I use them in a hierarchical grid I'm running into problems with the fact that only the expanded detail table gets rebound.
In a previous post you guys pointed out that I need to recreate the control structure for the view state to be bound into it, which worked fine for simple examples. In my real life sample however I actually don't know how many controls I need to create without having the data available, which means that all the other rows on the grid disappear.
Ideally from my point of view I'd turn off the view state altogether and rebuild the entire grid every postback, but when I tried that it forgets which detail tables have been opened.
So, is there any way to force a data bind on all expanded rows? Or alternatively is there a way to turn off view state while maintaining the detail table expansion state? Or am I on completely the wrong track with what I'm doing and there's a much easier way to do it?
I've knocked up a sample which pretty much does all the things I am doing for real, including the use of a hidden row count to associate with the real data set.
Hopefully this makes some sense :)
The basic problem I have is that the data objects I'm using are very complex and don't really fit into the normal mechanisms for data binding since they consist of field collections which vary from object to object (so I can't use accessor binding), and are of complex types (so I can't use data sets directly).
The way I got around this was to use Template columns which accessed the objects directly from the source. Unfortunately when I use them in a hierarchical grid I'm running into problems with the fact that only the expanded detail table gets rebound.
In a previous post you guys pointed out that I need to recreate the control structure for the view state to be bound into it, which worked fine for simple examples. In my real life sample however I actually don't know how many controls I need to create without having the data available, which means that all the other rows on the grid disappear.
Ideally from my point of view I'd turn off the view state altogether and rebuild the entire grid every postback, but when I tried that it forgets which detail tables have been opened.
So, is there any way to force a data bind on all expanded rows? Or alternatively is there a way to turn off view state while maintaining the detail table expansion state? Or am I on completely the wrong track with what I'm doing and there's a much easier way to do it?
I've knocked up a sample which pretty much does all the things I am doing for real, including the use of a hidden row count to associate with the real data set.
Hopefully this makes some sense :)
| using System; |
| using System.Collections.Generic; |
| using System.Data; |
| using System.Web.UI; |
| using System.Web.UI.WebControls; |
| using Telerik.WebControls; |
| namespace GridTest1.Hierarchy |
| { |
| public partial class HierarchyComplex : Page |
| { |
| private RadGrid RadGrid1; |
| private List<DataItem> data; |
| protected override void OnInit(EventArgs e) |
| { |
| RadGrid1 = new RadGrid(); |
| SetupGrid(); |
| SetupGridColumns(); |
| form1.Controls.Add(RadGrid1); |
| } |
| protected void Page_Load(object sender, EventArgs e) |
| { |
| if (!IsPostBack) |
| { |
| RadGrid1.DataSource = MakeDataSet(); |
| } |
| } |
| private void SetupGrid() |
| { |
| RadGrid1.EnableViewState = true; |
| RadGrid1.MasterTableView.EnableColumnsViewState = false; |
| RadGrid1.PageSize = 30; |
| RadGrid1.AllowPaging = true; |
| RadGrid1.AllowSorting = true; |
| RadGrid1.AutoGenerateColumns = false; |
| RadGrid1.ID = "Parent"; |
| RadGrid1.MasterTableView.Name = "Table1"; |
| RadGrid1.MasterTableView.ID = "Table1"; |
| RadGrid1.PagerStyle.AlwaysVisible = true; |
| RadGrid1.PagerStyle.Mode = GridPagerMode.NextPrevNumericAndAdvanced; |
| RadGrid1.PagerStyle.Position = GridPagerPosition.TopAndBottom; |
| //RadGrid1.ItemDataBound += BindTelericGridRow; |
| RadGrid1.DetailTableDataBind += grid_DetailTableDataBind; |
| RadGrid1.NeedDataSource += grid_NeedDataSource; |
| } |
| private void SetupGridColumns() |
| { |
| MakeData(); |
| AddTemplateColumn(RadGrid1.MasterTableView, "Column1"); |
| GridTableView childView = new GridTableView(RadGrid1, true); |
| RadGrid1.MasterTableView.DataKeyNames = new string[] { "Column1" }; |
| childView.Columns.Clear(); |
| AddTemplateColumn(childView, "Column1"); |
| AddTemplateColumn(childView, "Column2"); |
| AddTemplateColumn(childView, "RowNumber").Visible = false; |
| childView.Name = "Table2"; |
| childView.AllowPaging = false; |
| childView.AllowFilteringByColumn = false; |
| childView.EnableColumnsViewState = false; |
| childView.DataMember = "Table2"; |
| childView.DataKeyNames = new string[] { "Column1" }; |
| GridRelationFields rel = new GridRelationFields(); |
| rel.DetailKeyField = "Column1"; |
| rel.MasterKeyField = "Column1"; |
| childView.ParentTableRelation.Add(rel); |
| RadGrid1.MasterTableView.DetailTables.Clear(); |
| RadGrid1.MasterTableView.DetailTables.Add(childView); |
| } |
| void grid_NeedDataSource(object source, GridNeedDataSourceEventArgs e) |
| { |
| RadGrid grid = source as RadGrid; |
| if (!e.IsFromDetailTable) |
| grid.DataSource = MakeDataSet(); |
| } |
| private GridTemplateColumn AddTemplateColumn(GridTableView view, string columnName) |
| { |
| GridTemplateColumn template = new GridTemplateColumn(); |
| ColumnTemplate cTemplate = new ColumnTemplate(columnName, data); |
| template.ItemTemplate = cTemplate; |
| template.HeaderText = columnName; |
| template.UniqueName = columnName; |
| template.DataField = columnName; |
| template.DataType = typeof(string); |
| view.Columns.Add(template); |
| return template; |
| } |
| private void grid_DetailTableDataBind(object source, GridDetailTableDataBindEventArgs e) |
| { |
| DataSet data = MakeDataSet(); |
| DataTable table = data.Tables[e.DetailTableView.DataMember]; |
| e.DetailTableView.DataSource = table; |
| } |
| public List<DataItem> MakeData() |
| { |
| data = new List<DataItem>(); |
| int parentCount = 5; |
| int childCount = 5; |
| for (int i = 0; i < parentCount; ++i) |
| { |
| for (int j = 0; j < childCount; j++) |
| { |
| DataItem item = new DataItem(i, i * parentCount + j, (j % 3) + 1); |
| data.Add(item); |
| } |
| } |
| return data; |
| } |
| private DataSet MakeDataSet() |
| { |
| DataSet dataSet = new DataSet("DataSet"); |
| DataTable table = new DataTable("Table1"); |
| table.Columns.Add(new DataColumn("Column1", typeof(string))); |
| dataSet.Tables.Add(table); |
| MakeData(); |
| int parentId = -1; |
| foreach (DataItem item in data) |
| { |
| if (item.ParentId != parentId) |
| { |
| DataRow row = table.NewRow(); |
| row["Column1"] = item.ParentId; |
| table.Rows.Add(row); |
| parentId = item.ParentId; |
| } |
| } |
| table = new DataTable("Table2"); |
| table.Columns.Add(new DataColumn("Column1", typeof(string))); |
| table.Columns.Add(new DataColumn("Column2", typeof(string))); |
| table.Columns.Add(new DataColumn("RowNumber", typeof(int))); |
| dataSet.Tables.Add(table); |
| int rowCount = 0; |
| foreach (DataItem item in data) |
| { |
| DataRow row = table.NewRow(); |
| row["Column1"] = item.ParentId; |
| row["Column2"] = item.Id; |
| row["RowNumber"] = rowCount++; |
| table.Rows.Add(row); |
| } |
| return dataSet; |
| } |
| public class ColumnTemplate : ITemplate |
| { |
| private readonly string columnName; |
| private readonly List<DataItem> dataSet; |
| public ColumnTemplate(string columnName, List<DataItem> dataSet) |
| { |
| this.columnName = columnName; |
| this.dataSet = dataSet; |
| } |
| #region ITemplate Members |
| public void InstantiateIn(Control container) |
| { |
| ColumnControl column = new ColumnControl(columnName, dataSet); |
| column.ID = "Column" + columnName; |
| container.Controls.Add(column); |
| } |
| #endregion |
| } |
| public class DataItem |
| { |
| private readonly int parentId; |
| private readonly int id; |
| private readonly int labelCount; |
| public DataItem(int parentId, int id, int labelCount) |
| { |
| this.parentId = parentId; |
| this.id = id; |
| this.labelCount = labelCount; |
| } |
| public int LabelCount |
| { |
| get { return labelCount; } |
| } |
| public int Id |
| { |
| get { return id; } |
| } |
| public int ParentId |
| { |
| get { return parentId; } |
| } |
| public List<Label> Labels |
| { |
| get |
| { |
| List<Label> labels = new List<Label>(); |
| for (int i = 0; i < labelCount; ++i) |
| { |
| Label l = new Label(); |
| l.Text = i.ToString(); |
| labels.Add(l); |
| } |
| return labels; |
| } |
| } |
| } |
| public class ColumnControl : Control |
| { |
| private DataItem data; |
| private string columnName; |
| private string tableName; |
| private readonly List<DataItem> dataSet; |
| public ColumnControl(string columnName, List<DataItem> dataSet) |
| { |
| this.columnName = columnName; |
| this.dataSet = dataSet; |
| DataBinding += ColumnControl_DataBinding; |
| } |
| void ColumnControl_DataBinding(object sender, EventArgs e) |
| { |
| GridDataItem gridData = (GridDataItem)Parent.Parent; |
| tableName = gridData.OwnerTableView.Name; |
| DataRowView dataRow = (DataRowView)(gridData).DataItem; |
| if (tableName == "Table1") |
| { |
| string item = dataRow[columnName].ToString(); |
| HyperLink l = (HyperLink)FindControl(string.Format("{0}Label", columnName)); |
| l.Text = item; |
| l.NavigateUrl = "~"; |
| } |
| if (tableName == "Table2") |
| { |
| int rowNumber = (int)dataRow["RowNumber"]; |
| data = dataSet[rowNumber]; |
| } |
| } |
| protected override void CreateChildControls() |
| { |
| //if (tableName == "Table1") |
| { |
| HyperLink l = new HyperLink(); |
| l.ID = string.Format("{0}Label", columnName); |
| Controls.Add(l); |
| } |
| if (tableName == "Table2") |
| { |
| foreach (Label c in data.Labels) |
| Controls.Add(c); |
| } |
| } |
| } |
| } |
| } |