I am using Visual Studio with asp.net, vb.net, and webforms.
My issue is that I need to get the user's textbox input from my dynamically generated textboxes.
On my button click BtnStep4Next, I dynamically add ServiceForm.ascx (it is a form with FirstName, LastName, City, etc). I add the Service Form to a list, and the list is stored in a session. There could be, for example, 5 webforms (ServiceForm).
Protected Sub BtnStep4Next_Click(sender As Object, e As EventArgs) Handles BtnStep4Next.Click
Dim formCount As Integer = 0
'Create a list to hold all of the service forms
Dim listServiceForms As New List(Of openapps_luesreg_ServiceForm)
Session("vsServiceForms") = listServiceForms
For Each i In CBLModifications.Items
'Do not show a service form for membership (ID = 1)
If i.Value <> "1" Then
formCount += 1
Dim serviceForm As openapps_luesreg_ServiceForm = New openapps_luesreg_ServiceForm()
'Load a new Service Form to the page
serviceForm = Page.LoadControl("ServiceForm.ascx")
serviceForm.ID = "service" + formCount.ToString
Dim txtCity As TextBox = CType(serviceForm.FindControl("txtCity"), TextBox)
txtCity.ID = "serviceCity" + formCount.ToString
'Dim txtCityRegEx As Regex = CType(serviceForm.FindControl("RegExCity"), Regex)
'txtCityRegEx.ID = "serviceCity" + formCount.ToString
'Add the new Service Form to the panel PnlService Forms
PnlServiceForms.Controls.Add(serviceForm)
'Add the name to the top of the Form
Dim lblServiceName As Label = CType(serviceForm.FindControl("lblServiceName"), Label)
lblServiceName.Text = i.Text
'Add to listServiceForms
listServiceForms.Add(serviceForm)
'Set the list to the ViewState
Session("vsServiceForms") = listServiceForms
End If
Next
End Sub
On a different button click, now I want to grab the user's input they type into the textboxes. My code here though results in empty strings (the original form text) instead of the user's updated input. How can I actually get each textboxes' text that the user enters at this point?
Protected Sub BtnStep5Next_Click(sender As Object, e As EventArgs) Handles BtnStep5Next.Click
Dim listServiceForms As List(Of openapps_luesreg_ServiceForm)
listServiceForms = DirectCast(Session("vsServiceForms"), List(Of openapps_luesreg_ServiceForm))
Dim myCount As Integer = 0
For Each i In listServiceForms
myCount += 1
Dim ctlTxtCity As String = "serviceCity" + myCount.ToString
Dim myCity2 = ctlTxtCity
Dim txtNewCity As TextBox = CType(i.FindControl(ctlTxtCity), TextBox)
Dim myCity = txtNewCity.Text
Next
End Sub
You are writing way too much code. I mean, if I need 10 variables, do I go
dim v1 as integer
dim v2 as integer
... and so on
No! You do this:
dim MyValues(10) as integer
We use array's, collections. I mean even on desktop software. If I need a bunch of rows of repeating data - you use a grid, continues form or whatever other REPEATING type of control to do all this dirty work for you.
Say, I want to enter some Hotel Names. But, ahead of time I had no idea of how many. I mean, any database system + problem WILL have to deal with this type of scenario.
So, you don't try and inject controls. What happens now when you want to get that data back, say save it into the database? You now going to have a WHOLE set of controls that you have to have some numbering system to deal with.
Software not done this way. If you have repeating data, then use a repeating data control.
You can use repeater, listview, gridview. They all support repeating rows of data. The gridviewe or listview is for a grid layout. And the repeater would be the SAME idea, but you now laying out controls (repeating them) and you don't necessary have to use a grid format.
So, lets do a example of adding hotel names.
We have this grid markup:
<div style="float:left;margin-left:20px">
<style> .borderhide input {border:none}</style>
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False" DataKeyNames="ID" CssClass="table table-hover borderhide" >
<Columns>
<asp:TemplateField HeaderText="HotelName" >
<ItemTemplate><asp:TextBox id="HotelName" runat="server" Text='<%# Eval("HotelName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="FirstName" SortExpression="ORDER BY FirstName" >
<ItemTemplate><asp:TextBox id="FirstName" runat="server" Text='<%# Eval("FirstName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="LastName" >
<ItemTemplate><asp:TextBox id="LastName" runat="server" Text='<%# Eval("LastName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="City" >
<ItemTemplate><asp:TextBox id="City" runat="server" Text='<%# Eval("City") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Active">
<ItemTemplate><asp:CheckBox id="Active" runat="server" Checked = '<%# Eval("Active") %>' /></ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<div style="clear:both;padding-top:20px">
<asp:Button ID="cmdSave" runat="server" Text="Save" />
<asp:Button ID="cmdAdd" runat="server" Text="Add row" style="margin-left:20px" />
<asp:Button ID="cmdUnDo" runat="server" Text="UnDo" Style="margin-left:20px"/>
</div>
</div>
Not too much markup, but we GET to layout our repeating controls ONE time!!
Now, the code to fill this grid view is this:
Dim rstTable As New DataTable
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack = False Then
LoadGrid()
ViewState("rstTable") = rstTable
Else
rstTable = ViewState("rstTable")
End If
End Sub
Sub LoadGrid()
' load up our drop down list from database (city)
Dim strSQL As String
strSQL = "SELECT ID, FirstName, LastName, HotelName, City, Active, Rating from tblHotels"
Using cmdSQL As New SqlCommand(strSQL, New SqlConnection(My.Settings.TEST3))
cmdSQL.Connection.Open()
rstTable.Load(cmdSQL.ExecuteReader)
GridView1.DataSource = rstTable
GridView1.DataBind()
End Using
End Sub
Clean, simple code, and the result is this:
Now, even more incredible? I can tab around in that grid - edit like Excel.
So now how do we send back the changes to the database (the ONE save button).
The code looks like this:
Protected Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
' pull repeater rows back to table.
Dim bolDataOk As Boolean = True
For Each rRow As GridViewRow In GridView1.Rows
Dim RecordPtr As Integer = rRow.RowIndex
Dim OneDataRow As DataRow
OneDataRow = rstTable.Rows(RecordPtr)
OneDataRow.Item("HotelName") = CType(rRow.FindControl("HotelName"), TextBox).Text
OneDataRow.Item("FirstName") = CType(rRow.FindControl("FirstName"), TextBox).Text
OneDataRow.Item("LastName") = CType(rRow.FindControl("LastName"), TextBox).Text
OneDataRow.Item("City") = CType(rRow.FindControl("City"), TextBox).Text
OneDataRow.Item("Active") = TryCast(rRow.FindControl("Active"), CheckBox).Checked
Next
' now send table back to database with updates
If bolDataOk Then
Dim strSQL As String = "SELECT ID, FirstName, LastName, City, HotelName, Active from tblHotels WHERE ID = 0"
Using cmdSQL As New SqlCommand(strSQL, New SqlConnection(My.Settings.TEST3))
cmdSQL.Connection.Open()
Dim daupdate As New SqlDataAdapter(cmdSQL)
Dim cmdBuild As New SqlCommandBuilder(daupdate)
daupdate.Update(rstTable)
End Using
End If
End Sub
So now, note how we can SAVE all the edits in that grid in ONE shot.
And if we decide to add more columns, or maybe even a button we hit to see/view one row, we can do so with great ease.
Lets wire up the add button (adds a row to the grid). When we press it, we get/have/add a new row to the grid.
So the code could be like this:
Protected Sub cmdAdd_Click(sender As Object, e As EventArgs) Handles cmdAdd.Click
Dim MyOneRow = rstTable.NewRow
' set defaults for this new row
MyOneRow("City") = "Edmonton"
MyOneRow("Active") = True
' add htis new row to the table
rstTable.Rows.Add(MyOneRow)
' now update the grid to show this new added row
GridView1.DataSource = rstTable
GridView1.DataBind()
End Sub
Wow! - was that not nice and easy to add a WHOLE new grid row?
So, how this "should" work if using desktop MS-Access, FoxPro, or even the web?
Use the controls and bits and parts that allow you to work with repeating data.
There is VERY little need to try and inject controls into the web page. But worse, for repeating sets of data? Then for sure you don't' want to try and manage, and code and try to deal with a "set" of controls who's only job is to display repeating data.
Study the above. I suggest you drop all those controls, the whacks of code, and adopt either a gridview, or even a repeater to manage this problem.
I mean, what is even worse?
What happens if you need to go back to that page to edit data? Now you have to pull the data, and now re-inject all those controls again. So, you NOW need two sets of code. One set to add the new controls, and then later on to go back and display/edit that data, you again have to write another loop to re-inject the controls for display.
edit --------------------------------------
So the follow up question was how to do this for NOT a grid.
Well as noted, we have a "choice" of some controls, and their job is to "repeat" data for us. As noted, for grids, then GridView and ListView are top choices.
But, for non grid, and just some layout (say some fields that is a form or some text boxes)?
Sure, that suggest a Repeter. Note how NEAR exact the same code approach as above works. The is the wonderful part about this approch. Once you do this one time with a grid, then you know and have the skills to do this with say the repeater.
So, say we have this:
Ok, so I might want 2, or 15 of them, right? So we take the layout for above, and put it "inside" of a repeater control.
It will look like this:
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<div style="border-style:solid;color:black;width:250px;float:left">
<div style="padding:5px;text-align:right">
Hotel Name: <asp:TextBox ID="HotelName" runat="server" Text ='<%# Eval("HotelName") %>' Width="130px" />
<br />
First Name: <asp:TextBox ID="txtFirst" runat="server" Text ='<%# Eval("FirstName") %>' Width="130px" />
<br />
Last Name: <asp:TextBox ID="txtLast" runat="server" Text ='<%# Eval("LastName") %>' Width="130px" />
<br />
City: <asp:TextBox ID="txtCity" runat="server" Text ='<%# Eval("City") %>' Width="130px" />
<br />
Active: <asp:CheckBox ID="Active" runat="server" Checked = '<%# Eval("Active") %>'/>
</div>
</div>
<div style="clear:both;height:20px">
<!-- this div starts a new line -->
</div>
</ItemTemplate>
</asp:Repeater>
So, it really just the markup, but note how we used Eval(). This is a data expression - it ONLY works inside of grids, listview, repeater. However, it even works for say a Radio button list!
Ok, so the above is our markup. So, now the code to load up this repeater looks like this:
Dim rstTable As New DataTable
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
loadGrid()
ViewState("MyTable") = rstTable
Else
rstTable = ViewState("MyTable")
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()
rstTable.Load(cmdSQL.ExecuteReader)
Repeater1.DataSource = rstTable
Repeater1.DataBind()
End Using
End Sub
And now we get this:
Now, I also dropped in two more plane jane buttons (add, and save).
I just dropped those buttion right after the repater (outside of repeater)
eg:
<br />
<asp:Button ID="cmdAdd" runat="server" Text="Add new" />
<asp:Button ID="cmdSave" runat="server" Text="Save" Style="margin-left:20px" />
If you edit ANY of the 3 - tab around - we can now hit save and again in ONE operation send all changes back to the database.
First, lets wire up the add button. When you hit it, then in place of 3 examples, you have 4. The code is thus this:
Protected Sub cmdAdd_Click(sender As Object, e As EventArgs) Handles cmdAdd.Click
Dim MyOneRow = rstTable.NewRow
' set defaults for this new row
MyOneRow("City") = "Edmonton"
MyOneRow("Active") = True
' add this new row to the table
rstTable.Rows.Add(MyOneRow)
' now update the grid to show this new added row
Repeater1.DataSource = rstTable
Repeater1.DataBind()
End Sub
Again, note how VERY clean and simple this code is.
So, if I just hit that add button, then I would see this:
But, I am 100% free to tab around and edit ANY of the 4 displayed.
So, now we need the save button. That will:
send back to database any changes/edits to existing
save + send back to database any NEW rows.
and if we added a delete button - it would do that for us to!!
The save button code is thus:
Protected Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
' move data from each repeater back to table
For Each rRow As RepeaterItem In Repeater1.Items
Dim OneRow As DataRow = rstTable.Rows(rRow.ItemIndex)
OneRow("FirstName") = CType(rRow.FindControl("txtFirst"), TextBox).Text
OneRow("LastName") = CType(rRow.FindControl("txtLast"), TextBox).Text
OneRow("HotelName") = CType(rRow.FindControl("HotelName"), TextBox).Text
OneRow("City") = CType(rRow.FindControl("txtCity"), TextBox).Text
OneRow("Active") = CType(rRow.FindControl("Active"), CheckBox).Checked
Next
' now write the table back to database
Using cmdSQL As New SqlCommand("SELECT * FROM tblHotels WHERE ID = 0",
New SqlConnection(My.Settings.TEST3))
Dim DA As New SqlDataAdapter(cmdSQL)
Dim daUpdate As New SqlCommandBuilder(DA)
DA.Update(rstTable)
End Using
End Sub
So, we move data from Repeater back to table
Then we tell the system to update that table back to database.
So we did NOT have very much markup.
We were is REALLY great ease able to add a new row, but again let the repeater do all the work of showing that new data row.