How do I enable row selection in an ASP GridView without disabling EnableEventValidation? - asp.net

I have an ASPX page that includes a GridView. I want to be able to select a row from the grid, and populate another section of the page based on the selected row. It works if I have EnableEventValidation="false" in the <%# Page %> line, but I have been told that I cannot use that because of a security concern. When I don't include it, selecting a grid row throws an "Invalid postback or callback argument" exception.
How can I implement row selection without disabling event validation?
Here is my code:
ASPX page:
<asp:GridView runat="server" ID="TheGrid" AutoGenerateColumns="false" DataKeyNames="id" EmptyDataText="No Data Found" AllowSorting="true">
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="First Name" ReadOnly="true" SortExpression="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="Last Name" ReadOnly="true" SortExpression="LastName" />
<asp:BoundField DataField="Email" HeaderText="Email" ReadOnly="true" SortExpression="Email" />
</Columns>
</asp:GridView>
ASPX.VB code:
Protected Sub TheGrid_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles TheGrid.RowDataBound
If e.Row.RowType = DataControlRowType.DataRow Then
e.Row.Attributes("onclick") = Page.ClientScript.GetPostBackClientHyperlink(TheGrid, "Select$" & e.Row.RowIndex)
e.Row.Attributes("style") = "cursor:pointer"
End If
End Sub
Protected Overrides Sub Render(writer As HtmlTextWriter)
ClientScript.RegisterForEventValidation("TheGrid")
MyBase.Render(writer)
End Sub
Note that when I select a row, the exception is thrown somewhere between Page_Load and Render.

