How do I assign OnRowCommand to multiple IDs ASP.net and VB.net Backend - asp.net

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.

Related

ASP.NET bound hyperlink as SQL Server stored procedure parameter?

I've currently got a gridview that is populated with summary data. I've created a hyperlink from a bound field labeled ticket_num. What I'm wanting to accomplish is to click the hyperlink and have that call a stored procedure. I need to pass that text of that hyperlink into the stored procedure in SQL Server. So the flow is something like this...
User clicks link
The text of that hyperlink is passed into a parameter for the SQL Server stored procedure to use
Call the stored procedure and display results on new page
Any ideas? The stored procedure is created, connection into the server via ASP.NET is created. Everything works thus far but I can't figure this piece of it out.
Well, since you want some code to execute, then LITTLE reason exists to use a hyper-link, since that not what you need nor want.
You don't mention if you prefer to "hide" the URL and hyper link anyway? (often for security, this is not all such a bad idea).
so, if you need to hide/not show/don't want the value in the grid, then our simple button click can get the row id of the grid (also hidden), and then get the value, and then pass to sql server.
Or, you can add to the button the URL or value to the button - just use command argument. That way you don't have to hit the database again based say on row PK id.
So, here is a simple grid. I have the URL as a row on the grid, but lets also shove/put it into the button for you to see how this works.
So, simple grid:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table" >
<Columns>
<asp:BoundField DataField="Fighter" HeaderText="Fighter" />
<asp:BoundField DataField="Engine" HeaderText="Engine" />
<asp:BoundField DataField="Thrust" HeaderText="Thrust" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField HeaderText="Preview">
<ItemTemplate>
<asp:Image ID="Image2" runat="server" ImageUrl = '<%# Eval("ImagePath") %>' Width="140" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="View">
<ItemTemplate>
<asp:Button ID="cmdView" runat="server" Text="View" CssClass="btn"
CommandArgument = '<%# Eval("ImagePath") %>' OnClick="cmdView_Click" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
And our code to load up the above:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadGridF()
End If
End Sub
Sub LoadGridF()
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand("SELECT * FROM Fighters", conn)
conn.Open()
Dim rstData = New DataTable
rstData.Load(cmdSQL.ExecuteReader)
GridView1.DataSource = rstData
GridView1.DataBind()
End Using
End Using
End Sub
And now we have this:
Note how we have a PLAIN JANE button - after all, it not really a hyper link we need, is it?
So, for our button click event, we have this:
Protected Sub cmdView_Click(sender As Object, e As EventArgs)
Dim btn As Button = sender
Dim gRow As GridViewRow = btn.NamingContainer
Debug.Print("Row index click = " & gRow.RowIndex)
' get database row PK id
Dim iPK As Integer = GridView1.DataKeys(gRow.RowIndex).Item("ID")
' now we have database row PK - walk the dog, do payroll processing
' do ANYTHING we want like get the database row etc.
Debug.Print("Data base row PK id = " & iPK)
Debug.Print("Command button arugment = " & btn.CommandArgument)
End Sub
And output is then:
So, you can see, we did not need (or want a hyper link), but a simple button, and that click event can:
get any value from the given grid row
get the database PK id (hidden - never exposed to client side (good security).
get the row click index
get parameters passed to button
And we can use gRows.Cells() to get the other row values (for data fields)
And of course gRow.FindControl("ctrl name") for templated columns.

How to handle nulls with Eval

I can have a record with all filled in fields and then without an SO_ID or SO_Num. I want my eval to be able to handle these and just return a '-' in the grid column when this happens while still returning all other data for that row. I've tried other solutions online and couldn't find one that works.
<dx:GridViewDataColumn FieldName="SO_Num" VisibleIndex="19" runat="server" Caption="Sales Order Number">
<DataItemTemplate>
<a id="clickElement" href="../sales/order/view.aspx?ID=<%# Eval("SO_ID").ToString()%>"><%#Eval("SO_Num").ToString()%></a>
</DataItemTemplate>
</dx:GridViewDataColumn>
You can use a in-line "iif(), and thus this:
<asp:TemplateField HeaderText="City">
<ItemTemplate>
<asp:TextBox ID="txtCity" runat="server"
Text = '<%# IIf(IsDBNull(Eval("City")), "-", Eval("City")) %>'
></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
So, in above, if say Eval("City") is null, then we display a "-", else we display Eval("City")
Edit: display of values and some button to click and navgate are DIFFERENT!!
As I pointed out, if you need a Eval() in the GridView, and want to convert a null say into a "-", then do that (but, I fail to see why I would want to display some "-" in the GV. Why do that?? Seems rather strange to me?
However, if you have a button on the GV row, and you want to click on that button to jump or navigate to some other page? Fail to see how such a button click and navigate has ANY REALATIONSHIP to what we display? Why are the two concepts connected? I fail to see any sensible logic here?
If you want to drop in a pane jane button, or even a link button (no difference here), then wire up a click event for that given button you drop in.
so, say in our GV, we drop in a button to view a given row of data. Say this GV with hotels, and a button.
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
CssClass="table table-hover" Width="50%"
DataKeyNames="ID" >
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" />
<asp:TemplateField HeaderText="Hotel Name">
<ItemTemplate>
<asp:Label ID="txtHotel" runat="server"
Text='<%# Eval("HotelName") %>' >
</asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField HeaderText="View">
<ItemTemplate>
<asp:Button ID="cmdView" runat="server" Text="View" CssClass="btn "
/>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<PagerStyle CssClass="pagenavi" />
</asp:GridView>
so, we just dropped in a plane jane button for operations on that one row.
So, our code to load 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()
Using conn As New SqlConnection(My.Settings.TEST4)
Dim strSQL As String =
"SELECT * from tblHotelsA ORDER BY HotelName"
Using cmdSQL As New SqlCommand(strSQL, conn)
conn.Open()
Dim rst As New DataTable
rst.Load(cmdSQL.ExecuteReader)
GridView1.DataSource = rst
GridView1.DataBind()
End Using
End Using
End Sub
And our results are now this:
Ok, so now lets wire up that plane jane button click.
As a normal rule, you can double click on a button to build the click event, or bring up the property sheet, choose events tab/section, and then add the click event. However, since the button is in the GV, then we have to add the click event this way (in the markup).
Type in OnClick=, and when you hit the "=" sign, intel-sense will popup a dialog to create the event.
You get this:
So, we select create new event. Don't seem like anything occurred, but flipping to code behind, we have a click event stub, and our code thus can be this:
Protected Sub cmdView_Click(sender As Object, e As EventArgs)
Dim btn As Button = sender
Dim gRow As GridViewRow = btn.Parent.Parent
Dim intPKID As Integer = GridView1.DataKeys(gRow.RowIndex).Item("ID")
Debug.Print("Row click index = " & gRow.RowIndex)
Debug.Print("Row click database PK id = " & intPKID)
' now do whatever you want with this row information.
' to get values from non templated columns, use cells()
' to get values from tempated columns, use findcontrol
'eg:
' get last name (Boundfield)
Debug.Print("Last name = " & gRow.Cells(1).Text)
' get hotel name - template - "label"
Dim lblHotel As Label = gRow.FindControl("txtHotel")
Debug.Print("Hotel name (label) = " & lblHotel.Text)
End Sub
output:
So, as noted, I fail to see why ANY issue occurs here in regards to some data in a column of the GV being null?
In your case, just navigate based on the button click to anything you want, based on any value you want.
say like this:
<asp:TemplateField HeaderText="View">
<asp:Button ID="cmdView" runat="server" Text="View" CssClass="btn "
OnClick= "cmdView_Click"
CommandArgument = '<%# Eval("SO_ID") %>' />
</asp:TemplateField>
And then in code behind:
Protected Sub cmdView_Click(sender As Object, e As EventArgs)
Dim btn As Button = sender
Dim gRow As GridViewRow = btn.Parent.Parent
Dim intPKID As Integer = GridView1.DataKeys(gRow.RowIndex).Item("ID")
Debug.Print("Row click index = " & gRow.RowIndex)
Debug.Print("Row click database PK id = " & intPKID)
Dim intSOID = btn.CommandArgument
Dim strURLJumpTo = "../sales/order/view.aspx?ID=" & intSOID
Response.Redirect(strURLJumpTo)
So, you are free to cook up any URL navagation you want.
NOTE VERY close how I used the data keys feature of the GV. That allowed me to have, use, get, play with the database PK row id, but NEVER do I have to expose or show or have or mess with the database PK id in the actual GV markup.
This is not only nice, but also is a HUGE deal from a security point of view, since then the user, the browser (client side) thus NEVER has to see, or know or can munge or play with the database PK row id - it is 100% server side managed.
And in fact if you SO_ID or whatever values are not in the GV, or the user does not care? Then I would NOT pass the values in the URL as so called "query parms" of the URL, but would in fact pass the values in session() like say this:
Session("OrderID") = intPKID
Response.Redirect("../sales/order/view.aspx")
Then in the page load event of the target page, I do this:
and NEVER EVER forget to check/use the ispostback in your page load event.
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
ViewState("OrderID") = Session("OrderID")
Now, in that code behind, and that page, you use this to get/use/have the orederid
dim intOrderID = ViewState("OrderID")
So, on page load (first page, ispostback = false), you transfer the session() value to the ViewState. And you do this since the user might have more then one copy of the browser running - and thsu using session() to pass the value is ok, but session() is global to the ONE user, where as ViewState is per page. So, that's why we transfer to ViewState on page load, since FROM THAT POINT onwards in that page, and code behind, we use ViewState. If we used session() in the code behind, then it is global, and if more then one copy of the browser is running or even multiple tables, they will all have the same session() value.
So, say you click on a house to view or buy?
Well, then they might open another tab - display same GV, and click on a different row. If we use session, you now display two pages - but both have the one and same row PK id value - and if you click buy house, you get the wrong house if your code behind uses session(), but with ViewState, the issue does not exist.
And thus, you can even dump your ugle "id" and parameters out of the URL - they look much nicer, but are also much more secure, and the user thus does not have to see, or know about things such as database row PK junk and stuff.

Insert Rows In Gridview

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.

How to edit the data shown in a grid view from a SQL database

I made a search function using Grid View, the program reads the user input and based on that it returns the data that matches from the data base, however it returns the whole line, which includes 2 ID columns which I don't want to show. Sounds like something simple yet I can't seem to find any kind of tutorial on how to do this.
Also, the second column IdCargo (IdProfession, in english), I'd like to translate this data, as in, if a specific ID is supposed to appear I would like to instead show the profession of said employee. I would also like to show the column with "Cargo" name instead of "IdCargo", also instead of "CargaHoraria" I want to show "Carga Horaria".
If anyone knows any kind of guide or tutorial with using GridViews and SQL, that would be extremely helpful for future research as well.
Great. Ok, we don't have to worry to much about the search part- I'll assume you enter some search, with parameters, the result is a data table.
Now, I will STRONG suggest that you consider a listview in place of the grid view.
As for controlling which columns? Well, you can template each column. (and that's why I suggest list view - it is less markup).
However, I don't have too many columns - so a GV is "ok", but as you want more columns, more custom layout - then the LV will be LESS markup.
And another REALLY big advantage of LV, is you can get it to write the markup for you.
Anyway, ok, this is our GV.
VERY important:
We have a PK primary key for each row ("ID"). And we of course don't want to show or display that PK ID, but as we all know, a PK is the lifeblood of any data system. So, there is a VERY cool feature in GV - called DataKeys. It allows you to use/have/play the PK row id, but NEVER do you have to expose or display it in the GV. (so not only nice from UI point of view, also VERY nice from a security point of view).
So, say we have this GV layout:
<div style="width:40%;padding:25px">
<style> .borderhide input {border:none}</style>
<asp:GridView ID="GVPeople" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" cssclass="table borderhide">
<Columns>
<asp:TemplateField HeaderText="First Name">
<ItemTemplate>
<asp:TextBox ID="FirstName" runat="server" Text='<%# Eval("FirstName") %>' ></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Last Name">
<ItemTemplate>
<asp:TextBox ID="LastName" runat="server" Text='<%# Eval("LastName") %>' ></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="City">
<ItemTemplate>
<asp:TextBox ID="City" runat="server" Text='<%# Eval("City") %>' ></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Active" ItemStyle-HorizontalAlign="Center">
<ItemTemplate>
<asp:CheckBox ID="Active" runat="server"
Checked='<%# Eval("Active") %>'/>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Hotel ID">
<ItemTemplate>
<asp:TextBox ID="Hotel_ID" runat="server" Text='<%# Eval("Hotel_ID") %>' ></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
And we will feel this GV with data - MANY more columns exist - but we don't care.
so, my code so far is this:
Dim rstData As New DataTable
Dim rstHotels As New DataTable ' for combo box
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadGrid()
ViewState("rstData") = rstData
Else
rstData = ViewState("rstData")
End If
End Sub
Sub LoadGrid()
rstHotels = MyRst("SELECT ID, HotelName from tblHotels ORDER BY HotelName")
rstData = MyRst("SELECT * from People Order by FirstName")
GVPeople.DataSource = rstData
GVPeople.DataBind()
End Sub
So now we have this:
Ok, so your one part of the question is we obvious don't want to show the Hotel_id, but want to translate it to a description. And of course, if we going to allow edits, then lets convert the Hotel_ID to a combo box (drop down list). And like near most/all combo box, we will store the PK id of the hotel, but of course display the Hotel name for ease of use.
So, in place of hotel_id, we change our markup to this:
<asp:TemplateField HeaderText="Hotel ID">
<ItemTemplate>
<asp:DropDownList ID="cboHotel" runat="server"
DataTextField="HotelName"
DataValueField="ID">
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
Ok, so now we have to fill + setup the combo box. We have TWO tasks:
Fill the combo box with a data source
Set the combo box to the CURRENT selection for each gv row.
For this, we will use the GV row data bound event.
So, our code to fill out the combo will look like this:
So we have this code:
Protected Sub GVPeople_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles GVPeople.RowDataBound
If e.Row.RowType = DataControlRowType.DataRow Then
' get full row of data bind - all columns
Dim gData As DataRowView = e.Row.DataItem ' NOT A GRID VIEW ROW!!!!!
' get combo box
Dim cboHotels As DropDownList = e.Row.FindControl("cboHotel")
' setup cbo data source
cboHotels.DataSource = rstHotels
cboHotels.DataBind()
cboHotels.Items.Insert(0, New ListItem("", "")) ' add blank (no choice)
If IsDBNull(gData("Hotel_id")) = False Then
cboHotels.SelectedValue = gData("Hotel_ID").ToString
End If
End If
End Sub
so, now our results are this:
Ok, so that takes care of one of your questions/issues.
Next up is to edit - and this is REALLY cool, and REALLY easy.
Ok, if you look close, I "hide" the borders for the text boxes, but you find now that you can tab around quite much like excel. And a nice free-bee is that when text boxes have focus, they show!!
So lets drop in our button below the grid to save edits. It looks like this when I tab around:
Quite much like magic - you can now tab around - almost like Excel. And you can choose combo box value.
And in above, we dropped in a simple button below the GV like this:
</asp:GridView>
<asp:Button ID="cmdSave" runat="server" Text="Save Edits" CssClass="btn" />
Ok, so now the save data button.
We will write one helper routine. There is a BOATLOAD of reasons to split this code into two routines. So the first routine?
It will send the grid values BACK to our table. If you look close, I persisted the GV table data source as rstData.
So this routine sends grid back to table.
Sub GridToTable()
' pull GV rows back to table.
For Each gRow As GridViewRow In GVPeople.Rows
' Get database PK value
Dim PK As Integer = GVPeople.DataKeys(gRow.RowIndex).Item("ID")
Dim OneDataRow As DataRow = rstData.Select("id = " & PK)(0)
OneDataRow.Item("FirstName") = CType(gRow.FindControl("FirstName"), TextBox).Text
OneDataRow.Item("LastName") = CType(gRow.FindControl("LastName"), TextBox).Text
OneDataRow.Item("City") = CType(gRow.FindControl("City"), TextBox).Text
OneDataRow.Item("Active") = CType(gRow.FindControl("Active"), CheckBox).Checked
' combo box
Dim cboHotel As DropDownList = gRow.FindControl("cboHotel")
If cboHotel.Text = "" Then
OneDataRow("Hotel_ID") = DBNull.Value
Else
OneDataRow("Hotel_ID") = cboHotel.SelectedItem.Value
End If
Next
End Sub
Ok, so now all we have to do is send the rstData table (and get this: this will handle NEW rows, or edits!!!!).
so, now our save button code looks like 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 People where ID = 0", conn)
Dim da As New SqlDataAdapter(cmdSQL)
Dim daC As New SqlCommandBuilder(da)
conn.Open()
da.Update(rstData)
End Using
End Using
End Sub
So note how we send the WHOLE grid back to the database, and all changes in ONE shot.
Last but not least:
I used a helper routine to get a data table (became REAL fast typing that kind of code over and over, so I have this and I made it global to the whole application:
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)
rstData.TableName = strSQL
End Using
End Using
Return rstData
End Function
NOTE very carefull, I also STUFF the sql statement into the rst.Table name. Table name not really used, but now since I persisted the SQL for that table?
Then in fact this line
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand("SELECT * from People where ID = 0", conn)
becomes:
Using conn As New SqlConnection(My.Settings.TEST4)
Using cmdSQL As New SqlCommand(rstData.TableName, conn)
What this means is that if/when I have say a child master, or multiple tables of data to edit on a page? I use a dataset (a colleciton of tables), adn have ONE routien to send all tables and all edits back to the database in one shot/routine. We don't have more then one table of data to edit, but this explains why I shove in the SQL statement into the data table "table" name, since as you can see, then we don't even have to re-type the sql used.
NOTE FYI:
That sql statement I used of:
SELECT * from People WHERE ID = 0
Was NOT a type-o. I used that to allow sqlCommandBuilder to do all the dirty work of wiring up and creating the sql insert and update steaments for me.

How to get Repeater's current ItemIndex in code behind

While Container.ItemIndex as a way to get current repeater's item index in data binding expression works perfectly fine <%# Container.ItemIndex %>, it does not work in pure code behind. Container is not declared or inaccessible.
How can I get repeater's current item index here:
<ItemTemplate>
<% If Container.ItemIndex = 2 Then %>
TRUE/some longer HTML here/
<% Else %>
false/some longer HTML here/
<% End If %>
</ItemTemplate>
EDIT
For cases with not much HTML code this will work, but I am looking for Code Render Block solution as per example above.
<%#: If(Container.ItemIndex = 2, "TRUE", "false") %>
Ok, just drop in a button and pick up the repeter row.
So, say we have this repeater markup:
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<div style="border-style:solid;color:black;width:290px;float:left;padding:10px">
<div style="padding:5px;text-align:right">
Hotel Name: <asp:TextBox ID="txtHotelName" runat="server" Text ='<%# Eval("HotelName") %>' />
<br />
First Name: <asp:TextBox ID="txtFirst" runat="server" Text ='<%# Eval("FirstName") %>' />
<br />
Last Name: <asp:TextBox ID="txtLast" runat="server" Text ='<%# Eval("LastName") %>' />
<br />
City: <asp:TextBox ID="City" runat="server" Text ='<%# Eval("City") %>' />
<br />
Province: <asp:TextBox ID="Province" runat="server" Text ='<%# Eval("Province") %>'/>
<br />
Active: <asp:CheckBox ID="chkActive" runat="server" Checked = '<%# Eval("Active") %>'/>
<br />
<asp:Button ID="cmdRowC" runat="server" Text="Row Click" OnClick="cmdRowC_Click"/>
</div>
</div>
<div style="clear:both;height:5px"></div>
</ItemTemplate>
</asp:Repeater>
and our code behind to fill this repeater:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack = False Then
LoadGrid()
End If
End Sub
Sub LoadGrid()
Using cmdSQL As New SqlCommand("SELECT * FROM tblHotels ORDER BY HotelName",
New SqlConnection(My.Settings.TEST3))
cmdSQL.Connection.Open()
Repeater1.DataSource = cmdSQL.ExecuteReader
Repeater1.DataBind()
End Using
End Sub
And we now have this:
So now the button code. Note how I just dropped in a standard button.
But, we now can't double click on the button to automatic wire up and create a event.
BUT YOU CAN do this to create a event:
In the button code markup, type in OnClick=
WHEN YOU HIT "=", then intel-sense will pop up a dialog for you to create a button click event like this:
So, now click on create new event. It "seems" like nothing occured, but if we flip to code behind you find the click event stub.
So, now in our click event code we can easy pick up the current repeater row, the values and yes even the index.
The code works like this:
Protected Sub cmdRowC_Click(sender As Object, e As EventArgs)
Dim cBtn As Button = sender
Dim rRow As RepeaterItem = cBtn.Parent
Debug.Print("Row clicked = " & rRow.ItemIndex)
Debug.Print("First Name = " & DirectCast(rRow.FindControl("txtFirst"), TextBox).Text)
Debug.Print("Last Name = " & DirectCast(rRow.FindControl("txtLast"), TextBox).Text)
Debug.Print("Hotel Name = " & DirectCast(rRow.FindControl("txtHotelName"), TextBox).Text)
End Sub
output:
Row clicked = 1
First Name = Darcy
Last Name = Caroll
Hotel Name = Athabasca Hotel
So, just drop in a plane jane button. When you click on it the event stub runs, and as you can see, we pick up the "Repeater row item".
From that row, we can get the row index, and of course pluck out any other control value from that row using find control.
eg:
Dim txtHotel as TextBox = rRow.FindControl("txtHotel")
debug.print ("Hotel name = " & txtHotel.Text)
So, once you have the repeater row, you don't need some container.ItemIndex expression, since the row item lets you get the given index row with:
rRow.ItemIndex
So, ItemIndex is available, and is avilable regardless if you have or include or use the conttin.ItemIndex in the markup.
There is VERY LITTLE need to mess around with that code in the markup.
Note how VERY clean and simple the above is. I suggest you MAKE HUGE efforts to avoid dumping vb code inside of the markup like you are doing. If you even want to convert to c#, or even just maintain that code? Put that code in the code behind area - not in the markup.
As above shows, for repeating data etc., you can use a Repeater, or even often I use a listview - as it allows a repeating layout to be created automatic for you, and without having to write looping code.
******************** EDIT ***************************
Ok, now that we have this working, say we wanted to put in a message in the repeater that
This hotel is Active
or
This hotel is NOT active!
Ok, so in our markup, we could drop in a label like this:
And now from the repeater property sheet, double click here to create the item data bound event:
It is now a simple matter to put in our code logic for each row like this:
Protected Sub Repeater1_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) Handles Repeater1.ItemDataBound
Dim rRow As RepeaterItem = e.Item
Dim ckbox As CheckBox = rRow.FindControl("chkActive")
Dim MyLabel As Label = rRow.FindControl("Label1")
If ckbox.Checked Then
MyLabel.Text = "This Hotel is Active!!!"
Else
MyLabel.Text = "Not active!"
End If
End Sub
And output is now this:
So, once again, to conditional format the repeater, add color to active hotel, or whatever? Use the item bound event.
Say i wanted add to above that the HotelName is to be color blue when active, then we could do this:
Protected Sub Repeater1_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) Handles Repeater1.ItemDataBound
Dim rRow As RepeaterItem = e.Item
Dim ckbox As CheckBox = rRow.FindControl("chkActive")
Dim MyLabel As Label = rRow.FindControl("Label1")
If ckbox.Checked Then
MyLabel.Text = "This Hotel is Active!!!"
Else
MyLabel.Text = "Not active!"
End If
Dim txtHotel As TextBox = rRow.FindControl("txtHotelName")
If ckbox.Checked Then
txtHotel.BackColor = Drawing.Color.AliceBlue
End If
End Sub
Again, nice easy clean code - no mess markup. We seperate the code logic from the markup.
Output now is this:
Again, note how we did not even write ANY looping code here!!!!
So you want to leverage the built in options for the repeater, and for a GridView, listView, and a repeater?
Use the row data bind event - it lets you format, even do math, or whatever you want for EACH row of the repeating controls. And as you can see, its plain jane easy to write code as a bonus.

Resources