Making a GridView footer data bound like a Row data - c#

I have an ASP.NET project created with C# that uses GridViews. The footer row contains a ratio of a total based on the column data over a set value from the data saying how many there should be. The user needs to be able to modify the second value and have the footer update. I am not able to find out how to do this or even if it is possible. I contemplated using another GridView underneath, but ensuring the column lines sync between the two is a nightmare. Any ideas?
Also, when I change the column data (using Edit/Update rows), the total is not updated when I click on Update, but does when I click on Edit again. Can anyone tell me why this is and how to update the total in the footer on Updating?

If you find that your edited fields are refreshed "one click behind" typically it's due to thinking you've rebound your GridView but actually haven't or you've rebound before making the Update.
With Gridview, a typical scenario whenever you place fields in a footer is to calculate it's values during databinding operations of the Page Lifecycle
It goes like this, your .aspx file defines some footer fields, typically some BoundField that has been converted to TemplateFields. Here's a snippet:
<asp:GridView ID="GridView1" runat="server" ShowFooter="true" ...>
<Columns>
<asp:BoundField DataField="Field1" HeaderText="Title" SortExpression="Field1" />
<asp:TemplateField>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("Field2") %>' ></asp:Label>
</ItemTemplate>
<FooterTemplate>
<asp:Label ID="FooterLabel1" runat="server" Text="" ></asp:Label>
</FooterTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
GridView Events in the code behind: This is generally what you need to do to populate footer fields based on changing Row Values. This is VB but you should be able to convert it easily enough.
// Create a variable at the Page level that will exist for
// the duration of the Page LifeCycle
Private FooterLabel1SubTotal as Double
// Initialize
Private Sub GridView1_DataBinding(sender As Object, e As EventArgs) Handles GridView1.DataBinding
FooterLabel1SubTotal = 0.0
End Sub
// Accumulate
Private Sub GridView1_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles GridView1.RowDataBound
If e.Row.RowType = DataRow Then
Dim Field2 as Label = e.Row.FindControl("Field2")
FooterLabel1SubTotal += Convert.ToDouble(Field2.Text)
End If
End Sub
// Populate Footer with formated value
Private Sub GridView1_DataBound(sender As Object, e As EventArgs) Handles GridView1.DataBound
FooterLabel1 = GridView1.FooterRow.FindControl("FooterLabel1")
FooterLabel1.Text = String.Format("{0:F2}", FooterLabel1SubTotal)
End Sub
Now if you use any of the Built in Editing features of the GridView, that will cause a Postback and will cause the GridView to rebind, which should recalculate the footer fields each time.

Related

ASP.NET DataGrid is empty

