Home > database >  How to loop thru all controls on a web page using VB.net
How to loop thru all controls on a web page using VB.net

Time:06-28

I'm trying to come up with a way to loop thru all the controls ( textboxes, check boxes, dropdown lists,etc) on an aspx page. I tried converting some of the solutions given on line for C# to VB but nothing works.

I use the

for all c in me.controls
    id = c.id 
next 

loop but don't get any of the controls on the page. I have also tried this with the same result:

Sub checkcontrol(ByRef Parent As Control)
    Dim c As Control
    Dim x As Integer = Parent.Controls.Count
    For Each c In Parent.Controls
        If c.GetType.ToString = "ASP.site_master" Then
            checkcontrol(c)
        ElseIf c.GetType() Is GetType(TextBox) Then
            'is it a Text Box?
            Dim t As TextBox = c
            Debug.Print("textBox " & c.ID)
        ElseIf c.GetType() Is GetType(DropDownList) Then
            'is it a dropdown list?
            Dim d As DropDownList = c
            Debug.Print("DropDown Box " & c.ID)
            '   d.ClearSelection()
        ElseIf c.GetType() Is GetType(CheckBox) Then
            Debug.Print("check Box " & c.ID)
        ElseIf c.GetType() Is GetType(RadioButton) Then
            Debug.Print("Radio Button " & c.ID)
        End If
    Next
End sub

The first type I get is "ASP.site_master" since the page is linked to the Site Master.I'm on Visual studio community 2017 and running in the debug mode.

What am I doing wrong?

CodePudding user response:

There could be more layers. Always look for child controls:

Sub checkcontrol(ByRef Parent As Control)   
    For Each c As Control In Parent.Controls       
        If c.GetType() Is GetType(TextBox) Then
            'is it a Text Box?
            Dim t As TextBox = c
            Debug.Print("textBox " & c.ID)
        ElseIf c.GetType() Is GetType(DropDownList) Then
            'is it a dropdown list?
            Dim d As DropDownList = c
            Debug.Print("DropDown Box " & c.ID)
            '   d.ClearSelection()
        ElseIf c.GetType() Is GetType(CheckBox) Then
            Debug.Print("check Box " & c.ID)
        ElseIf c.GetType() Is GetType(RadioButton) Then
            Debug.Print("Radio Button " & c.ID)
        End If

        If c.Controls.Count > 0 Then checkcontrol(c)
    Next
End Sub

For fun:

Iterator Function AllControls(ByRef Parent As Control) As IEnumerable(Of Control)
    For Each c As Control In Parent.Controls       
        Yield c
        If c.Controls.Count > 0 Then
            For Eachchild As Control In AllControls(c)
               Yield child
            Next
        End If
    Next
End Sub

And then use it like this:

For Each c As Control In AllControlls(Me)
    Debug.Print($"Type: {c.GetType()}{vbTab}ID: {c.Id}{vbTab}Name: {c.Name}")
Next

CodePudding user response:

Actually, I become super tried of writing code to take a data table row, send to controls on the web page. Let user edit, and then send the values of the controls back to the database. Coming from VBA, vb, vb.net desktop, then it seemed like a silly waste of time to write such code over and over.

So, I wrote a routine do loop the controls. However, on a given page, I often need more then one section (one set of values might come from one table, and another section comes from another table).

So, I assume the grouping of controls I want to work with is placed in a div.

So, say I want to edit a hotel information.

And I don't want to wear out my keyboard.

And I don't want to write the same code over and over every time I want to edit a record?

well, we build 2 routines.

Routine to send a data row to the web page - (but, we use a "div" to control what parts).

Routine to send web page back to data row.

And then routine to send data row (data table) back to database.

The only other part? How do we data bind the controls? Well, we can just out of the blue add any old markup to each control. So, I just use "f" and that means the data column name.

The end result?

We actually wind up with a "mini" frame work to now edit data, do so with out messy binding expressions, and do so without messy templates, or anything.

Just simple code, simple markup, and you now have a full CRUD system.

Example:

Simple grid view display some hotels:

    <div id="MyGrid" runat="server" style="width:40%">
        <asp:GridView ID="GHotels" 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="Description" HeaderText="Description"  />
                <asp:TemplateField HeaderText="Edit">
                    <ItemTemplate>
                        <asp:Button ID="cmdEdit" runat="server" Text="Edit" CssClass="btn" OnClick="cmdEdit_Click" />
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>
        <br />
    </div>

Ok, code to load:

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 cmdSQL As New SqlCommand("SELECT * FROM tblHotelsA ORDER BY HotelName")
    GHotels.DataSource = MyrstP(cmdSQL)
    GHotels.DataBind()

End Sub

And we now have this:

enter image description here

So far, nothing new - dead simple. (don't worry, the helper routines I'll share in a bit).

