GridView loses column contents during PostBack - asp.net

I am having an issue with the behavior of a GridView between post backs.
The real problem comes from a TemplateField I define in the markup at column[0] with a child CheckBox control. Things work fine for the first, and second search executions. However, at some point between the second execution and anything that causes a post back there after, I lose the contents of the TemplateField.
Its only the the contents of the column and not the whole column itself that gets removed. The TemplateField is present in the source and shows a formated column at position 0 of the table.
CODE:
protected void ExecuteSearch(object sender, EventArgs e)
{
if (lb_SelectedFields.Items.Count == 0) { return; } //if no selected fields
//Generates custom SQL query based on user inputs and column Selections
BuildQuery(); // sets txbSqlText.Text = to the SQL string
DataTable Table = SqlAdapter.Select(new System.Data.SqlClient.SqlCommand(txbSqlText.Text));
for (int i = gv_SearchResults.Columns.Count - 1; i > 0; i--)
{ gv_SearchResults.Columns.RemoveAt(i); } //removes all the columns except[0]
foreach (ListItem Item in lb_SelectedFields.Items) //adds all the user defined columns
{
//Column object that is able to find the column definition
Column Col = ColumnsBasedOnFocus.FindColumName(Item.Value);
if (Col.Type == "HyperLink") { gv_SearchResults.Columns.Add(CreateHyperLinkField(Col)); }
else { gv_SearchResults.Columns.Add(CreateBoundColumn(Col, true)); } //true is if the column is visable
}
gv_SearchResults.DataSource = Table;
gv_SearchResults.DataBind();
}
ASP.NET:
<asp:GridView ID="gv_SearchResults" runat="server" GridLines="None" CellSpacing="0"
CellPadding="0" AutoGenerateColumns="false" CssClass="TABLE_LIGHTBLUE" Width="100%">
<HeaderStyle CssClass="TABLE_LIGHTBLUE_HEADERROW" />
<Columns>
<asp:TemplateField ItemStyle-Width="30" ItemStyle-Wrap="false">
<HeaderTemplate>
<center>
<asp:Button ID="btn_SelectAll" runat="server" OnClick="SelectAll" Text="All" CssClass="TEXT_SMALL" />
<asp:CheckBox ID="chk_Placeholder" runat="server" Visible="false" /></center>
</HeaderTemplate>
<ItemTemplate>
<center>
<asp:CheckBox ID="chk_Select" runat="server" Visible="true" />
<asp:Label ID="lbl_AssetGID" runat="server" Visible="false" Text='<%# Bind("i_GID") %>' /></center>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>

Found a link that describes a similar situation.
https://connect.microsoft.com/VisualStudio/feedback/details/104994/templatefield-in-a-gridview-doesnt-have-its-viewstate-restored-when-boundfields-are-inserted#details
They describe a bug in ASP.Net code that fails to properly manage view states with template fields in dynamically generated grid views.

Basically TemplateFields can't be properly restored from ViewState, and if you modify the ASPX-declared columns programmatically, it can't create them from the declarations either.
The only solution I could get working was to create a new class deriving from TemplateField that in the constructor set the ItemTemplate to an ITemplate-derived class, which means having to define the template programmatically instead of declaratively.
You can also rebind the gridview on each postback but that's its own can of worms.

Related

retrieve the value of hidden column of grid view on submit button click

How can I retrieve the value of hidden column of grid view on submit button click. I searched on internet but mostly found methods on row button click.
<asp:GridView ID="GridBilling">
<Columns>
<asp:BoundField Visible="false" DataField="ID"/>
<asp:TemplateField HeaderText="S No." ItemStyle-Width="8%">
<ItemTemplate>
<%#Container.DataItemIndex+1 %>
</ItemTemplate>
</asp:TemplateField>
... other fields ommitted
</Columns>
</asp:GridView>
<br />
<div style="width:100%"><asp:Button ID="btn_SubmitGrid" runat="server" Text="SUBMIT" OnClick="btn_SubmitGrid_Click" /></div>
protected void btn_SubmitGrid_Click(object sender, EventArgs e)
{
int iRowCnt = 0;
foreach (GridViewRow row in GridBilling.Rows)
{
dr = dt_mnthlybilling.NewRow();
dt_mnthlybilling.Rows.Add(dr);
iRowCnt += 1;
}
}
If you set visible to false on the hidden field, then you can't access it in the back-end code. This is because the data doesn't get rendered. However you can do do by accessing the dataKeys. Set the DataKeyNames on GridView. e.g:
<asp:GridView ID="GridBilling" runat="server" DataKeyNames="ID">
Then in code you can retrieve the value as
GridBilling.DataKeys[ROW_INDEX].Value; // This should return an object. I believe you can set to multiple keys (per row).
Alternatively you hide the column with css (on both header and item)
<asp:BoundField DataField="ID" HeaderStyle-CssClass="hidden" ItemStyle-CssClass="hidden" />
Css add:
<style type="text/css">
.hidden
{
display: none;
}
</style>
In back-end code, the bound column can be accessed as
GridBilling.Rows[*ROW_INDEX*].Cells[0].Text; // If you have a html control you can replace text with FindControl("control_name")
Also in your cs code you have a call to NewRow(). If you accessing a row then you should be calling row. eg:
int iRowCnt = GridBilling.Rows.Count;
foreach (GridViewRow row in GridBilling.Rows)
{
// row.Cells[0].Text
dt_mnthlybilling.Rows.Add(row);
}
(It's been years since I've done web forms, so the syntax may be slightly incorrect.)