I have a ASP.NET datagrid which is on a user control. I have a main page which adds the user control ( sometimes multiple copies of the user control ) and restores them when a post back occurs.
The dataGrid has insert / edit / delete links. I can add multiple copies of the user control to the page and the insert / edit delete functionality works perfectly for each separate control.
Yesterday I added some property binding to the main page to which are unrelated to the user control using the format Text='<%# DocumentTitle %>'. Initially I couldn't get this to work until I added Page.DataBind(); to the main page's Page_Load method. At this point the property binding started working correctly but then I noticed the insert functionality had stopped working in the datagrid within each user control....I debugged and found that when the following line executes it finds the text fields in the controls within the dataGrid to be empty or "":
eInfo.Ref = ((TextBox)gvEG.FooterRow.FindControl("txtEmployeeName")).Text;
If I remove the page.DataBind() method from the main page then the property binding stops working but the dataGrid(s) in the userControl start working.
My question is two fold i) Why does the seemingly unrelated change effect the dataGrid and ii) what do I do to get this working?
I've added the relevant sections of my code below...or at least what I think are the relevant sections.
Default.aspx
<div class="general">
<asp:TextBox Width="488" runat="server" placeholder="Document Title" Text='<%# DocumentTitle %>'></asp:TextBox>
</div>
Default.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Create an empty user control for the first requirements section.
EmployeeSectionUserControl myUserControl1 = (EmployeeSectionUserControl )LoadControl("~/EmployeeSectionUserControl .ascx");
// Add it to the panel control holding all the user controls.
MainPanel.Controls.Add(myUserControl1);
DocumentTitle = "I am the default document title and I'm bound.";
}
else
{
// Do nothing
}
Page.DataBind();
}
EmployeeSectionUserControl.ascx
<asp:GridView ID="gvEG" runat="server" AutoGenerateColumns="False" CssClass="grid"
AlternatingRowStyle-CssClass="gridAltRow" RowStyle-CssClass="gridRow" ShowFooter="True"
EditRowStyle-CssClass="gridEditRow" FooterStyle-CssClass="gridFooterRow" OnRowCancelingEdit="gvEG_RowCancelingEdit"
OnRowCommand="gvEG_RowCommand" OnRowDataBound="gvEG_RowDataBound" OnRowDeleting="gvEG_RowDeleting"
OnRowEditing="gvEG_RowEditing" OnRowUpdating="gvEG_RowUpdating" DataKeyNames="Id" ShowHeaderWhenEmpty="true">
<Columns>
<asp:TemplateField HeaderText="Id" HeaderStyle-HorizontalAlign="Left" ControlStyle-Width="50px">
<ItemTemplate>
<%# Eval("Id")%>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Ref" HeaderStyle-HorizontalAlign="Left" ControlStyle-Width="90px">
<EditItemTemplate>
<asp:TextBox ID="txtEmployeeName" runat="server" Text='<%# Bind("Ref") %>'
Width="90px"></asp:TextBox>
</EditItemTemplate>
<FooterTemplate>
<asp:TextBox ID="txtEmployeeName" runat="server" Width="90px"></asp:TextBox>
</FooterTemplate>
EmployeeSectionUserControl.ascx.cs
protected void gvEG_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName.Equals("Insert"))
{
employeeInfo eInfo = new employeeInfo();
eInfo.Id = 999;// Convert.ToInt32(((TextBox)gvEG.FooterRow.FindControl("Id")).Text);
// If we're inserting from the EmptyDataTemplate( ie an empty table ) of the gridview then we need to retreive the data differently.
// So we perform a check on the gridView FooterRow and if it's null then we can assume it's an empty table.
if (gvEG.FooterRow == null)
{
TextBox referenceTxtBox = (((Control)e.CommandSource).NamingContainer).FindControl("txtEmployeeName") as TextBox;
eInfo.Ref = referenceTxtBox.Text;
}
else
{
eInfo.Ref = ((TextBox)gvEG.FooterRow.FindControl("txtEmployeeName")).Text;
eInfo.Need =
}
// Store Update and Re-bind data to grid.
}
}
Page.DataBind() calls DataBind on it's children, so it updates DocumentTitle in the text box but it also DataBinds your grid. I didn't see a DataSource set in your grid, like an EntityDataSource, so I am assuming you are doing the smart retrieving (and preparation) of the data yourself in code and set the DataSource yourself:
gvEg.DataSource = someCollection;
gvEg.DataBind();
On the get your are loading the user-control and probably call this DataBind with specifying the DataSource. It binds and then you call Page.DataBind() which probably also triggers another DataBind for gvEg but since DataSource is set it shows the same.
On the post back you shouldn't do a DataBind() before handling events. Your call of Page.DataBind() does that. It triggers a DataBind() without a DataSource. Then the rowCommand comes and checks for the TextBox in the Footer which is cleared due to a DataBind with no elements.
What you should do is:
You shouldn't use Page.DataBind(). If you do so, you need todo it at a moment when all DataSources are set correctly and it shouldn't be kicked of during a post back. In general, I would not recommend using it because it makes it more complex and it's probably only helping a bit if you haven't set up your application correctly.
In your case it's definitely not necessary. Your textBox is a server control that's not part of any binding (GridView, Repeater, ListView). So your textBox is immediately available in your code behind.
So you should:
Give the textBox an ID you can use like txtDocumentTitle
<asp:TextBox Width="488" ID="txtDocumentTitle" runat="server" placeholder="Document Title"></asp:TextBox>
Replace setting DocumentTitle (unless you need it for something else too) with:
txtDocumentTitle.Text = "I am the default document title and I'm bound.";
Remove Page.DataBind();
So access server controls you have access immediately since they are also properties in your page or control.

DataGrid and formatting data

