Complex GirdView Problem, returning values from dynamic TemplateFi

G

Guest

Anyone up for a challenge?

I've been struggling with this for a few days and was hoping someone could
help me. Pouring through all the messageboards I just can't find the
solution.

We have a GridView that needs to be dynamically designed, depending on what
collection of fields our uses want to edit for their product data. We have
400+ fields of information per product so they're selecting a subset of those
fields to edit.

Second, they need "Excel-like" functionality. So we can't have a normal
BoundField grid where the user clicks "Edit" and then goes into EditTemplate
mode and must click "Submit/Cancel" to make their changes. They must have
the ability to update all of their records without clicking the buttons or
posting back. Then they click a single Update Products button on the page
that postback and makes all of their changes at once.

We do this by creating TemplateField columns with either a textbox control
or a checkbox control, depending on the datatype of the field. When the grid
loads they can update every field and then when they click UpdateProducts we
iterate through the columns and save all the changes to our Product business
object.

Here is our problem. The grid loads, we can edit the values, everything
seems to be working. However, when we post back we cannot access the textbox
and checkbox controls in the TemplateField columns. When we use the
..FindControl method on the GridViewRows they return a null and cannot find
the controls.

Through reading I have learned that apparently TemplateField created
dynamically do not return with their controls on postback. So before the
viewstate is restored (and the controls are repopulated with the new user
inputted values) the control tree in the TemplateField column must be
recreated.

I have tried everything. Recreating the grid in the Page_Load in the
Page_Init. I have tried clearing the columns and recreating, I've tried
delting them one at a time and re-adding. I can't get these values.

If anyone could help I would greatly appreciate it.

I will post a second message with my code for anyone who can take a look.
Thanks in advance. Larry Grady
 
G

Guest

<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="gridBuildDynamic_test.aspx.cs" Inherits="gridBuildDynamic_test" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div id="divDynamicGrid">
<asp:TextBox ID="txtOwnerID" runat="server"></asp:TextBox><asp:Button
ID="btnRefreshGrid" runat="server"
OnClick="btnRefreshGrid_Click" Text="Refresh Brands" /><br />
<asp:DropDownList ID="ddlBrands" runat="server">
</asp:DropDownList>
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click"
Text="Bind" /><br />
<br />
<br />
<asp:DropDownList ID="ddlGridViews" runat="server"
OnSelectedIndexChanged="ddlGridViews_SelectedIndexChanged"
AutoPostBack="True" AppendDataBoundItems="True">
</asp:DropDownList>
<asp:DropDownList ID="ddlGridFieldGroups" runat="server"
AppendDataBoundItems="True" AutoPostBack="True"
OnSelectedIndexChanged="ddlGridFieldGroups_SelectedIndexChanged">
</asp:DropDownList>
<br />
<br />
<asp:GridView ID="gvDynamicGrid" runat="server"
AutoGenerateColumns="False" BackColor="LightGoldenrodYellow"
BorderColor="Tan" BorderWidth="1px" CellPadding="2" ForeColor="Black"
GridLines="None" DataKeyNames="Product_ID">
<FooterStyle BackColor="Tan" />
<PagerStyle BackColor="PaleGoldenrod" ForeColor="DarkSlateBlue"
HorizontalAlign="Center" />
<SelectedRowStyle BackColor="DarkSlateBlue"
ForeColor="GhostWhite" />
<HeaderStyle BackColor="Tan" Font-Bold="True" />
<AlternatingRowStyle BackColor="PaleGoldenrod" />
</asp:GridView>
<br />
<asp:Button ID="btnSaveAll" runat="server"
OnClick="btnSaveAll_Click" Text="Update Products" /><asp:Button
ID="btnCancel" runat="server" OnClick="btnCancel_Click"
Text="Cancel Changes" />
</div>
</form>
</body>
</html>

____________________________

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Collections.Specialized;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Reflection;
using ReplinkDotNet.Library;

