skip to Main Content

I’ve managed to successfully create a panel with dynamically created label and textbox created controls and now I need to be able to place them in a 1 (easy), 2 or 3 column display within the panel (based on user selection). This is the code that generates the controls"

If MydataSet.Tables(0).Rows.Count > 0 Then
    For Each MyDataRow As DataRow In MydataSet.Tables(0).Rows
        Me.CreateLabel("lblDynamic_" & MyDataRow("ID"), MyDataRow("Title"))

        Me.CreateTextBox("txtDynamic_" & MyDataRow("ID"))
    Next
End If

CreateLabel and CreateTextbox code:

Private Sub CreateTextBox(id As String)
    Dim txt As New TextBox()
    txt.ID = id
    Panel_AdditionalData.Controls.Add(txt)

    Dim lt As New Literal()
    lt.Text = "<br />"
    Panel_AdditionalData.Controls.Add(lt)
End Sub

Private Sub CreateLabel(id As String, value As String)
    Dim lbl As New Label()
    lbl.ID = id
    lbl.Text = value
    lbl.CssClass = "label"
    Panel_AdditionalData.Controls.Add(lbl)

    Dim lt As New Literal()
    lt.Text = "<br />"
    Panel_AdditionalData.Controls.Add(lt)
End Sub

Obviously there is more code to manage postbacks and retrieving inputted data, but for the purposes of layout posting that code isn’t relevant.

I retrieve the user defined data fields from the database and use that information to create each control. Creating each control in a single column is easy, just add the control, no problem. I’ve already done that. However, creating a multi-column layout is proving to be difficult and everything I’ve tried thus far has failed. Anyone have any suggestions?

2