I'm new to ASP.NET and don't really understand how to display a table with database data. The data is loaded in an object, where I have a list of objects.
Now I want to show a table with some columns, an image (path created from two fields in the object), an link (passing one field of the object), a textarea (id from object, value from object), a radiobutton (id from object).
How should I do this. I have tried binding a datagrid to the list of objects, and it works. But I don't want to show all data members, and I don't know how to create correct headers and the image and form controls.
ImageDataGridView.DataSource = tradeObj.Images;
ImageDataGridView.DataBind();
To stop the automatic creation of a column for each column in the data source, set:
ImageDataGridView.AutoGenerateColumns = false
Then you need to define a column for each column in the data source that you want to display - depending on the column you may want a bound column (you can control the format) or something more involved.
See http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.datagrid.columns.aspx for a sample and the different column types available
You can spefify your column and header using boundfield
<asp:boundfield datafield="yourColumn"
headertext="theHeaderText"/>
And you need to turn off:
ImageDataGridView.AutoGenerateColumns = false
To format values look at this link at msdn.
You need to use template field to customize your datagrid as per below
<asp:GridView ID="GridView1" runat="server">
<Columns>
<asp:TemplateField ShowHeader="True">
<ItemTemplate>
<asp:Image runat="server" ID="Image1"
ImageUrl="Enabled.gif" />
</ItemTemplate>
</asp:TemplateField>
......
......
</Columns>
</asp:GridView>
See this msdn link to work with template field in datagrid
Update
For iterate through every row of gridview you need to handle Rowdatabound event as per below.
protected void gridView_RowDataBound(Object sender, GridViewRowEventArgs e)
{
if(e.Row.RowType == DataControlRowType.DataRow)
{
Image imgCtrl = (Image) e.Row.FindControl("imgCtrl");
imgCtrl.ImageUrl = "you can apply any format of url here";
}
}
for more information of Rowdatabound event visit this link
Just set autogenerate to false on your datagrid/gridview control. You can then create a templateField column & drop a label or textbox control inside itemTemplate & bind them like
<asp:Label ID="lblMyColumn" Text="<%# Bind("YourColumnName") %>' runat="server"></asp:Label>
Alternatively you can also drop a boundField & set its properties
<asp:BoundField DataField="YourColumnName" HeaderText="Your Text" SortExpression="YourColumnName" />

ASP.NET 2.0 Gridview CheckBox column

I have a GridView that displays data on three columns. On the third column I have a CheckBox. I have two things that I need to achieve
CheckBox will be checked based on the column value (1,0)
If it is checked the rest of the two columns should display #### However the data for the two columns should remain in the database.
How can this be achieved?
Can I find the CheckBox on RowDataBound event and check the value and make the CheckBox checked and unchecked? How about making the other columns ####?
NEW Comments:
string str = ((System.Data.DataRowView)(e.Row.DataItem)).Row.ItemArray[2].ToString();
this helps to set the checkbox checked or not.
if checked is true I am trying the following.
((System.Data.DataRowView)(e.Row.DataItem)).Row.ItemArray[0] = "#####";
((System.Data.DataRowView)(e.Row.DataItem)).Row.ItemArray[1] = "#####";
((System.Data.DataRowView)(e.Row.DataItem)).Row.AcceptChanges();
It is displaying the gridview checkbox as checked but the column value is not changed to "####"
You can turn your item columns into TemplateColumns and do the following which will localize your code to the control level and you don't have to worry about all the searching. I pefer to never use the built in column types because there are usually future enhancements that require changing the columns to TemplateColumns anyways. It also gives you a lot of flexibiltiy on useage.
Here is an example:
<asp:GridView ID="grdYourGrid" runat="server" AutoGenerateColumns="False">
<Columns>
<asp:TemplateField HeaderText="YourField1">
<ItemTemplate>
<asp:Literal runat="server" ID="ltYourField1"
OnDataBinding="ltYourField1_DataBinding" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="YourField2">
<ItemTemplate>
<asp:Literal runat="server" ID="ltYourField2"
OnDataBinding="ltYourField2_DataBinding" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="YourCheckBoxField">
<ItemTemplate>
<asp:CheckBox runat="server" ID="chkYourCheckBoxField"
OnDataBinding="chkYourCheckBoxField_DataBinding" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Then in your codebehind implement each control's OnDataBinding:
protected void ltYourField1_DataBinding(object sender, System.EventArgs e)
{
Literal lt = (Literal)(sender);
lt.Text = (bool)(Eval("YourCheckBoxField")) ?
"##########" : Eval("YourField1");
}
protected void ltYourField2_DataBinding(object sender, System.EventArgs e)
{
Literal lt = (Literal)(sender);
lt.Text = (bool)(Eval("YourCheckBoxField")) ?
"##########" : Eval("YourField2");
}
protected void chkYourCheckBoxField_DataBinding(object sender, System.EventArgs e)
{
CheckBox chk = (CheckBox)(sender);
chk.Checked = (bool)(Eval("YourCheckBoxField"));
}
The advantages to doing it this way is your could replace code easily as it is all isolated and has no 'searching' for expected controls. I very rarely use the RowDataBound event because it makes you have to write specific code to look for the controls and it makes more sense to me to have the code localized to the control. If someone changes something they know there are ONLY affecting that one control instead of everything on the row possibly as a side effect. You could also use the <%# method and do the Evals right in the markup but I personally prefer to never have any code in the aspx markup at all.