public partial class gridBuildDynamic_test : System.Web.UI.Page
{
//All of the Objects and Collections in this page use the CSLA framework
standards

#region Load Dropdowns and Build/Bind Grid

protected void Page_Load(object sender, EventArgs e)
{
//If the user has chosen a GridFieldGroup, create the grid.
//Also re-create it upon postback
if (ddlGridFieldGroups.SelectedValue != "")
{
BuildGrid(ddlGridFieldGroups.SelectedValue);
}

if (!IsPostBack)
{
//This loads the GridViewList, just a dropdownlist to filter the
GridFieldGroups
//so our users don't have 100 items in their drop down
GridViewList gv = GridViewList.GetGridViewList();
ddlGridViews.DataSource = gv.BindableList;
ddlGridViews.DataValueField = "Value";
ddlGridViews.DataTextField = "Name";

ddlGridViews.DataBind();
}

}
protected void ddlGridViews_SelectedIndexChanged(object sender,
EventArgs e)
{
//When a GridView is selected the GridFieldGroups drop down is
populated
ddlGridFieldGroups.Items.Clear();

GridFieldGroupList gfg =
GridFieldGroupList.GetGridFieldGroupList(Convert.ToInt32(ddlGridViews.SelectedValue));

ddlGridFieldGroups.DataSource = gfg.BindableList;
ddlGridFieldGroups.DataValueField = "Value";
ddlGridFieldGroups.DataTextField = "Name";
ddlGridFieldGroups.DataBind();
}


protected void ddlGridFieldGroups_SelectedIndexChanged(object sender,
EventArgs e)
{
//When a new GridFieldGroup is selected, re-bind the data to the new
grid (which is generated dynamically in the Page_Load)
BindData();
}



protected void BuildGrid(string GridFieldGroupID)
{
//Build the GridView Based upon the selected GridFieldGroup

//Clear the existing columns
gvDynamicGrid.Columns.Clear();

TemplateField tf;

//This is a collection of DictionaryField Business Objects. Each
DictionaryField item represents a database
//field that will be bound to a GridView column. We iterate through
the DictionaryField objects, create the columns,
//which include databinding events (further down in the
gridColumnTemplateClass)
DictionaryFields dfs =
DictionaryFields.GetDictionaryFields(Convert.ToInt32(GridFieldGroupID));
foreach (DictionaryField df in dfs)
{
//Create a new TeamplateField and set the header
tf = new TemplateField();
tf.HeaderText = df.FieldName;

//Set the ItemTemplate to a new instance of the gridCOlumnTemplate
//We need to pass in the ListItemType, the field's DataType, and
the Field's Name
tf.ItemTemplate = new gridColumnTemplate(ListItemType.Item,
df.DataType, df.FieldName);

//Add the TemplateField to the Columns collection of the GridView
gvDynamicGrid.Columns.Add(tf);
}

//This is one extra TemplateField we append to the end of the grid
//If the user wants the Product deleted, they click the checkbox in
this column
//It gets added to every grouping of fields
tf = new TemplateField();
tf.HeaderText = "Delete?";
tf.ItemTemplate = new gridColumnTemplate(ListItemType.Item, "bit",
"toDelete");
gvDynamicGrid.Columns.Add(tf);

}

protected void BindData()
{
//This function retrieves a collection of Product Business Objects
//Then it binds this collection to the GridView
//The products contain all of the fields in the database

//**** ON OF OUR QUESTIONS IS HOW TO LAZY LOAD THE OBJECT ****

//First we check to see if the user has selected a Brand (a Brand is
what we use to retrive our collection of Products)
//If they have not, we hard code one in just for TESTING purposes only
//If they have, we use it to retrieve the selected products
Guid g;
if (ddlBrands.SelectedValue == "")
{
g = new Guid("{8F00A604-421E-4AEB-A853-35319B49E838}");
}
else
{
g = new Guid(ddlBrands.SelectedValue);
}

//Retritve the Products collection
Products ps = Products.GetProducts(g);

//Set the Datasource and Bind the Grid
gvDynamicGrid.DataSource = ps;
gvDynamicGrid.DataBind();
}

#endregion

#region gridColumnTemplate Class


public class gridColumnTemplate : IBindableTemplate
{
//This is our TemplateField Class

ListItemType _templateType;
string _fieldName;
string _dataType;

public gridColumnTemplate(ListItemType type, string dataType, string
fieldName)
{
_templateType = type;
_dataType = dataType;
_fieldName = fieldName;
}

public void InstantiateIn(System.Web.UI.Control container)
{
//This function Instantiates teh controls for our FieldTemplate
and includes DataBinding events
//There can be three different items returned
//1. A Textbox bound to an object property
//2. A Checkbox bound to an object property (if the datatype is
bit)
//3. An HTML Checkbox to delete items, it is NOT bound

HtmlInputCheckBox cbDelete;
CheckBox cb;
TextBox tb;

switch (_templateType)
{
case ListItemType.Item:
switch (_fieldName)
{
case "toDelete":
cbDelete = new HtmlInputCheckBox();
cbDelete.ID = "toDelete";
container.Controls.Add(cbDelete);
break;
default:
switch (_dataType)
{
case "bit":
cb = new CheckBox();
cb.ID = _fieldName;
cb.DataBinding += new
EventHandler(TemplateControl_DataBinding);
container.Controls.Add(cb);
break;
// There are going to be many more cases for
pct, money, dates, etc. But for now they'll all be straigh textboxes
default:
tb = new TextBox();
tb.ID = _fieldName;
tb.BackColor =
System.Drawing.Color.Transparent;
tb.BorderStyle = BorderStyle.None;
tb.DataBinding += new
EventHandler(TemplateControl_DataBinding);
container.Controls.Add(tb);
break;
}
break;
}
break;
}

}