How to add TemplateField created in .aspx markup to GridView column in code behind?

Basically, I want a button that adds a new column with my controls (i.e. TextBox, DropDownList) to my GridView.
Already seems simple enough with this:
protected void btn_AddColumn_Click(object sender, EventArgs e)
{
TemplateField MyTemplateField = new TemplateField();
gv_SupplierPrices.Columns.Add(MyTemplateField);
}
Say I want to add this markup:
<asp:TemplateField>
<ItemTemplate>
<table style="width:100%;">
<tr>
<td><asp:TextBox ID="tbox_ItemPrice" runat="server"></asp:TextBox></td>
</tr>
</table>
</ItemTemplate>
</asp:TemplateField>
Here's the problem. How do I use a TemplateField I created in markup on my .aspx page and add it to my new column on code behind? I don't know and don't want to create my TemplateField on code behind because I will not be able to create elaborate or fancy markups. They will just look like controls that are next to each other. It's very ugly.
P.S. If anyone is curious as to what I'm actually making, I'm basically turning my GridView into an Excel-like gridsheet. New columns represent a supplier and each row represents an item the supplier is selling. They want it that way because it's easier to compare prices side-by-side whilst they're filling it up.
Update: I already figured it out. I can simply call the column of my GridView from codebehind that represents the TemplateField I created in markup, then I add it.
<asp:GridView ID="gv_SupplierTable" runat="server" AutoGenerateColumns="False"
DataKeyNames="Id" DataSourceID="TestTable_DS">
<Columns>
<asp:BoundField DataField="Id" HeaderText="Id" InsertVisible="False"
ReadOnly="True" SortExpression="Id" />
<asp:BoundField DataField="AccountType" HeaderText="AccountType"
SortExpression="AccountType" />
<asp:TemplateField AccessibleHeaderText="MyTemplateFieldCreated in Markup">
<ItemTemplate>
<table style="width:100%;">
<tr>
<td><asp:TextBox ID="tbox_ItemPrice" runat="server"></asp:TextBox></td>
</tr>
</table>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Then in codebehind, I can access that column that represents the TemplateField created in markup and add it again on button click, like a new copy:
protected void btn_AddColumn_Click(object sender, EventArgs e)
{
DataControlField newColumn = this.gv_SupplierTable.Columns[2];
//this column at index #2 from my GridView represents
the TemplateField created in markup since the columns
at index #0 and index #1 are DataBound.
gv_SupplierTable.Columns.Add(newColumn);
}
You can toggle the visibility of each column.
gv_SupplierPrices.Columns[10].Visible = true;
In your position, I'd consider biting the bullet and investigating creating templated fields programmatically. It would be a nuisance, but once you were done, you'd be done. Of course, you'd want to abstract them away into a helper class or separate control.
Here are a couple examples on dynamically adding a column to your GridView:
protected void Page_Init(object sender, EventArgs e)
{
btnAddColumn.Click += new EventHandler(btnAddColumn_Click);
BoundField temp = new BoundField();
temp.DataField = "Source";
temp.HeaderText = "Source";
temp.SortExpression = "Source";
gv.Columns.Add(temp);
}
void btnAddColumn_Click(object sender, EventArgs e)
{
TemplateField temp = new TemplateField();
temp.HeaderText = txtNewColumn.Text;
txtNewColumn.Text = string.Empty;
gv.Columns.Add(temp);
}
ASPX:
<div>
<asp:TextBox ID="txtNewColumn" runat="server" />
<asp:Button ID="btnAddColumn" runat="server" Text="Add Column" />
</div>
<asp:GridView ID="gv"
runat="server"
AutoGenerateColumns="False"
DataSourceID="dsExceptions">
<Columns>
<asp:BoundField DataField="Message" HeaderText="Message" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="dsExceptions"
runat="server"
SelectMethod="GetExceptions"
TypeName="WebApplication.Data.MyDataContext" EnablePaging="True"
SelectCountMethod="GetExceptionCount" >
<SelectParameters>
<asp:Parameter Name="maximumRows" Type="Int32" />
<asp:Parameter Name="startPageIndex" Type="Int32" />
<asp:Parameter Name="startRowIndex" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>
My ObjectDataSource is just getting a collection of Exceptions I create, so you can ignore that part of the code.
So I'm adding a column via code in the Page_Init handler, as well as adding a column with the header text set to the text in txtNewColumn. One thing about this, though, is that if you refresh the page, it will lose the columns you've added with the button on the page. You'll want to add some code in the button click event handler to store the new columns, so they're always in the GridView when it reloads. Since it sounds like the columns represent a vendor, you might just want to add a form for someone to add a vendor to your system. The vendor gets saved in a database/datastore somewhere, and in the Page_Init, you get all the vendors from that datastore and loop over them, creating the template fields for the vendors. It would be something like this:
// I am just creating a list here, you would pull the vendor names in from the
// datastore here instead.
var vendors = new List<string> { "Vendor A", "Vendor B", "Vendor C" };
foreach (var vendor in vendors)
{
TemplateField temp = new TemplateField();
temp.HeaderText = vendor;
gv.Columns.Add(temp);
}