Accessing the data or dataItem used to bind a gridview

I have a gridview for which I programmatically set the datasource and databind it to a collection of objects. For each row that is created I then use different methods in the fields to extract the relevant information from the object like this one:
<asp:TemplateField HeaderText="Aliases">
<ItemTemplate>
<%# ( (MyItem)Container.DataItem).Aliases.ToString() %>
</ItemTemplate>
</asp:TemplateField>
My problem is that in the OnRowDeleting method I would preferably like to be able to access that DataItem using e g MyGridView.Rows[e.RowIndex].DataItem or in other way. But I can’t find how to configure the Gridview to retain the DataItem. Is it possible to access the DataItem used and how would I configure it to do it? If that’s not possible can I access the values that are bind by the methods? Or do I have to go with plan B and rewrite the datasource object collection to a datatable and then use datakeysnames?
MyGridView.Rows[e.RowIndex].DataItem should generally work but I guess that you are probably relying the view-state for retaining grid data on post-backs. In such case, you will get the DataItem property as NULL.
Work-around can be to always re-bind the grid with actual data in each postback early in page life cycle (say page_load).
However, in your case, you can very well use DataKeyNames. Contrary to your belief, you don't need a DataTable for this property to work. For example, if your class has property named ItemId indicating the key for your object then you can use DataKeyNames="ItemId" in the markup and refer it in OnRowDeleting using Keys property of event arguments.
According to MSDN:
"The DataItem property is only available during and after the RowDataBound event of a GridView control."
Therefore, access the DataItem in the RowDataBound event:
Lets say you bind a List(Of Vehicle) to the grid:
Dim vehicles As List(Of Vehicle) = Vehicle.GetAll()
gvVehicles.DataSource = vehicles
gvVehicles.DataBind()
In the RowDataBound event access the DataItem:
Protected Sub gvVehicles_RowDataBound(sender As Object, e As GridViewRowEventArgs)
If e.Row.RowType = DataControlRowType.DataRow Then
Dim veh As Vehicle = TryCast(e.Row.DataItem, Vehicle)
If Not veh Is Nothing Then
Dim chkBox As CheckBox = CType(e.Row.FindControl("chkSelect"), CheckBox)
chkBox.Checked = True
End If
End If
End Sub
I'm aware this is very dated question at this point - however, I've just run into a similar issue and none of these answers resolved the issue; so I figured I'd post an alternative solution. In my scenario, the issue was on the OnSelectedIndexChanged event. So, it should theoretically hold true for OnRowDeleting but not necessarily on OnRowDeleted (depending on where exactly in the process the row is deleted).
My solution was to simply add a HiddenField for the data that I didn't want to be visible in the GridView, for example:
<asp:GridView ID="gvTutorGroups" runat="server" AutoGenerateColumns="False" DataSourceID="sqlTutorGroups" DataKeyNames="TTGP_Group_Code" AllowPaging="True" PageSize="8" EmptyDataText="You have no tutor groups to display." Style="margin: 0 auto; width: 870px;" OnRowDataBound="gvTutorGroups_RowDataBound" OnSelectedIndexChanged="gvTutorGroups_SelectedIndexChanged">
<Columns>
<asp:TemplateField >
<ItemTemplate>
<asp:HiddenField runat="server" ID="hfTTGPISN" Value="<%# Eval("TTGP_ISN") %>" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="TTGP_Group_Code" HeaderText="TG Code" />
<asp:BoundField DataField="PRPH_Title" HeaderText="Name" />
<asp:BoundField DataField="TTGP_Start_Date" HeaderText="Start Date" DataFormatString="{0:d}" />
<asp:BoundField DataField="TTGP_End_Date" HeaderText="End Date" DataFormatString="{0:d}" />
</Columns>
</asp:GridView>
Then I just used the FindControl method to access that field, like so:
protected void gvTutorGroups_SelectedIndexChanged(object sender, EventArgs e)
{
foreach (GridViewRow row in gvTutorGroups.Rows)
{
if (row.RowIndex == gvTutorGroups.SelectedIndex)
{
row.CssClass = "rowSelected";
DataRowView dataItem = (DataRowView)row.DataItem;
HiddenField hfTGIsn = (HiddenField)this.Parent.FindControl("hfTGisn");
hfTGIsn.Value = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
}
else
{
row.CssClass = "";
}
}
}
It's not an ideal solution, but it works.