public IOrderedDictionary ExtractValues(Control container)
{
//This allows us to retrieve our values
OrderedDictionary dict = new OrderedDictionary();

switch (_dataType)
{
case "bit":
bool bValue = ((CheckBox)container.Controls[0]).Checked;
dict.Add(this._fieldName, bValue);
break;
default:
string sValue = ((TextBox)container.Controls[0]).Text;
dict.Add(this._fieldName, sValue);
break;
}

return dict;
}

private void TemplateControl_DataBinding(object sender, EventArgs e)
{
//This is our DataBinding event


object databoundValue =
DataBinder.Eval(((IDataItemContainer)((Control)sender).NamingContainer).DataItem, _fieldName);

TextBox textBox = sender as TextBox;

if (textBox != null)
{
textBox.Text = databoundValue.ToString();
}
else
{
CheckBox checkBox = sender as CheckBox;
if (checkBox != null)
{
checkBox.Checked = (bool)databoundValue;
}
}
}


}

#endregion

#region BrandsBinding


protected void btnRefreshGrid_Click(object sender, EventArgs e)
{
//After a user enters an OwnerID they click this button to refresh
the Brands list
BindBrandList();
}

protected void BindBrandList()
{
//Brands is a collection of Brand Business Objects
Brands bnds;
bnds = Brands.GetBrands(txtOwnerID.Text);

//Set Data values and Bind to dropdownlist
ddlBrands.DataSource = bnds;
ddlBrands.DataTextField = "BrandName";
ddlBrands.DataValueField = "Brand_ID";
ddlBrands.DataBind();
}


protected void Button1_Click(object sender, EventArgs e)
{
//When the user selects a different brand they then select Bind to
rebind the grid
//Obviously this is just for testing, our user interface will not
require this button
BindData();
}
#endregion

#region Save and Cancel Changes

protected void btnSaveAll_Click(object sender, EventArgs e)
{
//This function iterates through the GridViewRows and saves teh
users changes
//**** THIS IS WHERE OUR MAIN ERROR IS ******

Product prd;


DictionaryFields dfs =
DictionaryFields.GetDictionaryFields(Convert.ToInt32(ddlGridFieldGroups.SelectedValue));

//Iterate through each row in the GridView
foreach (GridViewRow r in gvDynamicGrid.Rows)
{
//Retrive teh Product object using the Product_ID field
Guid id = new
Guid(Convert.ToString(gvDynamicGrid.DataKeys[r.RowIndex].Value));
prd = Product.GetProduct(id);

//Check the Delete HTML Checkbox
//*** THIS DOES NOT WORK ***
HtmlInputCheckBox cbDelete =
(HtmlInputCheckBox)r.FindControl("toDelete");

//If the user has checked Delete then delete the object from the
collection (and of course, from the database)
//*** THIS DOES NOT WORK ***
//Becuase the above line doesn't work we get the following error:
// "System.NullReferenceException: Object reference not set to
an instance of an object."
if (cbDelete.Checked)
{
//Mark the item as delete and save the change in the object
prd.Delete();
prd.Save();
}
else
{
//If Delete is not checked, update changes in each field

TextBox tb;
CheckBox cb;

//Iterate through the DictioaryFields collection retrieved
above
foreach (DictionaryField df in dfs)
{
//IF the datatype is a bit we need to update from a
Checkbox
if (df.DataType == "bit")
{
//*** THIS DOES NOT WORK ***
cb = (CheckBox)r.FindControl(df.FieldName);

//Use this function to set the Property value in the
object
//*** THIS DOES NOT WORK **
//Because the above line doesn't work we get the
following error here:
// "System.NullReferenceException: Object reference
not set to an instance of an object."
setProperty(prd, df.FieldName, cb.Checked);

}
else
{
//If the datatype is anything but a bit we need to
update from a TextBox

//*** THIS DOES NOT WORK ***
tb = (TextBox)r.FindControl(df.FieldName);

//Use this function to set the Property value in the
object
//*** THIS DOES NOT WORK **
//Because the above line doesn't work we get the
following error here:
// "System.NullReferenceException: Object reference
not set to an instance of an object."
setProperty(prd, df.FieldName, tb.Text);
}

}

//Save the changes to the Product Object
prd.Save();
}

}

//All of the changes have been made, Rebind the grid to the new data
BindData();
}