Ok, lets wire up the GV two ways.
First way, drop in a plane jane button for the row click
(we will delete the button and add row click in 2nd example).
so, say this simple GV
<div id="MyGridArea" runat="server" clientidmode="static">
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID"
CssClass="table table-hover" Width="60em" GridLines="None"
ShowHeaderWhenEmpty="true">
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" />
<asp:BoundField DataField="City" HeaderText="City" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="cmdEdit" runat="server" Text="Edit" CssClass="btn myshadow"
OnClick="cmdEdit_Click" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</div>
Note how we just dropped in a plane jane button.
And our code to fill out above is this:
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()
Dim strSQL = "SELECT * FROM tblHotelsA ORDER BY HotelName"
GridView1.DataSource = Myrst(strSQL)
GridView1.DataBind()
End Sub
And now we have this:
Ok, so next part is to drop in a "div" area that allows edit of one row.
So, again, quite much plan jane controls.
And we can "hide" grid when we edit, and show edit area div.
But, I often use jQuery.UI. Really amounts to much the same code as simple hide/show, but this way jQuery.UI turns that SAME div into a nice pop up area.
So, that div area might look like this:
<div id="EditRecord" runat="server" style="float: left; display: none" clientidmode="Static">
<br />
<div style="float: left" class="iForm">
<label>HotelName</label>
<asp:TextBox ID="txtHotel" runat="server" f="HotelName" Width="280">
</asp:TextBox>
<br />
<label>First Name</label>
<asp:TextBox ID="tFN" runat="server" f="FirstName" Width="140"></asp:TextBox>
<br />
<label>Last Name</label>
<asp:TextBox ID="tLN" runat="server" f="LastName" Width="140"></asp:TextBox>
<br />
<label>City</label>
<asp:TextBox ID="tCity" runat="server" f="City" Width="140"></asp:TextBox>
<br />
<label>Province</label><asp:TextBox ID="tProvince" runat="server" f="Province" Width="75"></asp:TextBox>
</div>
etc. etc. etc.
so, now lets wire up the button above.
The button will simple:
Get current grid row
Get PK id
Load up div and display.
So, that code is this:
Protected Sub cmdEdit_Click(sender As Object, e As EventArgs)
Dim btn As Button = sender
Dim gRow As GridViewRow = btn.NamingContainer
EditRow(gRow.RowIndex)
End Sub
Sub EditRow(rowNum As Integer)
Dim intPK As Integer = GridView1.DataKeys(rowNum).Item("ID")
Dim strSQL As String = "SELECT * FROM tblHotelsA WHERE ID = " & intPK
Dim rstData As DataTable = Myrst(strSQL)
' load up our edit div
fLoader(Me.EditRecord, rstData.Rows(0))
ViewState("PK") = intPK
ScriptManager.RegisterStartupScript(Me.Page, Page.GetType, "mypopedit", "popedit()", True)
End Sub
As noted, I added a popup from jQuery.UI, but we could just use plain jane "div" and hide/show the grid and show the edit area (or like you have, have that edit area in full view).
(fLoader is a routine I built some time ago - I like everyone became VERY tired of typing code over and over to fill out text boxes etc., so for any text box etc., I use a "made up" attribute called f="Data base column name", and that routine just loops the controls on the form and fills them out. No different than hand coding simple assignments to controls, but with this code I can re-use it over and over.
So, we now see, get this:
Ok, so the only next goal is to add a row click
(and not use that edit button).
So, then all we need is a routine that will get the current row index, and call our edit row routine we have above.
So, we use row data bound, and add that click event that way.
but, do note how the above button click gets the current row. That nice short code works for repeaters, listview etc. (we used naming container).
But, if you want a row click in place of that button?
Then on row data bound, add this code:
Protected Sub GridView1_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles GridView1.RowDataBound
If e.Row.RowType = DataControlRowType.DataRow Then
e.Row.Attributes.Add("onclick",
"__doPostBack('myrowedit'," & e.Row.RowIndex & ")")
End If
End Sub
And in our page load event, we have this:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadGrid()
Else
If Request("__EVENTTARGET") = "myrowedit" Then
EditRow(Request("__EVENTARGUMENT"))
End If
End If
End Sub
And a handy dandy routine that returns a table based on SQL I used above was this:
Public Function Myrst(strSQL As String) As DataTable
Dim rstData As New DataTable
Using mycon As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand(strSQL, mycon)
mycon.Open()
rstData.Load(cmdSQL.ExecuteReader)
End Using
End Using
Return rstData
End Function
So, all in all, not a lot of code.
You can try the above working example here:
http://www.kallal.ca/Website11/WebForm2

Related

Gridview Edit Mode - Drop down list does not show blank value

I have a drop down list 'Country', 'city' text box and an 'Add' button. The country drop down is NOT mandatory so I can just add a city without a country, adding an empty 'country' to the gridview works OK. the problem when I click on 'Edit' in the gridview it binds it to the first country in the list it does not just show a blank:
<asp:DropDownList ID="DDLCountry" runat="server" AutoPostBack="true" AppendDataBoundItems="true" OnSelectedIndexChanged="DDLCountry_SelectedIndexChanged" InitialValue="">
<asp:ListItem Text="------------------------ Select ------------------------" Value="" />
</asp:DropDownList>
<asp:TextBox ID="txtCity" runat="server"></asp:TextBox>
<asp:Button ID="btnNewLList" runat="server" OnClick="btnNewLList_Click" Text="Add new Country"/>
<asp:GridView ID="gvAddNewCountry" runat="server" AutoGenerateColumns="False" DataKeyNames="ID" OnRowCommand="gvAddNewCountry_RowCommand" OnRowDeleting="gvAddNewCountry_RowDeleting" OnRowDataBound="gvAddNewCountry_RowDataBound" OnRowUpdating="gvAddNewCountry_RowUpdating" OnRowEditing="gvAddNewCountry_RowEditing" OnRowCancelingEdit="gvAddNewCountry_RowCancelingEdit" ShowHeaderWhenEmpty="True">
<EmptyDataTemplate>
No Data
</EmptyDataTemplate>
<Columns>
<asp:TemplateField HeaderText="Actions">
<ItemTemplate>
<asp:Button ID="btnEdit" runat="server" Text="Edit"/>
</ItemTemplate>
</asp:TemplateField>
<asp:CommandField ButtonType="Button" ShowEditButton="true" ShowCancelButton="true">
</asp:CommandField>
<asp:TemplateField HeaderText="Country>
<ItemTemplate>
<asp:Label ID="lblCountry" runat="server" Text='<% #Eval("Country") %>'></asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:DropDownList ID="DDCountry" runat="server" AppendDataBoundItems="True" AutoPostBack="false"></asp:DropDownList>
</EditItemTemplate>
</asp:TemplateField>
Code behind:
Protected Sub gvAddNewCountry_RowCommand(sender As Object, e As GridViewCommandEventArgs)
If e.CommandName = "Edit" Then
Dim rowCountryToEdit As Integer = e.CommandArgument
Dim ddListCountry As DropDownList = (CType(gvAddNewCountry.Rows(CInt(e.CommandArgument)).FindControl("DDCountry"), DropDownList))
ddListCountry.DataSource = (From x In Country Where x.Domain = "lCountry" Order By x.Description Select x).ToList()
ddListCountry.DataTextField = "Description"
ddListCountry.DataValueField = "ID"
ddListCountry.DataBind()
End If
End Sub
Thanks for your help X
Ok, so when you have/want a ddl in a gv row?
We require TWO steps.
First step: Load up the list of choices for the dll
2nd step: set the ddl to the current row value, or blank (no choice) if null no value exists yet for the current row. This ALSO means we have to get/grab the current row value for the dll, and set the ddl to reflect this existing choice.
So, this is a two step process.
And the "event" we typical use for this is the row bind event. (all such controls from listview, gridview and more have this event).
Also, a VERY nice helper tip? During (but ONLY during) the data bind event, you have FULL USE of ALL columns from the data source - EVEN COLUMNS NOT in the gv!!!
I don't have a entity database first setup that you have, but lets load up a gv with a combo box:
So, our gv:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
CssClass="table table-hover" Width="50%"
DataKeyNames="ID">
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="First Name" />
<asp:BoundField DataField="LastName" HeaderText="First Name" />
<asp:BoundField DataField="HotelName" HeaderText="Hotel Name" />
<asp:BoundField DataField="City" HeaderText="City" />
<asp:BoundField DataField="Description" HeaderText="Descripiton" />
<asp:TemplateField HeaderText="Rating">
<ItemTemplate>
<asp:DropDownList ID="cboRating" runat="server"
DataTextField="Rating"
DataValueField="ID">
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
And our code to fill is this:
Dim rstRating As New DataTable
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()
' get data for rating combo
rstRating = MyRst("SELECT ID, Rating FROM tblRating ORDER BY ID")
' get data for grid
GridView1.DataSource = MyRst("SELECT * FROM tblHotelsA ORDER BY HotelName")
GridView1.DataBind()
End Sub
Public Function MyRst(strSQL As String) As DataTable
Dim rstData As New DataTable
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand(strSQL, conn)
conn.Open()
rstData.Load(cmdSQL.ExecuteReader)
End Using
End Using
Return rstData
End Function
Note VERY carefull in above, I created a page wide (class wide) data table called rstRating. This will go out of scope after the data bind, but we ONLY need it to persit DURING the gv data bind operating (since for each row of the gv, we don't want to run that query over and over - we need this same pick list for the dll).
Ok, so now we see/get this:
The only part we need is to load up the dll, and set it for each row. We use the RowDataBound event.
So, code for that was this:
Protected Sub GridView1_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles GridView1.RowDataBound
' bind drop down for each row
If e.Row.RowType = DataControlRowType.DataRow Then
' get combo
Dim rDrop As DropDownList = e.Row.FindControl("cboRating")
rDrop.DataSource = rstRating
rDrop.DataBind()
rDrop.Items.Insert(0, New ListItem("Please Select", "0"))
' now get current row value for rating.
Dim gData As DataRowView = e.Row.DataItem
If IsDBNull(gData("Rating")) = False Then
rDrop.Text = gData("Rating")
End If
End If
End Sub
So, for each row, get the dll.
For for that row, load up with choices
And THEN for that row, set the ddl to the current row value (but check for null, and don't set - it will then show our "select" choice value.

How to verify all button in approved in gridview while click on other button that is outside the gridview

I want to add one button in vb.net e.g bulk approval outside the gridview in another table. While clicking on that button I.e is bulk approval.. all verify button are verified in gridview in every row... and changed into verified.
Ok, so we have some kind of grid, and some kind of approve button for each row.
Ok, so we have some typical markup like this:
This is a grid (hotel bookings), and then a the "administrator" has to approve each one.
So, say some markup like this - nothing special:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table">
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" ItemStyle-Width="180" />
<asp:BoundField DataField="City" HeaderText="City" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField HeaderText="Approved" ItemStyle-HorizontalAlign="Center" >
<ItemTemplate>
<asp:CheckBox ID="chkApproved" runat="server" Checked='<%# Eval("Approved") %>'
CssClass="bigcheck" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="By">
<ItemTemplate>
<asp:Label ID="lblApBy" runat="server" Text='<%# Eval("ApprovedBy") %>' ></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Approve">
<ItemTemplate>
<asp:Button ID="cmdApprove" runat="server" Text="Approve" CssClass="btn"
onclick="cmdApprove_Click"/>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<div style="float:right">
<asp:Button ID="cmdApproveAll" runat="server" Text="Approve All" CssClass="btn" />
And 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)
Dim strSQL As String = "SELECT TOP 6 * from tblHotels"
Using cmdSQL As New SqlCommand(strSQL, conn)
conn.Open()
Dim rstData As New DataTable
rstData.Load(cmdSQL.ExecuteReader)
GridView1.DataSource = rstData
GridView1.DataBind()
End Using
End Using
End Sub
And we now have this:
Ok, so we have a plane jane button on each row. The code to approve a row is this code:
(a simple standard button in the grid view - button click)
Protected Sub cmdApprove_Click(sender As Object, e As EventArgs)
' approve one row
Dim btn As Button = sender
Dim gRow As GridViewRow = btn.NamingContainer
Call ApproveGRow(gRow)
End Sub
Sub ApproveGRow(gRow As GridViewRow)
' set checkbox = checked
Dim ckApprove As CheckBox = gRow.FindControl("chkApproved")
ckApprove.Checked = True
Dim lBy As Label = gRow.FindControl("lblApBy")
lBy.Text = UserInitials
' Get data base pk id
Dim pkID As Integer
pkID = GridView1.DataKeys(gRow.RowIndex).Item("ID")
' Now udpate database
Dim strSQL As String =
"UPDATE tblHotels SET Approved = 1, ApprovedBy = #Initials WHERE ID = #ID"
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand(strSQL, conn)
conn.Open()
cmdSQL.Parameters.Add("#Initials", SqlDbType.NVarChar).Value = UserInitials
cmdSQL.Parameters.Add("#ID", SqlDbType.Int).Value = pkID
cmdSQL.ExecuteNonQuery()
End Using
End Using
End Sub
So, on a button click, we get the current row click, and then call our approve routine that accepts grid ro
So, if I click say on the second row, we see this:
However, what about do the WHOLE gird, with one button, and approve all rows?
Well, the button for the approve all looks like this:
Protected Sub cmdApproveAll_Click(sender As Object, e As EventArgs) Handles cmdApproveAll.Click
For Each gRow In GridView1.Rows
Call ApproveGRow(gRow)
Next
End Sub
And if I click on it, then I get this:

ASP.NET query database with text from textbox without clicking on button

I have a datepicker calendar in JS. Once a user clicks on a date, that date gets passed to the asp:TextBox as string. I would like to query my database with the text in textbox without having to click on a button. Is this possible?
Well, there is what, 100's of JS date pickers?
(which one you using might have helped here).
So, it going to depend on how the JS works.
Lets drop in a text box, set autopostback = true, and then add a text change event.
So, we have this markup:
<h2>Choose Date</h2>
<asp:TextBox ID="txtDate" runat="server"
TextMode="Date" AutoPostBack="True" ></asp:TextBox>
and the code behind is this:
Protected Sub txtDate_TextChanged(sender As Object, e As EventArgs) Handles txtDate.TextChanged
Debug.Print("Date changed to " & txtDate.Text)
End Sub
Now run the page and we have this:
Note how that text box already shows a date picker if you set textMode = date. Maybe you don't need a JS date picker then?
Ok, so lets select a date:
And because I have autopost-back = true, I see this in output:
Output:
So, in your server side text changed event - it should trigger. But that triggering of the text changed event may well depend on how and what kind of out of the 1000+ date picker JS examples I can find on the web that you are using here.
So now lets query the database based on that value then right?
So, say we have this markup:
<h2>Choose Date</h2>
<asp:TextBox ID="txtDate" runat="server" TextMode="Date" AutoPostBack="True" ></asp:TextBox>
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table">
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" />
<asp:BoundField DataField="VisitDate" HeaderText="Vist" DataFormatString="{0:yyyy-MM-dd}" />
<asp:BoundField DataField="Description" HeaderText="Description" />
</Columns>
</asp:GridView>
We select a date, and show visits for that date.
Our code thus becomes this:
Protected Sub txtDate_TextChanged(sender As Object, e As EventArgs) Handles txtDate.TextChanged
Debug.Print("Date changed to " & txtDate.Text)
Using conn As New SqlConnection(My.Settings.TEST4)
Dim strSQL =
"SELECT * from tblHotels WHERE VisitDate = #VisitDate ORDER BY HotelName"
Using cmdSQL = New SqlCommand(strSQL, conn)
cmdSQL.Parameters.Add("#VisitDate", SqlDbType.DateTime).Value = txtDate.Text
conn.Open()
GridView1.DataSource = cmdSQL.ExecuteReader
GridView1.DataBind()
End Using
End Using
End Sub
And we now have this:

How to add new line items in a APS.net webform and submit to database

Please note: I do not necessarily need working code. I just don't know how to word what I am looking for to even find an answer on the web. I guess i'm just asking for a little guidance on what kind of control I would to use to accomplish my goal.
Basically when I go to a job site I will use different amounts and types of inventory. So one line item would consist of
Item Description
Quantity Used
UsedByTech
I'm collecting these via webform text box. I would like to have a button that says "Add" and then allows me to input another line item, and so on.
Then at some point a submit button on the form would gather those line items and input them in to a MSSQL databse.
I'm currently using ASP.Net framwork and webforms.
Can somone please tell me what kind of control would allow this a may be give me some hints about what to search for?
Ok, this is actually quite easy - but we let asp.net do most of the work for us.
So, we assume we have these two tables:
so, we have a table to list out the Job Sites
And then as per above, we have a table to list out the Job items for each site.
Ok, so we display the Job Sites.
Drop in a Gridview - build it using the wizards:
Now, blow out (delete) the data source from the page - don't need it.
Remove the DataSource setting for the Gridview
And lets drop in a plane jane button into that grid.
Thus, this markup - most of it was generated for me
<div style="padding:25px">
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table" Width="40%">
<Columns>
<asp:BoundField DataField="JobSite" HeaderText="JobSite" />
<asp:BoundField DataField="JobDate" HeaderText="JobDate" DataFormatString="{0:yyyy-MM-dd}" />
<asp:BoundField DataField="Foreman" HeaderText="Foreman" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField HeaderText="View">
<ItemTemplate>
<asp:Button ID="cmdView" runat="server" Text="View Job" CssClass="btn"
Onclick="cmdView_Click"/>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</div>
And our code to load this grid is thus this:
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()
GridView1.DataSource = MyRst("SELECT * from JobSites Order by JobDate")
GridView1.DataBind()
End Sub
Ok, we now have this:
Ok, so now we need to wire up that "view" button.
So, we grab the PK row value - jump to our edit items page;
Protected Sub cmdView_Click(sender As Object, e As EventArgs)
Dim btn As Button = sender
Dim gRow As GridViewRow = btn.Parent.Parent
' get PK database ID
Dim PK As Integer = GridView1.DataKeys(gRow.RowIndex).Item("ID")
' Now jump to our job add job items page
Session("JobID") = PK
Response.Redirect("JobAddItems.aspx")
End Sub
So, now we need to build our 2nd page.
I could use a repeater - but I used DataList - to display the ONE job.
I again used the wizards - then blow out the DataSource setting and item on that page.
We then drop in a gridview for the "many" items (the job items).
I now have this:
<br />
<div style="border:solid;border-color:black;width:20%">
<asp:DataList ID="DataList1" runat="server" DataKeyField="ID">
<ItemTemplate>
JobSite:
<asp:Label ID="JobSiteLabel" runat="server" Text='<%# Eval("JobSite") %>' Font-Size="Larger" Font-Bold="true" />
<br />
JobDate:
<asp:Label ID="JobDateLabel" runat="server" Text='<%# Eval("JobDate", "{0:yyyy-MM-dddd}") %>' />
<br />
Foreman:
<asp:Label ID="ForemanLabel" runat="server" Text='<%# Eval("Foreman") %>' />
<br />
Description:
<asp:Label ID="DescriptionLabel" runat="server" Text='<%# Eval("Description") %>' />
<br />
<br />
</ItemTemplate>
</asp:DataList>
<br />
</div>
<br />
<div style="float:left">
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table borderhide" Width="30%">
<Columns>
<asp:TemplateField HeaderText="Description">
<ItemTemplate>
<asp:TextBox id="txtDesc" runat="server" Text='<%# Eval("ItemDescription") %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Qty">
<ItemTemplate>
<asp:TextBox id="txtQty" runat="server" Text='<%# Eval("Qty") %>' TextMode="Number" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Used By">
<ItemTemplate>
<asp:TextBox id="txtUsedBy" runat="server" Text='<%# Eval("UsedBy") %>' />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<div style="float:right">
<asp:Button ID="cmdAdd" runat="server" Text="Add Item" CssClass="btn" style="margin-top:-20px" />
</div>
</div>
<div style="clear:both"></div>
<asp:Button ID="cmdSave" runat="server" Text="Save" CssClass="btn" />
<asp:Button ID="cmdCancel" runat="server" Text="Cancel" CssClass="btn" style="margin-left:40px"/>
</div>
Ok, and the code to load this up is this:
We load up the main record - just for display, and then the GridView of child items.
Dim rstJobItems As New DataTable
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
ViewState("JobID") = Session("JobId")
LoadData()
Else
rstJobItems = ViewState("JobItems")
End If
End Sub
Sub LoadData()
DataList1.DataSource = MyRst("SELECT * from JobSites WHERE ID = " & ViewState("JobID"))
DataList1.DataBind()
rstJobItems = MyRst("SELECT * FROM JobItems where Job_ID = " & ViewState("JobID"))
GridView1.DataSource = rstJobItems
GridView1.DataBind()
ViewState("JobItems") = rstJobItems
End Sub
Ok, so now on the first page, when we click on a row, we jump to the 2nd page, and we see/have this:
So we need to wire up the button to add a new row.
That code looks like this:
Protected Sub cmdAdd_Click(sender As Object, e As EventArgs) Handles cmdAdd.Click
' user might have done some edits
GridToTable()
' create a new row
Dim NewRow As DataRow = rstJobItems.NewRow
NewRow("Job_id") = ViewState("JobID")
NewRow("Qty") = 0
rstJobItems.Rows.Add(NewRow)
GridView1.DataSource = rstJobItems
GridView1.DataBind()
End Sub
So, say in above, I click the add row, then you will see this:
(in fact I clicked it two times).
Ok, so you are free to tab around - edit data in that grid. You can edit/change existing rows, or add a new row and simple tab around and enter data.
So, now lets wire up the Save button. That save button has to allow and deal with editing rows - and also saving edits - all in ONE shot to the database.
The code for save button is thus this:
Protected Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
GridToTable()
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand("SELECT * FROM JobItems WHERE ID = 0", conn)
Dim da As New SqlDataAdapter(cmdSQL)
Dim daU As New SqlCommandBuilder(da)
da.Update(rstJobItems)
End Using
End Using
' data add, and any edit now saved - return to our job site listing page
Response.Redirect("JobSites.aspx")
End Sub
So we send grid to table, and then in ONE database update, send the table back to the database.
Of course after we save, we return back to our first page with the grid now read to edit more.
So, the other routine used in above was sending the Grid (rows) back to the table.
That code is this:
Sub GridToTable()
For Each gRow As GridViewRow In GridView1.Rows
Dim OneRow As DataRow = rstJobItems.Rows(gRow.RowIndex)
OneRow("ItemDescription") = CType(gRow.FindControl("txtDesc"), TextBox).Text
OneRow("Qty") = CType(gRow.FindControl("txtQty"), TextBox).Text
OneRow("UsedBy") = CType(gRow.FindControl("txtUsedBy"), TextBox).Text
Next
End Sub
And of course the cancel button? Well, if you add some rows, or just edit the rows, and hit cancel? Well, we don't save - but just return to the previous page.
eg:
Protected Sub cmdCancel_Click(sender As Object, e As EventArgs) Handles cmdCancel.Click
Response.Redirect("JobSites.aspx")
End Sub
And last but not least, I have a "general" helper routine that simple returns a data table - and I used it several times in above. That routine kind of gives me a FoxPro or MS-Access like ability to get a data table with great ease (in place of typing that same code over and over).
That routine was this:
Public Function MyRst(strSQL As String) As DataTable
Dim rstData As New DataTable
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand(strSQL, conn)
conn.Open()
rstData.Load(cmdSQL.ExecuteReader)
End Using
End Using
Return rstData
End Function
So the above is quite much start to finish. It is of course quick code, and done rather fast/quick for stack overflow. But it is full of great ideas, and as noted, I let the wizards generate most of the markup. (but then remove the data source from the GV, and also the Data source item that gets created in the page.
So in summary:
I did not write most of that markup - I let the wizards generate most of it.
Note how SMALL and relative simple the code bits were. we broke up this into bite sized parts - thus each routine is not a lot of code.
We ALSO let .net built the sql update (and insert) commands for us. this eliminated BOAT LOADS of parameters, and BOATLOADS of work to wire that up. And it also MUCH better for performance, since ado.net is smart - for rows not changed - it don't send the updates to sql server.
Note also how ONE simple update method of
da.Update(rstJobItems)
Handles BOTH edits/updates, an inserts with one command. And we probably could/should drop in a delete row button - and the update would also handle that for us too!!!
So, there is a LOT of great ideas in above, but key concepts are:
Leverage as much as you can the wizards, and built in controls.
Leverage the ability of ado.net to update a table of edits in ONE shot back to the database - and as above shows, SAME single update also works for both inserts and updates at the same time.

First row of GridView table is missing data

When I call gvTags.DataSource = GetTags() and gvTags.DataBind() it shows that all the rows and columns have the appropriate data.
When the OnRowDataBound function is hit, the last columns in my first table row have no data (null values).
The only difference between this table and the others I've created is that I am showing/hiding columns based on a user selection higher in the page. But - the showing/hiding is done within the OnRowDataBound function, where the data is already missing. I have no idea what's going on, or where to even start for looking further.
UPDATE: It looks like the problem is caused by the last three lines of the RowDataBound function. When I remove those three rows, the data displays as it should. So - I need a way to show/hide those three columns based on if a user selects a checkbox elsewhere on the page (Include Removed Entries). If the checkbox is selected, the RemovedBy and RemovedDate columns are visible. If it is not selected, those hide but the Description column is visible.
<asp:GridView ID="gvTags" CssClass="table table-striped" runat="server" AutoGenerateColumns="false"
AllowSorting="false" GridLines="None" OnRowDataBound="gvTags_RowDataBound" DataKeyNames="TagID">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:LinkButton ID="btnRemove" runat="server" OnClick="btnRemove_Click">
Remove
</asp:LinkButton>
<asp:LinkButton ID="btnReapply" runat="server" OnClick="btnReapply_Click">
Re-Apply
</asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="TagID" HeaderText="ID" />
<asp:BoundField DataField="InstalledBy" HeaderText="Installed By" />
<asp:BoundField DataField="InstalledDate" HeaderText="Date Installed" />
<asp:BoundField DataField="RemovedBy" HeaderText="Removed By" />
<asp:BoundField DataField="RemovedDate" HeaderText="Date Removed" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:BoundField DataField="IsRemoved" Visible="false" />
</Columns>
</asp:GridView>
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
If (Page.IsPostBack) Then
Exit Sub
End If
gvTags.DataSource = GetTags()
gvTags.DataBind()
'NOTE - Here, DataSource has complete data (all rows & columns that should have data, do have data)
If (gvTags.Rows.Count > 0) Then
gvTags.HeaderRow.TableSection = TableRowSection.TableHeader
End If
End Sub
Protected Sub gvTags_RowDataBound(ByVal sender As Object, ByVal e As GridViewRowEventArgs)
If (Not e.Row.RowType = DataControlRowType.DataRow) Then
Exit Sub
End If
e.Row.FindControl("btnRemove").Visible = Not Boolean.Parse(DataBinder.Eval(e.Row.DataItem, "IsRemoved").ToString)
e.Row.FindControl("btnReapply").Visible = Boolean.Parse(DataBinder.Eval(e.Row.DataItem, "IsRemoved").ToString)
'NOTE - Here, gvTags.Columns(columnIndex) is null for each of the below three columns (RemovedBy, RemovedDate, and Description)
gvTags.Columns(removedByColumnIndex).Visible = checkbox.Checked
gvTags.Columns(removedDateColumnIndex).Visible = checkbox.Checked
gvTags.Columns(descriptionColumnIndex).Visible = (Not checkbox.Checked)
End Sub
My suggestion: if you columns are to be shown/hidden depending on a CheckBox outside of the grid, you could do this in the markup:
<asp:CheckBox ID="chkBox" runat="server" AutoPostBack="true" ... />
And process the event in code-behind:
Private Sub chkBox_CheckedChanged(sender As Object, e As System.EventArgs) Handles chkBox.CheckedChanged
gvTags.Columns(removedByColumnIndex).Visible = chkBox.Checked
gvTags.Columns(removedDateColumnIndex).Visible = chkBox.Checked
gvTags.Columns(descriptionColumnIndex).Visible = (Not chkBox.Checked)
gvTags.DataSource = GetTags()
gvTags.DataBind()
End Sub

Resources