problem with the nested gridview

I'm trying add a gridview dynamically (lets call this gridview2) to existing gridview (lets call this gridview1) added through .aspx page.
What i'm trying to do is: Depending upon the content of the row (more specifically invoice number displayed in the cell-1 with zero based indexing) displayed in "gridview1" i'm filtering data from a list and displaying the data in "gridview2".
I'm doing some terrible mistake somewhere or my approach should be fundamentally wrong.
Below is the code for gridview1 added in .aspx page.
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="ClickingBtn_Click" />
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataSourceID="SqlDataSource1">
<Columns>
<asp:BoundField DataField="Date Of Transaction"
HeaderText="Date Of Transaction" SortExpression="Date Of Transaction" />
<asp:BoundField DataField="Invoice Number" HeaderText="Invoice Number"
SortExpression="Invoice Number" />
<asp:BoundField DataField="totalAmount" HeaderText="totalAmount"
ReadOnly="True" SortExpression="totalAmount" />
</Columns>
</asp:GridView>
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings:ComponentDBConnectionString %>"
SelectCommand="SelectUserPreviousHistory" SelectCommandType="StoredProcedure">
<SelectParameters>
<asp:Parameter DefaultValue="duran" Name="userName" Type="String" />
</SelectParameters>
</asp:SqlDataSource>
This is fetching the data as shown here: here
And then I'm trying to add another gridview depending upon the unique invoice number displayed in each row by following code:
foreach (GridViewRow gridviewrow in GridView1.Rows)
{
GridView gridView2 = new GridView();
gridView2.AutoGenerateColumns = true;
String x = gridviewrow.Cells[1].Text; //IT FETCHES THE INVOICE NUMBER FROM EACH ROW
softwareTitlesList = SoftwareListRetrieve(); //lIST OF ALL THE SOFTWARE TITLES ADDED TO LIST
ArrayList titles = new ArrayList();
foreach (SoftwareTitles softwareTitle in softwareTitlesList)
{
if (softwareTitle.InvoiceNumber.Contains(x))
titles.Add(softwareTitle.SoftwareTitle); //ADDING ONLY THOSE TITLES THAT MATCH THAT PARTICULAR INVOICE NUMBER
}
gridView2.DataSource = titles;
gridView2.DataBind();
}
But nothing seems to be happening. What is wrong with this ?
Please help me
BTW I'm using asp.net/c# visualstudio 2010. And i'm not using LINQ in my project and the database is sql server 2005
Thanks in anticipation
There are quite a few problems with your approach.
You do create a new gridview in your server side code but you never add the gridview to the page or any other control on the page! It is effectively telling the .NET to create a gridview, bind it to the data but it is not telling .NET "where" to render the gridview on the page. Hence you don't see anything.
Dynamic controls - I am sorry if I am being presumptuous here but I think you haven't had much play with dynamic controls within ASP.NET. My whole hearted advice will be to not use them as far as you can. Dynamically created controls come with a HUGE baggage with respect to their state, their post back events and actually have to be added on every post back to the page. In a nutshell, if you add a button dynamically on the page as
Button btn = new Button();
btn.Text = "hi";
Page.Controls.Add(btn);
Then you must fire this code on every postback of the page. If during any postback this code doesn't get fired, the button will be missing when the client sees the page.
I can suggest some approaches to achieve what you are trying to do, however to be able to do that I would like to know
Where do you want the second gridview to appear in the page (i.e. embedded within the first one or outside of it)
When do you want to populate the second gridview? Is it something like the situation that the user clicks one row in the first gridview and then sees the relevant information in the second gridview?

Categories

Resources