protected void btnCancel_Click(object sender, EventArgs e)
{
//To cancel all of the users changes we just need to Re-Bind the
grid without saving any changes
BindData();
}

public void setProperty(object obj, string field, object value)
{
//This function uses reflection to set the Object Property
//C# doesn't have an EVAL function like VB and our object don't
support
//Our objects do not support Object[PropertyName]=New value, only
Object.PropertyName=New value
//Therefore we need to use reflection. In VB we could use
EVAL("Object."+PropertyName+"=NewValue") but we are using C# obviously
PropertyInfo pi = obj.GetType().GetProperty(field);
pi.SetValue(obj, value, null);
}

#endregion
}
 
S

S. Justin Gengo

Larry,

I can get you started...

I have some code examples in a code library on my website. Go to
www.aboutfortunate.com, click the "Code Library" link at the top of the page
and then use the search box there to search for: "dynamic text box"

In the list of articles found you'll see a link to view a bit of sample code
that shows how to dynamically add text boxes to page and access those text
boxe's on post back. It's very simple, but I think it will provide you with
the starting point you need.

If you need more than that please feel free to email me.

--
Sincerely,

S. Justin Gengo, MCP
Web Developer / Programmer

www.aboutfortunate.com

"Out of chaos comes order."
Nietzsche
 
G

Guest

Hey Justin,

Your example really helped me! Not quite finished but it was a bit of a
breakthrough.

My page has a GridView on it. It has some formatting done to it in design
time so it looks good and behaves correctly, but it has no columns or data.
That all happens dynamically.

In my BuildGrid() function I do a Columns.Clear on the grid, then add the
columns dynamically. When I was doing this my viewstate was not
re-populating, the controls were not accessible.

However, from your example, instead of a GridView I created a PlaceHolder.
Then in my BuildGrid() function I do something like this:

GridView gvDynamicGrid = new GridView();
....do some formatting to the grid...
PlaceHolder1.Controls.Add(gvDynamicGrid)

Now, voila, my controls are repopulating from the viewstate and I can access
my users inputted values to update my business objects. That is fantastic.

There are a couple of issues I am still having though.

Issue #1
This is SO much slower. My grid was popping and now it's crawling. I am
assuming that because I am creating the GridView in memory, then formatting
it, then binding it must be taking up a whole lot more resource than already
having it preformatted and compiled and then just adding columns and binding.

It is such a signifigant difference than I really think I need to find a way
to repopulate those controls from within a static grid object on my page.
The Placeholder showed me it can work, however, I think I need to find a way
to get my controls back into my TemplateField columns, with the same tree
structure, without deleting and creating the entire GridView.

Do you have any idea how I would do that.

If you take a look at my second post it has a BuildGrid() function and you
can see how I was building it. I think I need to maintain that structure but
get it working.


Issue #2
When the users chooses a different group of fields on my page, then a
different grid is created. However, when the page posts back it tries to
re-instate the viewstate values back into the new, and different, grid. I
get an error:

"Failed to load viewstate. The control tree into which viewstate is being
loaded must match the control tree that was used to save viewstate during the
previous request. For example, when adding controls dynamically, the
controls added during a post-back must match the type and position of the
controls added during the initial request. "

Do you know how to get around this? I am guessing I need to clear the
viewstate or keep it from loading the grid? Of course I would have to keep
the viewstate of my other controls, just not the grid.

Any suggestions on controlling this?

Again, thanks a ton. I can see light at the end of the tunnel.
 
S

S. Justin Gengo

Larry,

I'm glad the sample code helped!

Issue #1 - When doing similar things I utilize the OnItemDatabound event of
the datagrid. From within that method you will have access to the grid row
as the data is bound to it, it fires for every row of the grid and you can
add dynamic items to the grid from there. That should be faster.

Issue #2 - Save the user's selection(s) to viewstate. Then check that those
value haven't changed before re-creating the grid.

--
Sincerely,

S. Justin Gengo, MCP
Web Developer / Programmer

www.aboutfortunate.com

"Out of chaos comes order."
Nietzsche
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top