I am developing asp.net webform application with gridview control. I have grid control which is having more than 20 rows. Each rows having 4 columns like Ordered, Ex-Received, Current-Receiving and Pending. Here, Current-Receiving column having input box to enter the qty. Once entered the qty, Pending Columns will show the Pending qty in the corresponding row after calculate done (Pending Qty = Ordered - (Ex-Received + Current-Receiving) . And in footer sum of pending and sum of receiving qty will show. It working fine if i have 4 or 5 rows. If i have more than and above 15 rows, it takes time to calculate. Please find below my code which is written in current-receiving input box text_changed event. Any idea to make it faster ? In future, rows count will increase like 50 or more.
Dim textBox1 As TextBox = CType(sender, TextBox)
Dim dt As New DataTable
Dim RowIndex As Integer = CType(CType(sender, TextBox).NamingContainer, GridViewRow).RowIndex
Dim glblRcvdRolls As Label =grd.Rows(RowIndex).FindControl("lblRcvdRolls")
'
Dim glblOrdRolls As Label = grd.Rows(RowIndex).FindControl("lblOrdRolls")
Dim glblPendWt As Label = grd.Rows(RowIndex).FindControl("lblPendWt")
If textBox1.Text = "" Then textBox1.Text = 0
If glblRcvdRolls.Text = "" Then glblRcvdRolls.Text = 0
If glblOrdRolls.Text = "" Then glblOrdRolls.Text = 0
'Each Row Calculations
glblPendWt.Text = Convert.ToInt32(glblOrdRolls.Text) - (Convert.ToInt32(textBox1.Text) + Convert.ToInt32(glblRcvdRolls.Text))
If Convert.ToInt32(glblPendWt.Text) < 0 Then
glblPendWt.ForeColor = System.Drawing.Color.Yellow
Else
glblPendWt.ForeColor = System.Drawing.Color.White
End If
' Total Row Calculations
Dim mPendQty As Double = ViewState("PndQty")
Dim mRcvdQty As Double = ViewState("RcvdQty")
If IsNumeric(glblPendWt.Text) Then mPendQty += Convert.ToDouble(glblPendWt.Text)
If IsNumeric(textBox1.Text) Then mRcvdQty += Convert.ToDouble(textBox1.Text)
ViewState("PndQty") = mPendQty
ViewState("RcvdQty") = mRcvdQty
grd.FooterRow.Cells(5).Text = mRcvdQty
grd.FooterRow.Cells(6).Text = mPendQty
'Setfocus after postback - otherwise focus will set somewhere in the form.
If RowIndex <=grd.Rows.Count Then
Dim grdNRText As TextBox = grd.Rows(RowIndex + 1).FindControl("txtRcvdQty")
'grdNRText.Focus()
ScriptManager.RegisterStartupScript(Me, Me.GetType(), "focus", "document.getElementById('" + grdNRText.ClientID + "').focus();document.getElementById('" + grdNRText.ClientID + "').select();", True)
Else
'btnInward.Focus()
ScriptManager.RegisterStartupScript(Me, Me.GetType(), "focus", "document.getElementById('" + btnInward.ClientID + "').focus();document.getElementById('" + btnInward.ClientID + "').select();", True)
End If
Exit Sub
I am betting that your main issue and problem is the the code you have, but in fact that on page load, you re-binding the grid.
The fact that you started adopting JavaScript to set the focus is the tell tell sign here.
You should not in ANY case re-bind the grid each time. Remember, for ANY post-back, the page load event will fire. And there should be no need to persist the two total values you have.
So, if you have a data source on the page - I would remove it - load the GV in code.
You should be able to use the server side .Focus() method of the control (and I see at one time you had or were attempting to use that, and now are using JavaScript. You should NOT have to do that.
So, in the last 100, or in fact 200 web pages I had made, the page load event MUST check the isPostBack, and ONLY ONE time do you run ANY code, and ANY code to bind.
Here is a example grid, and I have TWO columns that can post back (text boxes), one is Qty, and the other is Price (I used Qty in code - it was displaed as nights).
Take a close look. If you ahve any combo box binding code - again, it should ONLY run one time - in row data bound event.
And if you want, you can drop the whole thing into a update panel - but it not required.
So, say this markup:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID"
ShowFooter="True" CssClass="table">
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField HeaderText="Nights" SortExpression="Nights">
<ItemTemplate>
<asp:TextBox ID="txtNights" runat="server" Text='<%# Bind("Nights") %>'
OnTextChanged="txtNights_TextChanged" AutoPostBack="true"
TextMode="Number">
</asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Price" SortExpression="Price">
<ItemTemplate>
<asp:TextBox ID="txtPrice" runat="server" Text='<%# Bind("Price") %>'
OnTextChanged="txtPrice_TextChanged" AutoPostBack="true"
TextMode="Number"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Tamount"
ItemStyle-HorizontalAlign="Right" FooterStyle-HorizontalAlign="Right" >
<ItemTemplate>
<asp:Label ID="lblAmount" runat="server" Text='<%# Bind("Tamount") %>'></asp:Label>
</ItemTemplate>
<FooterTemplate >
<asp:Label ID="LblTotal" runat="server" Text="0"></asp:Label>
</FooterTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Ok, now our code - remember - ONLY bind the GV ONE time, NEVER again.
So the grid looks like this:
Our code to load the Grid
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadGrid()
End If
End Sub
Sub LoadGrid()
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand(
"SELECT top 10 * FROM tblHotels WHERE Description is not null ORDER BY HOtelName", conn)
conn.Open()
Dim rstData As New DataTable
rstData.Load(cmdSQL.ExecuteReader)
GridView1.DataSource = rstData
GridView1.DataBind()
SumRows()
End Using
End Using
End Sub
Ok, that's it - don't re- bind the gv.
Now, here are the two code stubs for the two text boxes (qty, price), and auto postback = true.
Protected Sub txtNights_TextChanged(sender As Object, e As EventArgs)
Dim tQty As TextBox = sender
Dim gRow As GridViewRow = tQty.NamingContainer
Call RowCalc(gRow)
' we were in Qty control - set focus to next (price) control
Dim tPrice As TextBox = gRow.FindControl("txtPrice")
tPrice.Focus()
SumRows()
End Sub
Protected Sub txtPrice_TextChanged(sender As Object, e As EventArgs)
Dim tPrice As TextBox = sender
Dim gRow As GridViewRow = tPrice.NamingContainer
Call RowCalc(gRow)
SumRows()
' we in Price - we jump to next row below (or cycle to first row)
Dim gRowNext As GridViewRow = Nothing
If gRow.RowIndex + 1 < GridView1.Rows.Count Then
gRowNext = GridView1.Rows(gRow.RowIndex + 1)
Else
' jump to top row
gRowNext = GridView1.Rows(0)
End If
Dim tQty As TextBox = gRowNext.FindControl("txtNights")
tQty.Focus()
End Sub
Sub RowCalc(gRow As GridViewRow)
Dim tQty As TextBox = gRow.FindControl("txtNights")
Dim tPrice As TextBox = gRow.FindControl("txtPrice")
Dim lblAmount As Label = gRow.FindControl("lblAmount")
lblAmount.Text = Nz(tQty.Text, 0) * Nz(tPrice.Text, 0)
End Sub
In fact, we have MORE code to set the focus to the next control then quite much the actuall work.
And since I had TWO controls that can change, then I built a common routine for both controls to call (RowCalc).
Sub RowCalc(gRow As GridViewRow)
Dim tQty As TextBox = gRow.FindControl("txtNights")
Dim tPrice As TextBox = gRow.FindControl("txtPrice")
Dim lblAmount As Label = gRow.FindControl("lblAmount")
lblAmount.Text = Nz(tQty.Text, 0) * Nz(tPrice.Text, 0)
End Sub
And, of course the code to sum the rows - total into footer is this:
But NOTE carefully - I don't have to persist that total value - it will and should survive post-backs.
Sub SumRows()
' sum value - set footer total value
Dim MyTotal As Double
For Each gRow As GridViewRow In GridView1.Rows
Dim lblAmount As Label = gRow.FindControl("lblAmount")
MyTotal += Nz(lblAmount.Text, 0)
Next
Dim lblTotal As Label = GridView1.FooterRow.FindControl("lblTotal")
lblTotal.Text = MyTotal
End Sub
And I used a nz() function - since I did a abone head move, and allowed nulls as the default for those number rows - not 0.
Not a huge deal, but I have this for nz()
Public Function Nz(ByVal Value As Object, Optional ByVal MyDefault As Object = 0) As Object
If Value Is Nothing OrElse IsDBNull(Value) OrElse (Value.ToString = "") Then
Return MyDefault
Else
Return Value
End If
End Function
Now, with 20, or even 30 rows - it is butter smooth, and I can't tell difference speed wise with 5 rows, or 30. And you should find the same results.
So, my spider sense suggests that you have some wonkey code running each time on page load. Keep in mind that any button, any post-back WILL run the page load event each time.
So, ensure that you only EVER load up the GV one time, and don't re-bind, as that will for sure make this run slow.
Try some of the above ideas and code - but do check/address the first page load and only load/bind the GV one time.
Edit: Don't allow nor use a post back - do this 100% client side
So, of course for really amazing performance, the solution of course is to do this 100% client side code - in the browser. This will be instant, and better yet, no post back required at all.
So, dump the auto post backs, and now our Qty, and Price controls look like this - with a js code on change event.
<asp:TemplateField HeaderText="Nights">
<ItemTemplate>
<asp:TextBox ID="txtNights" runat="server" Text='<%# Bind("Nights") %>'
OnTextChanged="txtNights_TextChanged" onchange="myjscalc(this,'txtNights')"
TextMode="Number">
</asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Price" SortExpression="Price">
<ItemTemplate>
<asp:TextBox ID="txtPrice" runat="server" Text='<%# Bind("Price") %>'
OnTextChanged="txtPrice_TextChanged" onchange="myjscalc(this,'txtPrice')"
TextMode="Number"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Tamount"
ItemStyle-HorizontalAlign="Right" FooterStyle-HorizontalAlign="Right" >
<ItemTemplate>
<asp:Label ID="lblAmount" runat="server" Text='<%# Bind("Tamount") %>'></asp:Label>
</ItemTemplate>
<FooterTemplate >
<asp:Label ID="LblTotal" runat="server" Text="0"></asp:Label>
</FooterTemplate>
</asp:TemplateField>
And now the script for this is this:
<script>
function myjscalc(ctl, tid) {
var Qty = $('#' + ctl.id.replace(tid, 'txtNights'))
var Price = $('#' + ctl.id.replace(tid, 'txtPrice'))
var tAmount = $('#' + ctl.id.replace(tid, 'lblAmount'))
var n = Qty.val() * Price.val()
tAmount.text(n.toFixed(2))
// now total
var MyTotal = 0.0
var rowCount = $("#<%=GridView1.ClientID %> tr").length;
rowCount = rowCount - 2 // remove header row, and footer from count
for (var iRow = 0; iRow < rowCount; iRow++) {
tAmount = $('#GridView1_lblAmount_' + iRow)
MyTotal = MyTotal + + tAmount.text()
}
$('#GridView1_LblTotal').text(MyTotal.toFixed(2))
}
</script>
So, elimination of the post-back and doing this 100% client side ALSO fixes the focus issue. And any change will update instant - including the final total.
Related
so I have this code for grid view in asp.net
<asp:GridView ID="grdNewClaim" CssClass="table table-bordered table-condensed" GridLines="None" runat="server" AutoGenerateColumns="false">
<Columns>
<asp:BoundField DataField="claim_type" HeaderText="Claim Type" />
<asp:BoundField DataField="purpose_desc" HeaderText="Purpos/Description" />
<asp:BoundField DataField="currency" HeaderText="Currency" />
<asp:BoundField DataField="amount" HeaderText="Amount" />
<asp:TemplateField HeaderText="Currency Rate">
<ItemTemplate>
<asp:TextBox ID="txtChangeCurrRate" runat="server"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Converted Rate">
<ItemTemplate>
<asp:TextBox ID="txtChangeConvertedRate" runat="server"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
And this code to add the new row for the gridview
Private Sub addClaimProgrammatically()
Dim dt As New DataTable
Dim dr As DataRow
If grdNewClaim.Rows.Count = 0 Then
dt.Columns.Add("claim_type")
dt.Columns.Add("purpose_desc")
dt.Columns.Add("currency")
dt.Columns.Add("amount")
dt.Columns.Add("txtChangeCurrRate")
dt.Columns.Add("txtChangeConvertedRate")
dr = dt.NewRow
dr.Item("claim_type") = cmbClaimtype.Text
dr.Item("purpose_desc") = txtPurpDesc.Text
dr.Item("currency") = cmbCurrency.Value
dr.Item("amount") = txtAmmount.Text
dr.Item("txtChangeCurrRate") = lblCurrencyrate.Text
dr.Item("txtChangeConvertedRate") = lblconvertedRateMYR.Text
dt.Rows.Add(dr)
End If
grdNewClaim.DataSource = dt
grdNewClaim.DataBind()
End Sub
But for some reason the items for for both textbox just blank like this :
So any idea why? Because if I change the textbox to <asp:BoundField/> it works just fine like this
[![enter image description here][2]][2]
EDIT : create another rows in gridview
My markup is exactly the same as you. So this is my code behind. This code works just fine, however the only problem is my textbox for first row went blank. The second row is alright.
NOTE : I didn't do this on page_load.
Private Sub LoadGrid()
grdNewClaim.DataSource = dtGrd
grdNewClaim.DataBind()
End Sub
Private Sub addClaimProgrammatically()
Dim dr As DataRow
If grdNewClaim.Rows.Count = 0 Then
dtGrd.Columns.Add("claim_type")
dtGrd.Columns.Add("purpose_desc")
dtGrd.Columns.Add("currency")
dtGrd.Columns.Add("amount")
dtGrd.Columns.Add("CurrencyRate")
dtGrd.Columns.Add("ConvertedRate")
dr = dtGrd.NewRow
dr.Item("claim_type") = cmbClaimtype.Text
dr.Item("purpose_desc") = txtPurpDesc.Text
dr.Item("currency") = cmbCurrency.Value
dr.Item("amount") = txtAmmount.Text
dr.Item("CurrencyRate") = lblCurrencyrate.Text
dr.Item("ConvertedRate") = lblconvertedRateMYR.Text
dtGrd.Rows.Add(dr)
ElseIf grdNewClaim.Rows.Count > 0 Then
dtGrd.Columns.Add("claim_type")
dtGrd.Columns.Add("purpose_desc")
dtGrd.Columns.Add("currency")
dtGrd.Columns.Add("amount")
dtGrd.Columns.Add("CurrencyRate")
dtGrd.Columns.Add("ConvertedRate")
For i = 0 To grdNewClaim.Rows.Count - 1
dr = dtGrd.NewRow
dr.Item("claim_type") = grdNewClaim.Rows(i).Cells(1).Text
dr.Item("purpose_desc") = grdNewClaim.Rows(i).Cells(2).Text
dr.Item("currency") = grdNewClaim.Rows(i).Cells(3).Text
dr.Item("amount") = grdNewClaim.Rows(i).Cells(4).Text
dr.Item("CurrencyRate") = grdNewClaim.Rows(i).Cells(5).Text
dr.Item("ConvertedRate") = grdNewClaim.Rows(i).Cells(6).Text
dtGrd.Rows.Add(dr)
Next
dr = dtGrd.NewRow
dr.Item("claim_type") = cmbClaimtype.Text
dr.Item("purpose_desc") = txtPurpDesc.Text
dr.Item("currency") = cmbCurrency.Value
dr.Item("amount") = txtAmmount.Text
dr.Item("CurrencyRate") = lblCurrencyrate.Text
dr.Item("ConvertedRate") = lblconvertedRateMYR.Text
dtGrd.Rows.Add(dr)
End If
LoadGrid()
End Sub
Protected Sub btnAddClaim_Click(sender As Object, e As EventArgs) Handles btnAddClaim.Click
addClaimProgrammatically()
End Sub
The values just went missing
Thank you very much for your time !
When you use a boundfield, it works because you CAN SPECIFY which column to display.
bound field only works due to this:
Computers do not "guess" or "assume" what data column to use here.
But, when you use a templated column, and a standard control, then you need to use a expression that defines which column to use.
eg this:
<asp:TemplateField HeaderText="Currency Rate">
<ItemTemplate>
<asp:TextBox ID="txtChangeCurrRate" runat="server"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
so, in above, ask this question:
Where in above does the markup KNOW which column to use?
So, you have to use a binding expression, since TextBox does not have a "boundfield" property to tell it which column to use.
Try like this:
<asp:TemplateField HeaderText="Currency Rate">
<ItemTemplate>
<asp:TextBox ID="txtChangeCurrRate" runat="server"
Text = '<%# Eval("Currency") %>'
></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
So, in above, we would be using column "Currency" for the text (or you can use "amount" or "purpose_desc" or any column from the data source.
So, your example does not work, since with a templated textbox, you have to specify the column to use - (by some how and some way). So, use the above binding expression.
Edit: data does not persist - only can add one row.
Correct - you need to save that table between each post-back, and NOT re-create the table from scratch each time, else you lose previous rows.
So, the code has to look like this:
NOTE how I defined dt at the class level (right before page load).
And note how I "restore" this dt in page load EACH time.
So, your code will become this
Markup like this:
<asp:GridView ID="grdNewClaim" CssClass="table table-bordered table-condensed"
GridLines="None" runat="server" AutoGenerateColumns="false"
ShowHeaderWhenEmpty="true" >
<Columns>
<asp:BoundField DataField="claim_type" HeaderText="Claim Type" />
<asp:BoundField DataField="purpose_desc" HeaderText="Purpos/Description" />
<asp:BoundField DataField="currency" HeaderText="Currency" />
<asp:BoundField DataField="amount" HeaderText="Amount" />
<asp:TemplateField HeaderText="Currency Rate">
<ItemTemplate>
<asp:TextBox ID="txtCRateZoo" runat="server"
Text = '<%# Eval("txtChangeCurrRate") %>'>
</asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Converted Rate">
<ItemTemplate>
<asp:TextBox ID="txtConvertedRate" runat="server"
Text = '<%# Eval("txtChangeCurrRate") %>' >
</asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
And code now like this:
Dim dt As New DataTable
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
CreateTable()
LoadGrid()
Session("dt") = dt
Else
' restore table
dt = Session("dt")
End If
End Sub
Sub CreateTable()
dt.Columns.Add("claim_type", GetType(String))
dt.Columns.Add("purpose_desc")
dt.Columns.Add("currency", GetType(Double))
dt.Columns.Add("amount", GetType(Double))
dt.Columns.Add("txtChangeCurrRate", GetType(Double))
dt.Columns.Add("txtChangeConvertedRate", GetType(Double))
End Sub
Sub LoadGrid()
grdNewClaim.DataSource = dt
grdNewClaim.DataBind()
End Sub
Private Sub addClaimProgrammatically()
Dim dr As DataRow
dr = dt.NewRow
dr.Item("claim_type") = cmbClaimtype.Text
dr.Item("purpose_desc") = txtPurpDesc.Text
dr.Item("currency") = cmbCurrency.Value
dr.Item("amount") = txtAmmount.Text
dr.Item("txtChangeCurrRate") = lblCurrencyrate.Text
dr.Item("txtChangeConvertedRate") = lblconvertedRateMYR.Text
dt.Rows.Add(dr)
LoadGrid()
End Sub
Edit2: allow edits of two text box - save back to table.
So, just add this to the code:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
CreateTable()
LoadGrid()
Session("dt") = dt
Else
' restore table
dt = Session("dt")
' restore grid to dt
GridToTable
End If
End Sub
Sub GridToTable()
' move any and all possile updates to grid back to our table dt
For Each gRow As GridViewRow In grdNewClaim.Rows
Dim OneRow As DataRow = dt.Rows(gRow.RowIndex)
Dim txtCurrRate As TextBox = gRow.FindControl("txtCRateZoo")
Dim txtConvertRate As TextBox = gRow.FindControl("txtConvertedRate")
OneRow("txtChangeCurrRate") = txtCurrRate.Text
OneRow("txtChangeConvertedRate") = txtConvertRate.Text
Next
End Sub
What we do is send any changes back to the dt.
This means, that any button click etc on that page will shuffle gv back to dt.
This also means that if you have a button click to jump or move on to the next page/url, then session("dt") will have the correct data.
but, since we doing this on page load, then don't use a link button, or a button with post-back url to jump to the next page (just use regular button + code behind, and say:
Response.Redirect("url to next page to jump to")
So, with above, user can now freely modify the two columns, and they will be sent back to the dt.
This also means you can loop/process the dt in code.
Say we drop a button on the form, and then behind that button we do this:
Protected Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
For Each OneRow As DataRow In dt.Rows
Debug.Print(OneRow("currency"))
Debug.Print(OneRow("txtChangeCurrRate"))
' etc . etc.
Next
End Sub
So, we don't even have to hit the original grid, and our dt will be always updated in our session(). So, we now can work with table in code - not have to use the grid. As such, then we could have a button "continue" to move on to the next page - as along as we follow above setup, then dt will always have the latest data and edits upon a post-back.
I have a standard Gridview which is populated from a SQLDataSource. The Gridview will always have 17 rows. Can anyone please give me an example of how to manually insert rows into it at designated row spots? For example, insert a new row into rows 3 and 5.
Thanks
Well, it not at all clear why say inserting at row 5, or at row 8 is important here?
Remember, when we used desktop PC - single user, or say used punch cards?
Then order of data entered DID matter. But, we now don't use punched cards, and thus order of such data does not really matter from a database point of view. I mean, if you have multiple users entering data, and I insert at row 5, then what happens if 3 other uses also did the same. Now that record is in the 8th position. So, introduction of a "order" or some "position" NEEDS some further context here - in other words what is the goal here?
when working with a database, the FIRST rule is that data does NOT have order. And if YOU need some kind of order, say like date of entry, or even say order of new records added, then you the developer needs to design that into your software.
Now, having stated the above? Sure, there are class problems or say UI interface types of issues in which you might have say a order of 5 boxes, and you need to offer the user the ability to re-order those 5 items in ANY order you wish.
Ok, so having stated above?
Well, it not clear how the records were entered in the first place. and it not clear HOW the record that exists in the 5th position is supposed to be the 5th record.
Ok, now regardless of the above?
The trick to adding rows, or deleting rows, or inserting data? Do this at the data level, and NOT at the gridview level. This not only separates out the UI layer, and the data layer? It also saves world poverty and boatloads of complex code.
So, lets assume we have a column called "myorder". And if you don't have this column in the table, then you really quite much need to add it, since as I stated, some data order does not exist by magic, but in fact has to be "designed" and "managed" by YOU THE developer!!!
So, lets take a simple gv like this:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table borderhide" >
<Columns>
<asp:TemplateField HeaderText="First" ><ItemTemplate>
<asp:TextBox ID="txtFirst" runat="server" Text='<%# Eval("FirstName") %>' Width="80px">
</asp:TextBox>
</ItemTemplate></asp:TemplateField>
<asp:TemplateField HeaderText="Last"><ItemTemplate>
<asp:TextBox ID="txtLast" runat="server" Text='<%# Eval("LastName") %>' Width="80px">
</asp:TextBox>
</ItemTemplate></asp:TemplateField>
<asp:TemplateField HeaderText="Hotel"><ItemTemplate>
<asp:TextBox ID="txtHotel" runat="server" Text='<%# Eval("HotelName") %>'></asp:TextBox>
</ItemTemplate></asp:TemplateField>
<asp:TemplateField HeaderText="Description" ><ItemTemplate>
<asp:TextBox ID="txtDescription" runat="server" Text='<%# Eval("Description") %>'
TextMode="MultiLine" Rows="3" Columns="45"
></asp:TextBox>
</ItemTemplate></asp:TemplateField>
<asp:TemplateField HeaderText="Active" ItemStyle-HorizontalAlign="Center" ><ItemTemplate>
<asp:CheckBox ID="chkActive" runat="server" Checked='<%# Eval("Active") %>' />
</ItemTemplate></asp:TemplateField>
</Columns>
</asp:GridView>
Ok, and our code to load the grid. and as noted, we will persit the data source for the grid, since we need to do those inserts.
So, our code to load:
Dim rstData As New DataTable
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadData()
LoadGrid()
Else
rstData = ViewState("rstData")
End If
End Sub
Sub LoadData()
Using conn As New SqlConnection(My.Settings.TEST4)
Dim strSQL = "SELECT * FROM tblHotelsA ORDER BY MyOrder"
Using cmdSQL As New SqlCommand(strSQL, conn)
conn.Open()
rstData.Load(cmdSQL.ExecuteReader)
End Using
End Using
End Sub
Sub LoadGrid()
rstData.DefaultView.Sort = "MyOrder"
GridView1.DataSource = rstData
GridView1.DataBind()
RowCount.Value = rstData.Rows.Count
ViewState("rstData") = rstData
End Sub
and we now have this:
So, now we have that add button. That button will add the row to the grid, and prompt us for the location (what row to insert at).
so, the button looks like this:
<asp:Button ID="cmdSave" runat="server" Text="Save Changes" style="float:left" CssClass="btn"/>
<asp:Button ID="cmdAddRow" runat="server" Text="+Add New" style="float:right" CssClass="btn"
OnClientClick="return askwhatrow(this);"
/>
<asp:HiddenField ID="WhatRow" runat="server" ClientIDMode="Static"/>
<asp:HiddenField ID="RowCount" runat="server" ClientIDMode="Static"/>
<script>
function askwhatrow(btn) {
MyRowCount = $('#RowCount').val()
strMsg = "There are row 1 to " + MyRowCount + "\n" +
"What row to insert new record?"
strAns = prompt(strMsg)
if (strAns === null) {
return false
}
if ((strAns < 1) || (strAns > MyRowCount) ) {
alert("only 1 to " + MyRowCount + " is allowed")
return false
}
// ok, set the row insert location, and run our server side buttion
$('#WhatRow').val(strAns)
return true
}
</script>
So, when we click on add row, we get this prompt:
I of course entered say 2 (or your 5 for your example).
the js code will prompt the user, but of couse if the user hits cancel, then the button code (code behind server) will not run. The code to add the row of data now looks like this:
Protected Sub cmdAddRow_Click(sender As Object, e As EventArgs) Handles cmdAddRow.Click
' add new row to grid at location choosen by user:
GridToTable() ' save any possbile edits by user
Dim InsertLocation As Integer = WhatRow.Value
For Each OneRow In rstData.Rows
If Int(OneRow("MyOrder")) >= InsertLocation Then
OneRow("MyOrder") += 1
End If
Next
' setup new row - some defaults
Dim NewRow As DataRow = rstData.NewRow
NewRow("MyOrder") = InsertLocation
NewRow("Active") = False
rstData.Rows.Add(NewRow)
LoadGrid()
End Sub
Again, note how VERY easy it is to add that row of data. That's because we add the row into the table, and then simple re-bind the grid to display that new record. In other words, don't try to manipulate the gv, but ONLY manipulate the data!!! (that way we save world poverty and you starving from having to write too much code).
So, after we do the above, we see this:
So, with above grid? You can tab around (almost like excel). You can edit any row, make any changes you want.
We then have that ONE save button. All that does is send gv data back to the table, AND THEN IN ONE operation sends the data back to the database.
The code looks like:
Protected Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
GridToTable()
Using conn As New SqlConnection(My.Settings.TEST4)
Dim strSQL = "SELECT * FROM tblHotelsA"
Using cmdSQL As New SqlCommand(strSQL, conn)
conn.Open()
Dim da As New SqlDataAdapter(cmdSQL)
Dim daU As New SqlCommandBuilder(da)
da.Update(rstData)
End Using
End Using
End Sub
Sub GridToTable()
' send all data from gv to local table
For Each gvRow As GridViewRow In GridView1.Rows
Dim pkID As Integer = GridView1.DataKeys(gvRow.RowIndex).Item("ID")
Dim OneRow As DataRow = rstData.Select("ID = " & pkID).FirstOrDefault
OneRow("FirstName") = CType(gvRow.FindControl("txtFirst"), TextBox).Text
OneRow("LastName") = CType(gvRow.FindControl("txtLast"), TextBox).Text
OneRow("HotelName") = CType(gvRow.FindControl("txtHotel"), TextBox).Text
OneRow("Description") = CType(gvRow.FindControl("txtDescription"), TextBox).Text
OneRow("Active") = CType(gvRow.FindControl("chkActive"), CheckBox).Checked
Next
End Sub
So in above, we send all edits, all additions (and if you have or add a delete button to each row, then even deletions are ALL SEND back to the database with the above simple few lines of code. As noted, this was and is possible since we persisted the rstData that drives the GV.
and really nice? Well, for 30+ years, every user on the planet who used Excel, or used any accounting system, or any other computer software? They can bounce around in that grid, make changes. And then with one simple save button send the whole she-bang back to the database.
I have 2 GridViews with separate IDs
I need the backend code to update the one being viewed when a button is clicked.
` Protected Sub savestatus(sender As Object, e As EventArgs)
Dim btn As Button = TryCast(sender, Button)
Dim row As GridViewRow = CType(((CType(sender, Button)).NamingContainer), GridViewRow)
Dim rowindex As Integer = row.RowIndex
Dim code As String = GridView1.DataKeys(row.RowIndex).Values(0).ToString()
Dim type As Int32 = GridView1.DataKeys(row.RowIndex).Values(1)
Dim statusid As Integer
Dim checkLocked, checkerror As CheckBox
' For Each row As GridViewRow In GridView1.Rows
checkLocked = CType(GridView1.Rows(rowindex).FindControl("lock"), CheckBox)
checkerror = CType(GridView1.Rows(rowindex).FindControl("error"), CheckBox)
If checkerror.Checked Then ' error
statusid = 2
End If
If checkLocked.Checked Then
statusid = 3
End If`
How do I make the GridView1 a variable depending on which grid view the button is pressed in.
Ok, it would have helped a lot to at least show the button and a few rows of the gridview markup.
There are about 10 ways to do this. (really !!!).
However, in your case, two check boxes, and you need actions to occur when a check box is changed – AND say change the other one!!
Now I am using two check boxes – but it could be a text box or whatever I change.
So, say I have this grid markup
Some columns + TWO un-bound check boxes.
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false">
<Columns>
<asp:BoundField DataField="ID" HeaderText="ID" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" />
<asp:BoundField DataField="City" HeaderText="City" />
<asp:BoundField DataField="Province" HeaderText="Province" />
<asp:TemplateField HeaderText="Good">
<ItemTemplate>
<asp:CheckBox ID="chkGood" runat="server"
AutoPostBack="true"
OnCheckedChanged="chkGood_CheckedChanged"
MyRowID ='<%# Container.DataItemIndex %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Bad">
<ItemTemplate>
<asp:CheckBox ID="chkBad" runat="server"
AutoPostBack="true"
OnCheckedChanged="chkBad_CheckedChanged"
MyRowID ='<%# Container.DataItemIndex %>' />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Ok, and now the code to load the grid:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack = False Then
Using cmdSQL As New SqlCommand("SELECT ID, HotelName, City, Province from tblHotels",
New SqlConnection(My.Settings.Test3))
cmdSQL.Connection.Open()
GridView1.DataSource = cmdSQL.ExecuteReader
GridView1.DataBind()
End Using
End If
End Sub
And thus we have this:
Ok, so far - very simple.
Now note CLOSE at the markup for the two check boxes.
And while dropping a button or whatever on a normal form - you can then double click to JUMP to the code behind event/stub?
Well, for buttons or whatever you drop INSIDE of a grid, you can't double click on the control to create + jump to the code behind stub.
But, WHILE in the markup, you can start typing the event, and you get this:
Note VERY careful how the intel-sense pops up a option to create the event. So click on that option. Nothing seems to happen, but NOW we get a code stub behind.
So, we have this code stub for the chkOk event:
Protected Sub chkGood_CheckedChanged(sender As Object, e As EventArgs)
Dim ckBox As CheckBox = sender
Dim RowID As Integer = ckBox.Attributes.Item("MyRowID")
Dim gvRow As GridViewRow = GridView1.Rows(RowID)
If ckBox.Checked = True Then
' do whatever if true - say un-check the "bad" check box
Dim ckBoxBad As CheckBox = gvRow.FindControl("chkBad")
ckBoxBad.Checked = False
Else
' code here if the user just un-checked the "good" check box
End If
End Sub
Note a few things:
We pick up the button click - then shove it into a checkbox control. This is just a lot easier to get the check box value, and our CUSTOM MyRowID
(and this works if it was a button for example).
We then get the custom made up Attribute we added called "MyRowID"
MyRowID ='<%# Container.DataItemIndex %>'
You can see the expression in the Markup - it passes the current row id. Sometimes, I'll pass other values from the row and you can do this like this:
<asp:CheckBox ID="chkBad" runat="server"
AutoPostBack="true"
OnCheckedChanged="chkBad_CheckedChanged"
MyRowID ='<%# Container.DataItemIndex %>'
MyPKID = '<%# Eval("ID") %>' />
So in above, I pass both RowID and a custom MyPKID (so the Eval() expression can be used to pass any valid data row avaialble at binding time. Its often handy then having to grab and mess with a data row - you JUST grab the button from sender - and you don't care about gridview or anything else to get a few extra values. (just a FYI tip). So for example, I REALLY don't want the PK row id as the first row. So I could remove it and STILL use the above idea to PASS the pk row id - all columns can be used - even if a control is NOT in the grid - as long as the column exists during the data binding process - you can grab it.
So, now we pick up the current GridRow - and we are free to modify whatever we want on that row.
In my simple example, we pick up the OTHER check box - and un-check if it was checked. But we could say update other things on that row.
And I did the same for the chkBad check box. And I have really the same as the first chkBox code stub. Eg this:
Protected Sub chkBad_CheckedChanged(sender As Object, e As EventArgs)
Dim ckBox As CheckBox = sender
Dim RowID As Integer = ckBox.Attributes.Item("MyRowID")
Dim gvRow As GridViewRow = GridView1.Rows(RowID)
If ckBox.Checked = True Then
' user checked the bad box, un-check the good one
Dim ckBoxGood As CheckBox = gvRow.FindControl("chkGood")
ckBoxGood.Checked = False
Else
' code here if the user just un-checked the "bad" check box
End If
End Sub
So in above we just hard right past the GridView bult in events.
So in above, if you check one box and the other is checked - we un-check it. Needless to say, I would use a button list, or a checkbox list, and that above code would of course then not be required. But it still a good example on how to pluck/get the current row. And then get/pluck controls from that row.
Note that for the first 3 rows (the databound), you can NOT use findControl, and they are referenced using the gvRow.Cells(0) (starting at 0 to N columns. So no findcontrol is required for these databound columns or autogenerated ones. They do NOT have a name - you have to use number starting at 0 in the cells collection. Of course for "templated" controls that we added as per above? Then you do in fact use findcontrol as per above.
I have a gridview that I need to disable the "Edit" linkbutton when it's not the current week. I have accomplished this in my code, but when I got that to work, it took away the update and cancel functionality, giving me a Object reference not set to an instance of an object error when i press update or cancel. I'll post the entire code, but I want to point out this is the code that makes the disabling work and the code that I can comment out and get the update and cancel functions to work again:
'Disable edit button if not the current week
For i As Integer = 0 To GridView1.Rows.Count - 1
Dim lbl As Label
lbl = CType(GridView1.Rows(i).FindControl("lblWeekStart"), Label)
If lbl.Text <> currentDay Then
GridView1.Rows(i).Cells(0).Enabled = False
End If
Next
More specifically, it errors out on this line b/c of lbl.Text:
If lbl.Text <> currentDay Then
But here's the entire sub routine:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'Get User's Name
Dim lblFtrSubmitter As Label = GridView1.FooterRow.FindControl("lblFtrSubmitter")
lblFtrSubmitter.Text = Context.User.Identity.Name
'Get Time Recorded
Dim lblFtrTimeRecorded As Label = GridView1.FooterRow.FindControl("lblFtrTimeRecorded")
lblFtrTimeRecorded.Text = Date.Now
'Get The Sunday of Current Week and put into label
Dim lblFtrWeekStart As Label = GridView1.FooterRow.FindControl("lblFtrWeekStart")
Dim myDate As Date = DateTime.Today
Dim dayDiff As Integer = myDate.DayOfWeek - DayOfWeek.Sunday
Dim currentDay As Date = myDate.AddDays(-dayDiff) 'Sunday
For i = 1 To 7
Console.WriteLine(currentDay)
'Do something with current day
currentDay = myDate.AddDays(-myDate.DayOfWeek)
lblFtrWeekStart.Text = currentDay
Next
'Disable edit button if not the current week
For i As Integer = 0 To GridView1.Rows.Count - 1
Dim lbl As Label
lbl = CType(GridView1.Rows(i).FindControl("lblWeekStart"), Label)
If lbl.Text <> currentDay Then
GridView1.Rows(i).Cells(0).Enabled = False
End If
Next
End If
End Sub
Here's the .aspx code for the template field that contains the linkbuttons:
<asp:TemplateField ShowHeader="False">
<EditItemTemplate>
<asp:LinkButton ID="lnkUpdate" runat="server" CausesValidation="True" CommandName="Update"
Text="Update" ></asp:LinkButton>
<asp:LinkButton ID="lnkCancel" runat="server" CausesValidation="False" CommandName="Cancel"
Text="Cancel" ></asp:LinkButton>
</EditItemTemplate>
<FooterTemplate>
<asp:Button ID="btnAdd" runat="server" Text="ADD" CommandName="Insert" OnClientClick="return confirm('Are you sure you want to add this record?');" />
</FooterTemplate>
<ItemTemplate>
<asp:LinkButton ID="lnkEdit" runat="server" CausesValidation="False" CommandName="Edit"
Text="Edit" ></asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
Any help is greatly appreciated!
The problem here is this line is returning Nothing
lbl = CType(GridView1.Rows(i).FindControl("lblWeekStart"), Label)
Hence the following line will thrown an exception because lbl = Nothing
If lbl.Text <> currentDay Then
Looking at the template code it appears there is nothing named "lblWeekStart" available to find. This means FindControl is return Nothing as expected. You need to switch the code to look for the correct ID or change the place where you are searching for "lblWeekStart"
I have a simple usercontrol, with a datalist, and checkboxes inside.
<asp:DataList ID="DataListDroits" runat="server" DataKeyField="droit_id" DataSourceID="SqlDroits">
<ItemTemplate>
<asp:HiddenField ID="HiddenFieldDroitID" runat="server" Value='<%# Eval("droit_id") %>' />
<asp:CheckBox ID="CheckBoxDroit" runat="server" Text='<%# Eval("droit_label") %>' />
</ItemTemplate>
</asp:DataList>
I check them using code behind in the usercontrol :
Public Sub CheckRole(ByVal role As Integer)
For Each dliOrganisme As DataListItem In Me.DataListOrganismes.Items
Dim DataListDroits As DataList = dliOrganisme.FindControl("DataListDroits")
If DataListDroits IsNot Nothing Then
For Each dliDroit As DataListItem In DataListDroits.Items
If role = CInt(CType(dliDroit.FindControl("HiddenFieldDroitID"), HiddenField).Value) Then
Dim CheckBoxDroit As CheckBox = dliDroit.FindControl("CheckBoxDroit")
CheckBoxDroit.Checked = True
End If
Next ' DataListDroits
End If
Next ' DataListItem
End Sub
And in the page_load of the calling webform :
Dim CheckBoxesRoles1 As ASP.organisme_checkboxesroles_ascx = Me.FormViewRubrique.FindControl("CheckBoxesRoles1")
Dim rolesCoches As New List(Of Integer)
Dim cmdRoles As New SqlCommand("SELECT droit_id FROM o_droit_rubrique WHERE rubrique_id = #rubrique", conn)
cmdRoles.Parameters.AddWithValue("rubrique", Request.QueryString("rid"))
Dim rdrRoles As SqlDataReader = cmdRoles.ExecuteReader
While rdrRoles.Read
CheckBoxesRoles1.CheckRole(rdrRoles("droit_id"))
End While
rdrRoles.Close()
... and yet, they are not checked.
But if I do this :
Protected Sub Page_LoadComplete(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.LoadComplete
Dim CheckBoxesRoles1 As ASP.organisme_checkboxesroles_ascx = Me.FormViewRubrique.FindControl("CheckBoxesRoles1")
If CheckBoxesRoles1 IsNot Nothing Then
For Each role As Integer In CheckBoxesRoles1.CheckedRoles
Response.Write("role : " & role & "<br>")
Next
End If
End Sub
I tells me they are...
I'm going mad here ! Why does it tells me they are checked while they obviously are not ?
Well... for one thing, you aren't checking if your checkboxes are checked, all you're doing is outputting the value of "role". What exactly are you expecting here?
Two suggestions:
1) Set the Checked property of your CheckBox in the aspx like so:
<asp:CheckBox ID="CheckBoxDroit" runat="server" Text='<%# Eval("droit_label") %>' Checked='<%# (Eval("droit_id") > 0).ToString()' />
2) Set the property in OnItemDataBound in code-behind
One of two things is happening: Either the code you expect to be executing is not really executing (ie, is your if block ever true? Is the control not being found? Try a breakpoint), OR you are doing it at the wrong time -- after the page has already been rendered.