Answers


  1. Chosen as BEST ANSWER

    As my research and the comments/answers posted in response to my question point out, there are a myriad of ways one can get the job done. Given my time constraints and the need to get the infrastructure in place to provide this functionality to my users, I went with the "quick and relatively" easy method.

    This is what I went with:

    ; defined in the page class
    Shared ColumnCount As Integer = 1
    
    ' class initiated with the users layout preferences and other user information
    ' MyUserPrefrence.NumberOfColumns  
    
    ' defined in the page load event
    Dim MydataSet As New DataSet ' Data field information loaded from database
    Dim MyHTMLTable As New HtmlTable
    Dim MyTableRow As New HtmlTableRow
    Dim MyTableCell As New HtmlTableCell
    
    If MydataSet.Tables(0).Rows.Count > 0 Then
        For Each MyDataRow As DataRow In MydataSet.Tables(0).Rows
            MyTableCell = New HtmlTableCell
    
            Dim lbl As New Label()
            lbl.ID = ID
            lbl.Text = MyDataRow("Title")
            lbl.CssClass = "label"
            Dim lt As New Literal()
            lt.Text = "<br />"
    
            MyTableCell.Controls.Add(lbl)
            MyTableCell.Controls.Add(lt)
    
            Dim txt As New TextBox()
            txt.ID = MyDataRow("ID")
            lt = New Literal()
            lt.Text = "<br />"
            MyTableCell.Controls.Add(txt)
            MyTableCell.Controls.Add(lt)
    
    
            If ColumnCount < MyUserPrefrence.NumberOfColumns Then
                MyTableRow.Cells.Add(MyTableCell)
                ColumnCount += 1
            Else
                MyTableRow.Cells.Add(MyTableCell)
                MyHTMLTable.Rows.Add(MyTableRow)
                MyTableRow = New HtmlTableRow
                ColumnCount = 1
            End If
    
        Next
        MyHTMLTable.Rows.Add(MyTableRow)
        Panel_AdditionalData.Controls.Add(MyHTMLTable)
        ColumnCount = 1 'Reset colum count to 1
    

    The page that contains the dynamic controls will not be posted back to the server via the usual post back method but rather by an ajax method and json data posted to a web method in the code behind to avoid any issues with maintaining the dynamic fields on a post back. I have been gradually going to this method of posting back data as it is (apparently) very efficient and avoids screen refreshing and I am able to provide immediate feedback to the user in real time a lot more cleanly. I find that very appealing. In this particular instance it also solves the problem of linking the data to a particular entity whether it is a new or existing (edited) entity.


  2. One of the approaches in webforms is always try to achieve this with a data bound option. (GridView, ListView, Repeater). Not only does this simplify the code, but MORE important when it comes time to click on a row, process a row etc., then you have a lot more "row click" options. And even in those cases in which you don’t need a row click option, you still are better off to use some layout for repeating data.

    While you can "claim" that the controls are "dynamic" in your approach, looking at the code, we see hard coded column names anyway.

    So, I not saying you never should "write code" to inject controls to repeat data and place controls on the web form, but I am suggesting you ONLY want to go down that road after exhausting the data bound choices we have.

    In other words, while quite a few web approaches do "hint" and "suggest" that you can inject controls, in web form land, it should be a last resort.

    I have done boatloads of work in web forms, and in fact I can’t EVER think of a case in which using the built in system to "repeat" data (and thus controls) was always the better choice. As noted, the result then in effect a type of "object" on the form that your code behind can now process with great ease.

    I’ll also point out that injected controls tend to NOT keep their view state, and thus any post back means such controls are NOT persisted, and thus on every post back, such controls will disappear, and that injecting code will have to run again. All in all, it gets real messy real fast. So, exhaust the easy road and choices first.

    My personal favorite is the ListView. (it has unlimited layout, can render as a grid, but like Access continues forms, you can have more then one line for the controls that make up one record of the data source).

    While a GridView also works well, I find when using standard controls (that I want to fill out and repeat for each row a bit of a pain, since each control has to be placed inside of a template start tag and end tag. So, for just a few columns, then GridView is fine, but for more complex layout (and more columns) then jumping over to the ListView wins the day.

    So, most simple would be a repeater. It will repeat the markup for each data row.

    So, I might then say have this:

    (FirstName, LastName, HotelName).

    So, then this:

    <asp:Panel ID="Panel1" runat="server">
        <h3>Please select Hotels to process</h3>
        <asp:Repeater ID="Repeater1" runat="server">
            <ItemTemplate>
                <asp:TextBox ID="fn" runat="server"
                    Text='<%# Eval("FirstName") %>'>                        
                </asp:TextBox>
    
                <asp:TextBox ID="ln" runat="server"
                    Style="margin-left: 20px"
                    Text='<%# Eval("LastName") %>'>                        
                </asp:TextBox>
    
                <asp:Label ID="lblHotel" runat="server"
                    Style="margin-left: 20px"
                    Text='<%# Eval("HotelName") %>'>                        
                </asp:Label>
                <asp:CheckBox ID="chkSel" runat="server"
                    Style="margin-left: 20px"
                    Text="Select" />
                <asp:HiddenField ID="dataPK" runat="server" 
                    Value='<%# Eval("ID") %>' />
                <br />
                <hr />
            </ItemTemplate>
        </asp:Repeater>
        <br />
        <asp:Button ID="cmdProcess" runat="server"
            Text="Process Selected"
            CssClass="btn" />
        <label style="margin-left:30px">Selected rows are:</label>
        <asp:TextBox ID="txtSel" runat="server"></asp:TextBox>
    </asp:Panel>
    

    So, while it takes somewhat more markup, our code behind now becomes very simple, since all we do is "feed" the data.

    Hence this code:

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    
        If Not IsPostBack Then
    
            ' the REAL first page load code goes here
    
            Dim strSQL As String =
                "SELECT TOP 3 ID, FirstName, LastName, HotelName
                FROM tblHotelsA ORDER BY HotelName"
    
            Repeater1.DataSource = MyRst(strSQL)
            Repeater1.DataBind()
    
        End If
    End Sub
    

    And the result is this:

    enter image description here

    And our button click can now with ease process some rows.

    Say this code:

    Protected Sub cmdProcess_Click(sender As Object, e As EventArgs)
    
        Dim sPKList As String = ""
    
        For Each OneRow As RepeaterItem In Repeater1.Items
    
            Dim chkBox As CheckBox = OneRow.FindControl("chkSel")
            Dim fPK As HiddenField = OneRow.FindControl("dataPK")
    
            If chkBox.Checked Then
                If sPKList <> "" Then sPKList &= ","
                sPKList &= fPK.Value
            End If
    
        Next
    
        txtSel.Text = sPKList
    
    End Sub
    

    So, it doesn’t matter if we have 2 or 15 rows. Our code is still the same.

    The result thus looks like this:

    enter image description here

    However, as noted, much better would be to use a "column" layout, and thus a GridView, or ListView would be a better choice.

    But, the bonus and cool part? I can change what my markup looks like, but VERY little code behind now changes (a huge bonus here).

    So, let’s use a GridView.

    <asp:Panel ID="Panel1" runat="server" Width="40%">
        <h3>Please select Hotels to process</h3>
    
        <asp:GridView ID="GridView1" runat="server"
            DataKeyNames="ID"
            AutoGenerateColumns="false"
            CssClass="table table-hover"
            >
    
            <Columns>
                <asp:TemplateField HeaderText="First Name">
                    <ItemTemplate>
                        <asp:TextBox ID="fn" runat="server"
                            Text='<%# Eval("FirstName") %>'>                        
                        </asp:TextBox>
                    </ItemTemplate>
                </asp:TemplateField>
    
                <asp:TemplateField HeaderText="Last Name">
                    <ItemTemplate>
                        <asp:TextBox ID="ln" runat="server"
                            Text='<%# Eval("LastName") %>'>                        
                        </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="Select">
                    <ItemTemplate>
                        <asp:CheckBox ID="chkSel" runat="server"
                    />
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>
        <br />
        <asp:Button ID="cmdProcess" runat="server"
            Text="Process Selected"
            OnClick="cmdProcess_Click"
            CssClass="btn" />
        <label style="margin-left:30px">Selected rows are:</label>
        <asp:TextBox ID="txtSel" runat="server"></asp:TextBox>
    </asp:Panel>
    

    Note how I applied the bootstrap class "table, and table-hover. This makes the GridView "size" automatic to whatever page size you have (it’s responsive). And note the nice hover effect.

    This:

    enter image description here

    And my code behind is almost the same. The nice advantage (of GridView or ListView) is they allow you to "hide" and not have to show the database PK id in the markup (this is nice for added security).

    So, code behind is now this:

    In the first data load, I have this:

            GridView1.DataSource = MyRst(strSQL)
            GridView1.DataBind()
    

    And the row processing code is this:

    Protected Sub cmdProcess_Click(sender As Object, e As EventArgs)
    
        Dim sPKList As String = ""
    
        For Each OneRow As GridViewRow In GridView1.Rows
    
            Dim chkBox As CheckBox = OneRow.FindControl("chkSel")
            Dim fPK As String = GridView1.DataKeys(OneRow.RowIndex)("ID")
    
            If chkBox.Checked Then
                If sPKList <> "" Then sPKList &= ","
                sPKList &= fPK
            End If
    
        Next
    
        txtSel.Text = sPKList
    
    End Sub
    

    And say for some rows, I did not want the Hotel to show, there are many options I can have/choose to hide and format each row – and I don’t have to write loops for above.

    And I can do things like highlight the given row, say based on some other column in the database.

    But, again, note how my code did not change much, and this demonstrates how placing your layout things in the markup and NOT code behind allows layouts to be changed with ease.

    Almost no code changes occurred for a vast different layout.

    So, this post is already a bit long, but it does show that using some kind of "data aware" control moves out the layout code and controls code to the markup. Thus, code behind becomes less efforts.

    Where the "huge" gains come is if you need to then accept, see, or view the controls, and if the user makes changes to not only the check box, but the text box controls, then you again can loop over that set of controls, and once again, the code to save/send such data back to the database can be done with far greater ease.

    I am most tempted to post a ListView, since as noted, I tend to like that control the most, and this is especially the case if the data in the controls has to be changed, or saved back to the database.

    Also, for completeness, I fast become tired of typing code over and over to pull data, so I have some helper routines (global module), and used my handy dandy MyRst routine.

    That code was this:

    Public Function MyRst(strSQL As String) As DataTable
        ' general get any data from SQL
    
        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
    

    Now, if your goal is to SAVE the typed in data for above? (or allow edits), then just ask, and I’ll post some code to deal with that (and I do NOT suggest using the built in edit events and editing, since they are a messy pain to use).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search