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

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);
}

Related

Extract a BoundField to a Label

I have a DetailsView which works fine using a data access layer and a query string. However, I'd like to extract one of the fields and use it as the text in a label to go above the DetailsView as a title to that page.
Is this possible? And if so, how?
This is an abstract of the DetailsView:
<Fields>
<asp:BoundField DataField="bandname" HeaderText="Band" />
<asp:BoundField DataField="contactname" HeaderText="Contact" />
<asp:BoundField DataField="county" HeaderText="County" />
</Fields>
and the code behind:
if (Request.QueryString.Count != 0)
{
int id = int.Parse(Request.QueryString["bandid"]);
dtvBand.Visible = true;
List<Band> bandDetails = new List<Band> { BandDAL.AnonGetAllBandDetails(id) };
dtvBand.DataSource = bandDetails;
dtvBand.DataBind();
}
What I'd like to do is take the data in the first BoundField row and make it the text of a label. Pseudocode:
Label1.Text = (<asp:BoundField DataField="band")
I would not try to find the text on the DetailsView but in it's DataSource. You could use the DataBound event which is triggered after the DetailsView was databound, so it's ensured that the DataItem exists.
It depends on the Datasource of your DetailsView. Often it is a DataRowView. You have to cast it, then you can access it's column:
protected void DetailsView1_DataBound(Object sender, EventArgs e)
{
DetailsView dv = (DetailsView)sender;
string yourText = (string)((DataRowView)dv.DataItem)["ColumnName"];
Label1.Text = yourText;
}
If it's not a DataRowView use the debugger to see what dv.DataItem actually is.
I managed to achieve what I wanted using:
string titletext = dtvBand.Rows[0].Cells[1].Text.ToString();
dtvBand.Rows[0].Visible = false;
lblBand.Text = titletext;
It takes the first row of the DetailsView, puts it above the rest in a Label so it can be formatted as a header, then hides the first row of the DetailsView.
How about using a TemplateField as what Tim mentioned:
<Fields>
<asp:TemplateField>
<ItemTemplate>
<asp:Label ID="lblName" runat="server" Text='<%# Eval("Band") %>' />
</ItemTemplate>
</asp:TemplateField>
</Fields>

Asp.net gridview edit without editbutton

I have regular asp.net gridview,and i want to enable editmode in each row,and also without editbutton (like excel grid).
Edited data i want to save to my database by clicking "Post" button outside the grid(one button for whole grid).
How can i reach it?
To achieve this, you are going to have to use ItemTemplates for each column with textboxes in them as the control..
ASP
<asp:TemplateField HeaderText="Heading Title" SortExpression="Heading Title">
<ItemTemplate>
<asp:TextBox ID="tbTextbox" runat="server" Width="65px" Text='<%# Bind("ColumnNameYouWantToView") %>'></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
After this is set up properly, you will want the post button. You can either put it in the grid or outside the grid. I use both, but here is the one inside the grid as a footer.
ASP
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="btnView" runat="server" Text="View" OnClick="btnView_Click" Width="40px" />
</ItemTemplate>
<FooterTemplate>
<asp:Button ValidationGroup="UPDATE" ID="btnUpdate" OnClick="btnUpdate_Click" runat="server" Text="Update" Width="50px"></asp:Button>
</FooterTemplate>
<FooterStyle HorizontalAlign="Center" VerticalAlign="Middle" Width="50px" />
</asp:TemplateField>
So far, what I have found that works best is using a foreach statement in your button click. The biggest flaw with this idea is that it will update every single row. It works, but if you only change a single row at a time, it will update all of them. I have my pager set to 10, so 10 rows are always updated (unless you are just searching for a single record and update it, the only that single record is updated).
Code Behind C#
protected void btnUpdate_Click(object sender, EventArgs e)
{
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["databaseConnection"].ConnectionString);
conn.Open();
//finds the controls within the gridview and updates them
foreach (GridViewRow gvr in gvGridViewName.Rows)
{
string ID = (gvr.FindControl("lblId") as Label).Text.Trim();//finds the control in the gridview
string anotherControl = ((TextBox)gvr.FindControl("tbTextBox")).Text.Trim();//finds the textbox in the gridview
//Your update or insert statements
}
This is how I do it. You can also look into Real World Grids but I haven't had much luck with this as I would always get an error if a textbox was empty. This however, is suppose to be "smart" enough to just update the rows that have been changed, but again, I didn't have much luck of doing it this way. Hope this helps!

GridView loses column contents during PostBack

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.