How to put more control in each single cell of the grid view Control

I am using grid view control in my web application.Here i need to make label ,image and button controls into each single cell of grid view control.how to put controls into single cell.
You can use TemplateField to place multiple controls inside single cell:
<asp:GridView ID="grdView">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="btn" />
<asp:Label ID="lbl" />
....
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Later in your code behind you can retrieve them using their id, you first have to get the reference to the individual row:
for (int i = 0; i < grdView.Rows.Count; i++)
{
if (grdView.Rows[i].RowType == DataControlRowType.DataRow)
{
Button objBtn = (Button)grdView.Rows[i].FindControl("btn"); //btn must match with the id defined in aspx page
Label objLbl = (Label)grdView.Rows[i].FindControl("lbl"); .....
}
}

Using FindControl to get GridView in a Content Page

I would like to find a GridView Control within a separate class and I am having issues doing so. I even tried placing my code in the aspx.cs page to no avail. I keep getting Object reference not set to an instance of an object. I'm sure there is a simple step I'm missing, but in my research I cannot seem to find anything.
Aspx code
<asp:GridView ID="GridView1" EnableViewState="true"
runat="server"
BackColor="White" BorderColor="#CC9966"
BorderStyle="None" BorderWidth="1px" CellPadding="4" Width="933px"
onrowdatabound="GridView1_RowDataBound"
onrowdeleting="GridView1_RowDeleting"
onrowediting="GridView1_RowEditing"
onrowupdating="GridView1_RowUpdating"
onsorting="GridView1_Sorting"
AllowSorting="true"
AutoGenerateColumns="False"
PersistedSelection="true" onrowcancelingedit="GridView1_RowCancelingEdit">
<EditRowStyle Font-Size="Small" Width="100px" />
<FooterStyle BackColor="#FFFFCC" ForeColor="#330099" />
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:LinkButton runat="server" ID="EditLinkButton" CausesValidation="True"
Text="Edit" CommandName="Edit"/>
<asp:LinkButton runat="server" ID="DeleteLinkButton" CausesValidation="False"
Text="Delete" CommandName="Delete"/>
</ItemTemplate>
<EditItemTemplate>
<asp:LinkButton runat="server" ID="UpdateLinkButton" CausesValidation="True"
Text="Update" CommandName="Update"/>
<asp:LinkButton runat="server" ID="CancelLinkButton" CausesValidation="False"
Text="Cancel" CommandName="Cancel"/>
</EditItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
.cs code
protected void Page_Load(object sender, EventArgs e) {
SetDirectory();
System.Web.UI.Page page = (System.Web.UI.Page)System.Web.HttpContext.Current.Handler;
GridView GridViewCopy = (GridView)page.FindControl("GridView1");
Log.WriteLine("SortBindGrid: GridView Row Count: " +
GridViewCopy.Rows.Count, Log.DEBUG_LEVEL.TERSE);
return;
}
I've tried a few variations of using MainContent_GridView for the find to get and Master.FindControl with all the same result.
In one of your comments you state that the GridView isn't on the Master Page, so is it safe to assume that it's on a page that uses a Master Page? And therefore it must be in a ContentPlaceholder control?
The key issue is that FindControl method only looks for direct children (emphasis added):
This method will find a control only if the control is directly contained by the specified container; that is, the method does not search throughout a hierarchy of controls within controls.
So you either need to:
Search for the control within the correct ContentPlaceholder control, rather than from Page.
Loop through each of the Controls in Page.Controls recursively until you find the control you're after.
An example of 2:
private Control FindControlRecursive(Control rootControl, string controlID)
{
if (rootControl.ID == controlID) return rootControl;
foreach (Control controlToSearch in rootControl.Controls)
{
Control controlToReturn =
FindControlRecursive(controlToSearch, controlID);
if (controlToReturn != null) return controlToReturn;
}
return null;
}
Once you've got your control, you should cast it using as and then check for null just in case it's not quite what you were expecting:
var gridView = FindControlRecursively(Page, "GridView1") as GridView
if (null != gridView) {
// Do Stuff
}
Don't get the page from HttpContext if you are already within the page. Instead, is there a control you can use FindControl from? Instead of use page, use:
parentControl.FindControl("GridView1") as GridView;
Instead. There is an issue with finding the grid from the page level, and using a lower level control closer to the grid will have better success.
Brian got it right but he forgot the essential part.
You won't be able to do use his code unless you add this code on top of your HTML-Code of the file where you want to use it.(Page.aspx)
<%# MasterType VirtualPath="~/Master/Site.master" %>
then you can use the code Brian suggested:
GridView grid = this.Master.FindControl("GridView1");
Edit:
If you want to use the gridview from within another class in the same file i would use the following:
Add this to the class created when you make the page
public partial class YourPageName: System.Web.UI.Page
{
public static Gridview mygrid = this.GridviewnameOFYourASPX
...
}
And to your custom class add this in your method
YourPageName.mygrid.(The changes you want to make);