Ok, so, in above we dropped in a plane jane asp.net button into the grid view (don't bother with the built-in events for the GV - they are more pain then help).

Now, normally, to add a click even to a button, you just double click on such buttons, but since buttons (or any control) inside of a GV does not allow this?

Then you use markup, type in onclick="". You get/see this:

enter image description here

so, choose create new event. Now, we get to use a simple click event in the gridview (this trick works for all controls - drop downs or whatever). This trick saves us world poverty, and we now get to enjoy and use a simple standard button click event for the GV - no messy on row command and that world poverty's causing efforts.

When we click on a row, we will:

hide the GV, display our div to edit the row. Use our handy page looping controls routine (your current question), and fill out the div.

So, first our div we drop below the grid - this is our edit div.

say this:

    <div id="EditRecord" runat="server" style="float:left;display: normal" clientidmode="Static"  >
        <style>
            .iForm label {display:inline-block;width:90px}
            .iForm input {border-radius:8px;border-width:1px;margin-bottom:10px}                
            .iForm textarea {border-radius:8px;border-width:1px;margin-bottom:10px}     
            .iForm input[type=checkbox] {margin-right:8px}
        </style>

        <div style="float:left" >
                <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> <br />
        </div>
        <div style="float:left;margin-left:20px" >
            <label>Description</label> <br />
            <asp:TextBox ID="txtNotes" runat="server" Width="400" TextMode="MultiLine" 
                Height="150px" f="Description" ></asp:TextBox> <br />
            <asp:CheckBox ID="chkActive" f="Active" Text=" Active" runat="server" TextAlign="Right" />
            <asp:CheckBox ID="chkBalcony" f="Balcony" Text=" Has Balcony" runat="server" TextAlign="Right" />
        </div>
        <div style="clear:both"></div>
        <button id="cmdSave" runat="server"  onserverclick="cmdSave_ServerClick" >
            <span aria-hidden="true" > Save</span> 
        </button>

        <button id="cmdCancel" runat="server"  style="margin-left:15px"
            onserverclick="cmdCancel_ServerClick"
            >
            <span aria-hidden="true" > Back/Cancel</span>
        </button>

        <button id="cmdDelete" runat="server"  style="margin-left:15px">
            <span aria-hidden="true" > Delete</span>
        </button>
   </div>

Now, in above, I decided to use just a html button, and the reason why is EASE in which to add icons to the buttons. (but, plane jane asp.net buttons could also have been used).

Noting really special in above EXCEPT for our adding of "f". That's the data base column mapping!!! Really simple, and a gazillion times less work then say using a databound object like repeater or whatever.

So, now the button code click:

Protected Sub cmdEdit_Click(sender As Object, e As EventArgs)

    Dim btn As Button = sender
    Dim gRow As GridViewRow = btn.NamingContainer
    Dim pkID = GHotels.DataKeys(gRow.RowIndex).Item("ID")

    Dim cmdSQL As New SqlCommand("SELECT * from tblHotelsA where ID = @ID")
    cmdSQL.Parameters.Add("@ID", SqlDbType.Int).Value = pkID
    Dim rstData As DataTable = MyrstP(cmdSQL)

    Call fLoader(Me.EditRecord, rstData.Rows(0)) ' load up hidden div with data
    ' hide grid
    MyGrid.Style.Add("display", "none")
    EditRecord.Style.Add("display", "normal")
    ViewState("rstData") = rstData

End Sub

Once again, the above code - very simple, very plain.

We now see this when I hit edit on a given row:

enter image description here

And the save button code:

Protected Sub cmdSave_ServerClick(sender As Object, e As EventArgs)

    Dim rstData As DataTable = ViewState("rstData")
    Call fWriterW(EditRecord, rstData.Rows(0))  ' div to table
    Call SaveTable(rstData, "tblHotelsA")  ' send table back to database

    LoadGrid()    ' refresh grid
    MyGrid.Style.Add("display", "normal")
    EditRecord.Style.Add("display", "none")


End Sub

Again, note how simple the code is. note that I COULD HAVE combined the two routines into one (fwriterW, and SaveTable). but, often after we pull data from web form back to table, we might want to do some additonal process or code on the data BEFORE we save.

So, now the looping code. We have two such routines:

data to web page

web page to data table.

So, this is the looping controls part:

first the loader:

Public Sub fLoader(F As HtmlGenericControl, rst As DataRow)

    For Each c As System.Web.UI.Control In F.Controls
        Select Case c.GetType
            Case GetType(TextBox)
                Dim ctlC As TextBox = c
                If Not ctlC.Attributes("f") Is Nothing Then
                    If rst.Table.Columns.Contains(ctlC.Attributes("f")) Then
                        ctlC.Text = IIf(IsDBNull(rst(ctlC.Attributes("f"))), "", rst(ctlC.Attributes("f")))
                    End If
                End If
            Case GetType(Label)
                Dim ctlC As Label = c
                If Not ctlC.Attributes("f") Is Nothing Then
                    If rst.Table.Columns.Contains(ctlC.Attributes("f")) Then
                        ctlC.Text = IIf(IsDBNull(rst(ctlC.Attributes("f"))), "", rst(ctlC.Attributes("f")))
                    End If
                End If
            Case GetType(DropDownList)
                Dim ctlC As DropDownList = c
                If Not ctlC.Attributes("f") Is Nothing Then
                    If rst.Table.Columns.Contains(ctlC.Attributes("f")) Then
                        ctlC.Text = IIf(IsDBNull(rst(ctlC.Attributes("f"))), "", rst(ctlC.Attributes("f")))
                    End If
                End If
            Case GetType(CheckBox)
                Dim ctlC As CheckBox = c
                If Not ctlC.Attributes("f") Is Nothing Then
                    If rst.Table.Columns.Contains(ctlC.Attributes("f")) Then
                        ctlC.Checked = rst(ctlC.Attributes("f"))
                    End If
                End If
            Case GetType(RadioButtonList)
                Dim ctlC As RadioButtonList = c
                If Not ctlC.Attributes("f") Is Nothing Then
                    If rst.Table.Columns.Contains(ctlC.Attributes("f")) Then
                        ctlC.SelectedValue = rst(ctlC.Attributes("f"))
                    End If
                End If
        End Select
    Next

End Sub

You can over time add a few more control types if you use them. So, note how the above loops the controls, fills them based on "f" attribute setting.

And in my global set of routines, we have also

Public Sub fWriterW(f As HtmlGenericControl, rst As DataRow)

    For Each c As System.Web.UI.Control In f.Controls
        Select Case c.GetType
            Case GetType(TextBox)
                Dim ctlC As TextBox = c
                If Not ctlC.Attributes("f") Is Nothing Then
                    If rst.Table.Columns.Contains(ctlC.Attributes("f")) Then
                        rst(ctlC.Attributes("f")) = IIf(ctlC.Text = "", DBNull.Value, ctlC.Text)
                    End If
                End If
            Case GetType(Label)
                Dim ctlC As Label = c
                If Not ctlC.Attributes("f") Is Nothing Then
                    If rst.Table.Columns.Contains(ctlC.Attributes("f")) Then
                        rst(ctlC.Attributes("f")) = IIf(ctlC.Text = "", DBNull.Value, ctlC.Text)
                    End If
                End If
            Case GetType(DropDownList)
                Dim ctlC As DropDownList = c
                If Not ctlC.Attributes("f") Is Nothing Then
                    If rst.Table.Columns.Contains(ctlC.Attributes("f")) Then
                        rst(ctlC.Attributes("f")) = IIf(ctlC.Text = "", DBNull.Value, ctlC.Text)
                    End If
                End If
            Case GetType(CheckBox)
                Dim ctlC As CheckBox = c
                If Not ctlC.Attributes("f") Is Nothing Then
                    If rst.Table.Columns.Contains(ctlC.Attributes("f")) Then
                        rst(ctlC.Attributes("f")) = ctlC.Checked
                    End If
                End If

            Case GetType(RadioButtonList)
                Dim ctlC As RadioButtonList = c
                If Not ctlC.Attributes("f") Is Nothing Then
                    If rst.Table.Columns.Contains(ctlC.Attributes("f")) Then
                        rst(ctlC.Attributes("f")) = ctlC.SelectedValue
                    End If
                End If


        End Select
    Next

End Sub

This is really just the reverse of the first loop.

So, we pass these routines a "div" (with id, and runat server always).

So the only other two helper routines are:

Public Function MyrstP(sqlCmd As SqlCommand) As DataTable

    Dim rstData As New DataTable
    Using sqlCmd
        Using conn = New SqlConnection(My.Settings.TEST4)
            conn.Open()
            sqlCmd.Connection = conn
            rstData.Load(sqlCmd.ExecuteReader)
        End Using
    End Using
    Return rstData

End Function

Again, I use the above everyone - in all my code. Again, super easy.

And then the code to write the table back:

Sub SaveTable(rstData As DataTable, strTable As String)

    Using conn As New SqlConnection(My.Settings.TEST4)
        Using cmdSQL As New SqlCommand("select * FROM " & strTable, conn)
            Dim da As New SqlDataAdapter(cmdSQL)
            Dim daU As New SqlCommandBuilder(da)
            conn.Open()
            da.Update(rstData)
        End Using
    End Using

End Sub

So, that's about it.

The above in fact is a whole re-useable frame work. It allows you now re-use the above code - allows easy edit of any data. And you not re-writing the same code over and over.

Adopting above code quite gives you true RAD development, and great ease of editing records in web forms - in fact it becomes as easy as desktop software.

So, in most cases looping every control on the page is not all so useful. But, placing controls in a "div" (with id, and runat=server) now allows you to pass THAT single div. It also means that you can have multiple parts on a page - maybe a table of billing address and customer record.

and all of the above just simple works - including when you have a master page.

  • Related