Passing multiple argument through CommandArgument of Button in Asp.net

I have a gridview with multiple rows, each has a Update button and I need to pass 2 values when someone clicks on Update button.
Aside from packing the arguments inside CommandArgument separated by commas (archaic and not elegant), how would I pass more than one argument?
<asp:LinkButton ID="UpdateButton" runat="server" CommandName="UpdateRow" CommandArgument="arg_value" Text="Update and Insert" OnCommand="CommandButton_Click" ></asp:LinkButton>
As a note, the values can't be retrieved from any controls on the page, so I am presently not seeking any design solutions.
You can pass semicolon separated values as command argument and then split the string and use it.
<asp:TemplateField ShowHeader="false">
<ItemTemplate>
<asp:LinkButton ID="lnkCustomize" Text="Customize" CommandName="Customize" CommandArgument='<%#Eval("IdTemplate") + ";" +Eval("EntityId")%>' runat="server">
</asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
at server side
protected void gridview_RowCommand(object sender, GridViewCommandEventArgs e)
{
string[] arg = new string[2];
arg = e.CommandArgument.ToString().Split(';');
Session["IdTemplate"] = arg[0];
Session["IdEntity"] = arg[1];
Response.Redirect("Samplepage.aspx");
}
After poking around it looks like Kelsey is correct.
Just use a comma or something and split it when you want to consume it.
#Patrick's answer is a good idea, and deserves more credit!. You can have as many data items as you want, they are all separated, and can be used client side if necessary.
They can also be added declaratively rather than in code. I just did this for a GridView like this:
<asp:TemplateField HeaderText="Remind">
<ItemTemplate>
<asp:ImageButton ID="btnEmail"
data-rider-name="<%# ((Result)((GridViewRow) Container).DataItem).Rider %>"
data-rider-email="<%# ((Result)((GridViewRow) Container).DataItem).RiderEmail %>"
CommandName="Email" runat="server" ImageAlign="AbsMiddle" ImageUrl="~/images/email.gif" />
</ItemTemplate>
</asp:TemplateField>
In the RowCommand, you do this:
void gvMyView_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "Email")
{
var btnSender = (ImageButton)e.CommandSource;
var riderName = btnSender.Attributes["data-rider-name"];
var riderEmail = btnSender.Attributes["data-rider-email"];
// Do something here
}
}
So much cleaner than hacking all the values together with delimiters and unpacking again at the end.
Don't forget to test/clean any data you get back from the page, in case it's been tampered with!
My approach is using the attributes collection to add HTML data- attributes from code behind. This is more inline with jquery and client side scripting.
// This would likely be done with findControl in your grid OnItemCreated handler
LinkButton targetBtn = new LinkButton();
// Add attributes
targetBtn.Attributes.Add("data-{your data name here}", value.ToString() );
targetBtn.Attributes.Add("data-{your data name 2 here}", value2.ToString() );
Then retrieve the values through the attribute collection
string val = targetBtn.Attributes["data-{your data name here}"].ToString();
A little more elegant way of doing the same adding on to the above comment ..
<asp:GridView ID="grdParent" runat="server" BackColor="White" BorderColor="#DEDFDE"
AutoGenerateColumns="false"
OnRowDeleting="deleteRow"
GridLines="Vertical">
<asp:BoundField DataField="IdTemplate" HeaderText="IdTemplate" />
<asp:BoundField DataField="EntityId" HeaderText="EntityId" />
<asp:TemplateField ShowHeader="false">
<ItemTemplate>
<asp:LinkButton ID="lnkCustomize" Text="Delete" CommandName="Delete" CommandArgument='<%#Eval("IdTemplate") + ";" +Eval("EntityId")%>' runat="server">
</asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
</asp:GridView>
And on the server side:
protected void deleteRow(object sender, GridViewDeleteEventArgs e)
{
string IdTemplate= e.Values["IdTemplate"].ToString();
string EntityId = e.Values["EntityId"].ToString();
// Do stuff..
}
Either store it in the gridview datakeys collection, or store it in a hidden field inside the same cell, or join the values together. That is the only way. You can't store two values in one link.
If you want to pass two values, you can use this approach
<asp:LinkButton ID="RemoveFroRole" Text="Remove From Role" runat="server"
CommandName='<%# Eval("UserName") %>' CommandArgument='<%# Eval("RoleName") %>'
OnClick="RemoveFromRole_Click" />
Basically I am treating {CommmandName,CommandArgument} as key value. Set both from database field. You will have to use OnClick event and use OnCommand event in this case, which I think is more clean code.

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