Optionally display a text in place of GridView when the GridView has no rows

I have a basic GridView that displays a list of tasks to do (just an example)
<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" AllowSorting="True" >
<Columns>
<asp:BoundField DataField="todo" HeaderText="To Do" ReadOnly="True" SortExpression="todo" />
<asp:BoundField DataField="byDate" HeaderText="By When" ReadOnly="True"
SortExpression="byDate" />
the data source is specified within the aspx page and it is a result set from a stored procedure
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="..."
SelectCommand="pToDoList" SelectCommandType="StoredProcedure">
</asp:SqlDataSource>
So the rendered page presents tasks as rows of data (a row per task)
My question is
When there is not data returned by stored procedure the page remains blank. I would like to have a text instead saying for example: "nothing to do today"
How to do that? Thank you
Use the EmptyDataTemplate:
<asp:gridview id="CustomersGridView"
datasourceid="CustomersSqlDataSource"
autogeneratecolumns="true"
runat="server">
<emptydatarowstyle backcolor="LightBlue"
forecolor="Red"/>
<emptydatatemplate>
<asp:image id="NoDataImage"
imageurl="~/images/Image.jpg"
alternatetext="No Image"
runat="server"/>
No Data Found.
</emptydatatemplate>
</asp:gridview>
Example from http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.gridview.emptydatatemplate.aspx
there is a property "EmptyDataText" that allows you to specify text if the gridview is empty
Add a Label to the page, call it ErrorLabel, then do something like this in the DataBound event of the GridView:
protected void GridView1_DataBound(object sender, EventArgs e)
{
if(GridView1.Rows.Count <= 0) ErrorLabel.Text = "Nothing to do today";
else ErrorLabel.Visible = false;
}
Although a Google search would have probably gotten that for you.
One option is to use the Footer to display this message. During the ItemDataBound, if the ItemType is the footer, check if the DataSource is empty and if it is, display the message.
Or if you want to work declaritvely in the ASPX you can use the EmptyDataTemplate and add your message there.
I would simply would add a label control noDataLabel containing the text "Nothing to do today", and add some code in the Page_Load event:
if (GridView1.Rows == 0)
{
GridView1.Visible = false;
}
else
{
noDataLabel.Visible = false;
}
It is a bit crude, but it beats have embedded C# inside your webform HTML. If there is no data, and the form is rendered, the GridView is simply ignored.

Resources