Community discussion forum

Problem with dynamic Tablerows containing controls

  • 1 year ago

    Hi!

    I'm trying to create a page with dynamic controls, and I'm having difficulty wrt the life cycle.

    The page has some static controls, but also a table which has dynamic rows, cells of which contain dynamic controls, including a DropDownList.

    The idea is that the page has an "Add" button, which should result in a new row being added to the table.  The selected values in the earlier DropDownLists must be preserved.


    So, first time the page looks like:

    [Static Controls]
    [TableRow1 [DropDownList1]]
    [Add Button]


    Clicking the button should result in:

    [Static Controls]
    [TableRow1 [DropDownList1]]
    [TableRow2 [DropDownList2]]
    [Add Button]

    with DropDownList1 selection being preserved.



    The Button click event code tries to read the current selections of the DDLs and store them in session, It then causes a redirect to the same page, forcing the page to be drawn as new (from the life cycle point of view).


    My sequence of code is:

        protected void Page_Load(object sender, EventArgs e) {
            if (!IsPostBack) {
                populate();
            }
            populateVolatile();
        }

    where populate() sets up the static controls with data retrieved from a database (first time) or session (subsequently), and

    populateVolatile() creates and fills the dynamic controls depending on the session data, including DDL selections.

    It contains the code:

            // add each type/num info to table
            foreach (TypeNumInfo tni in volInfo.typeNumRows) {
                TableRow tr = new TableRow();

                // index number
                TableCell tc1 = new TableCell();
                tc1.Text = tni.idx.ToString();
                tr.Cells.Add(tc1);

                // add a DDL
                TableCell tc2 = new TableCell();
                DropDownList ddl = new DropDownList();

                // add ID and list data
                ddl.ID = "ddl" + tni.idx.ToString();
                foreach (ListItem li in dTypes) {
                    ddl.Items.Add(li);
                }

                // set the selected
                // TODO not working
                ddl.SelectedIndex = tni.typeSel;

                tc2.Controls.Add(ddl);
                tr.Cells.Add(tc2);

                // add row to table
                tblTypeNum.Rows.Add(tr);
            }


    The button click code is as follows:

        protected void bAddTypeNum_Click(object sender, EventArgs e) {
            // get dept, applies selections from static controls
            statInfo.selDept = ddlDept.SelectedIndex;
            statInfo.selApplies = ddlApplies.SelectedIndex;

            // create new type/num info representing tablerow data
            TypeNumInfo tni = new TypeNumInfo();
            int lastIdx = volInfo.typeNumRows.Count;
            tni.idx = lastIdx + 1;

            // add to list
            volInfo.typeNumRows.Add(tni);

            // cache previous rows' info
            int i = 0;
            foreach (TableRow tr in tblTypeNum.Rows) {
                // if row has droplist, i.e. more than 1 cell
                if (tr.Cells.Count > 1) {
                    DropDownList ddl = (DropDownList)tr.Cells[1].Controls[0];

                    // get the selection in that row
                    // TODO not working reliably
                    volInfo.typeNumRows[i++].typeSel = ddl.SelectedIndex;
                }
            }

            // update session
            Session[strStat] = statInfo;
            Session[strVol] = volInfo;

            // draw as new page
            Response.Redirect("");
        }

    statInfo and volInfo are sessionised objects that contain static and dynamic control data and state.

    I know the redirect creates an extra trip to the server, but I couldn't find a more useful way to enable redrawing after the button event.

    Everything works fine except for the dynamic DDL selection handling - I cannot set or correctly read the selections.

    Help!


    John
     

  • 1 year ago

    Can you provide the error being thrown John, or perhaps a better explanation over what happens when the table is populated. I've just replicated your code and all seems well:

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    Dim rows As Integer = 9

    Dim table As New Table()

    Dim row As TableRow

    Dim cell As TableCell

    Dim dropdown As DropDownList

    For i As Integer = 0 To 9

    row = New TableRow()

    cell = New TableCell()

    cell.Text = "SomeText"

    row.Cells.Add(cell)

    cell =
    New TableCell()

    dropdown = New DropDownList()

    dropdown.ID = "DropDown" + i.ToString

    For j As Integer = 0 To 5

    dropdown.Items.Add("Item " + j.ToString)

    Next

    cell.Controls.Add(dropdown)

    row.Cells.Add(cell)

    table.Rows.Add(row)

    Next

    Place1.Controls.Add(table)

    End Sub

  • 1 year ago

    [quote user="D'Scouser"]

    Can you provide the error being thrown John, or perhaps a better explanation over what happens when the table is populated. I've just replicated your code and all seems well:

    [/quote] 

     

    Hiya,

     

    No error thrown - it just doesn't draw the DDLs with the correct selections after button click.  I've used the debugger to step through, the correct values are being extracted from session and the assignment seems to go ok.  But when the page appears, all the DDLs display as selection 0;

     

    Interestingly, when the button is clicked a second time to produce a third row, the loop to extract the selection values from the DDLs pulls the same value from the first two DDLs, even though the input was for different values.  This makes me think that the user input is not filtering through correctly, or that the system is somehow using the same memory DDL instance for the three on the page or their contained lists.  Debugger won't let me look inside the DDLs with enough detail.  It made me think perhaps I'm not using the life-cycle correctly.

     

    I placed the table in a Panel on the static page - should I be doing something else, such as a Placeholder in each DDL cell?   I'm not clear on the significance of Panels and Placeholders wrt functionality.

     

  • 1 year ago

    Can't you save an ArrayList in Session, and then type it out when needed. I'm imagining something is going wrong saving the values to state. By the way, have you set Session state in the config file?

  • 1 year ago

    [quote user="D'Scouser"]

    Can't you save an ArrayList in Session, and then type it out when needed. I'm imagining something is going wrong saving the values to state. By the way, have you set Session state in the config file?

    [/quote] 

     

    Hiya,

    I'm just using the default session state, but that seems to be fine.  I am indeed using a sessionised list to populate the DDLs, and that part is working fine.

    It's only the selection assignment and read that's giving the problem.

     

    I've pruned down the code and substituted the DB access with strings, so I'm showing it here in its entirety.  It's not too long, not enough to embarrass me hopefully!

     

    using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    using System.Text;
    using System.Collections.Generic;

    public partial class DocSearchAdvanced : System.Web.UI.Page {
        protected StatInfo statInfo;
        protected VolInfo volInfo;
        protected List<ListItem> dTypes;
        private String strVol = "Volatile";
        private String strStat = "Static";
        private String strDocType = "dtypes";
        

        protected void Page_Load(object sender, EventArgs e) {
            if (!IsPostBack) {
                populate();
            }
            populateVolatile();
        }


        
        // populates the static controls and sets up once-only info
        protected void populate() {
            // get info from session if exists
            statInfo = (StatInfo)Session[strStat];

            // if first time, set up static info
            if (statInfo == null) {
                statInfo = new StatInfo();

                // set defaults
                statInfo.selDept = 0;
                statInfo.selApplies = 0;

                // cache to session
                Session[strStat] = statInfo;
            }

            // department and applies droplist - contain same information
            ddlDept.Items.Add(new ListItem("Any", "0"));
            ddlApplies.Items.Add(new ListItem("Any", "0"));
            // add "departments", as if from DB
            for (int i=1; i<5; i++) {
                StringBuilder sb = new StringBuilder();
                sb.Append("Dept").Append(i.ToString()).Append(" (").Append("D").Append(i.ToString()).Append(")");
                ddlDept.Items.Add(new ListItem(sb.ToString(), i.ToString()));
                ddlApplies.Items.Add(new ListItem(sb.ToString(), i.ToString()));
            }

            // set current selections
            ddlDept.SelectedIndex = statInfo.selDept;
            ddlApplies.SelectedIndex = statInfo.selApplies;


            // document types
            dTypes = (List<ListItem>)Session[strDocType];

            // if first time, set up doc types as if from DB
            if (dTypes == null) {
                dTypes = new List<ListItem>();
                dTypes.Add(new ListItem("Any", "0"));
                for (int i=1; i<5; i++) {
                    StringBuilder sb = new StringBuilder();
                    sb.Append("DocType").Append(i.ToString()).Append(" (").Append("DT").Append(i.ToString()).Append(")");
                    dTypes.Add(new ListItem(sb.ToString(), i.ToString()));
                }

                // cache it
                Session[strDocType] = dTypes;
            }
        }

        protected void getSession() {
            // get static data if in postback
            if (IsPostBack) {
                statInfo = (StatInfo)Session[strStat];
                dTypes = (List<ListItem>)Session[strDocType];
            }

            // get dynamic typenum info from session if exists
            volInfo = (VolInfo)Session[strVol];

            // first time?
            if (volInfo == null) {
                // need to create volinfo and add first typenum row
                volInfo = new VolInfo();

                List<TypeNumInfo> typeRows = new List<TypeNumInfo>();
                TypeNumInfo tni = new TypeNumInfo();
                // index is 1, selection 0
                tni.idx = 1;
                tni.typeSel = 0;

                typeRows.Add(tni);
                volInfo.typeNumRows = typeRows;

                // update session
                Session[strVol] = volInfo;
            }
        }


        protected void populateVolatile() {
            if (IsPostBack) {
                tblTypeNum.Rows.Clear();
            }

            // get the session data
            getSession();

            // add each typenum info to table
            foreach (TypeNumInfo tni in volInfo.typeNumRows) {
                TableRow tr = new TableRow();

                // index number
                TableCell tc1 = new TableCell();
                tc1.Text = tni.idx.ToString();
                tr.Cells.Add(tc1);

                // add a DDL
                TableCell tc2 = new TableCell();
                DropDownList ddl = new DropDownList();

                // add ID and list data
                ddl.ID = "ddl" + tni.idx;
                foreach (ListItem li in dTypes) {
                    ddl.Items.Add(li);
                }

                // set the selected
                // TODO not working
                ddl.SelectedIndex = tni.typeSel;

                tc2.Controls.Add(ddl);
                tr.Cells.Add(tc2);

                TableCell tc3 = new TableCell();
                tc3.Text = "Should be " + ddl.SelectedItem.Text;
                tr.Cells.Add(tc3);

                tblTypeNum.Rows.Add(tr);
            }

            // add row with button
            TableRow trb = new TableRow();
            TableCell tcb = new TableCell();
            int rwidth = tblTypeNum.Rows[0].Cells.Count;
            tcb.ColumnSpan = rwidth;  // colspan as first row

            Button bAddTypeNum = new Button();
            bAddTypeNum.Text = "Add New Row";
            bAddTypeNum.Click += new EventHandler(bAddTypeNum_Click);
            tcb.Controls.Add(bAddTypeNum);
            trb.Cells.Add(tcb);

            tblTypeNum.Rows.Add(trb);
        }


        // add typenum row
        protected void bAddTypeNum_Click(object sender, EventArgs e) {
            // get dept, applies selections from static controls
            statInfo.selDept = ddlDept.SelectedIndex;
            statInfo.selApplies = ddlApplies.SelectedIndex;

            // create new type/num info representing tablerow data
            TypeNumInfo tni = new TypeNumInfo();
            int lastIdx = volInfo.typeNumRows.Count;
            tni.idx = lastIdx + 1;

            // add to list
            volInfo.typeNumRows.Add(tni);

            // cache previous rows
            int i = 0;
            foreach (TableRow tr in tblTypeNum.Rows) {
                // if row has droplist, i.e. more than 1 cell
                if (tr.Cells.Count > 1) {
                    DropDownList ddl = (DropDownList)tr.Cells[1].Controls[0];
                    // get the selected in that row
                    // TODO not working reliably
                    volInfo.typeNumRows[i++].typeSel = ddl.SelectedIndex;
                }
            }

            // update session
            Session[strStat] = statInfo;
            Session[strVol] = volInfo;

            // draw as new page
            Response.Redirect("");
        }


        protected void bSearch_Click(object sender, EventArgs e) {
            // TODO
        }

        // class for type/num rows
        protected class TypeNumInfo {
            public int idx;
            public int typeSel;
        }

        // class for static data
        protected class StatInfo {
            // Department[] depts;
            public int selDept;
            public int selApplies;
        }

        // class for volatile control data
        protected class VolInfo {
            public List<TypeNumInfo> typeNumRows;
        }
    }

     


    ... and the markup, in case the problem is there...

     


     

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="DocSearchAdvanced.aspx.cs"
        Inherits="DocSearchAdvanced" %>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <asp:Panel ID="PanelEdit" runat="server">
                    &nbsp;<table border="0" cellpadding="0" cellspacing="0" style="width: 800px">
                        <tr>
                            <td style="width: 200px; height: 19px;">
                                Department</td>
                            <td style="width: 200px; height: 19px;">
                                Applies to</td>
                        </tr>
                        <tr>
                            <td style="width: 200px">
                                <asp:DropDownList ID="ddlDept" runat="server" Width="172px">
                                </asp:DropDownList></td>
                            <td style="width: 200px">
                                <asp:DropDownList ID="ddlApplies" runat="server">
                                </asp:DropDownList></td>
                        </tr>
                        <tr>
                            <td colspan="2" style="height: 163px">
                                <asp:Table ID="tblTypeNum" runat="server" Height="144px" Width="741px" BorderColor="Black"
                                    BorderWidth="1px" GridLines="Both">
                                </asp:Table>
                            </td>
                        </tr>
                        <tr>
                            <td style="width: 200px">
                            </td>
                            <td style="width: 200px">
                                <asp:Button ID="bSearch" runat="server" OnClick="bSearch_Click" Text="Search" /></td>
                        </tr>
                    </table>
                    &nbsp;<br />
                </asp:Panel>
            </div>
        </form>
    </body>
    </html>
     

  • 1 year ago

    Hey John, I think I know the problem by looking at how your setting up your session store, because I think I've run into this problem before. But I'll just check. Does the values saved in the store not come out correctly, or completely disappear when doing a postback?

  • 1 year ago

    Try setting the session store in the page load before a postback; I'll just use my code, but the rule is the same regardless:

    PageLoad

    SetupDataStore()

    If Not Postback Then

    Do your population here...

    Else

    Do your popvolatile here...

    End If

    End Sub

     

    Sub SetupDataStore()

           If Me.Session("products") Is Nothing Then
                dtOrders = CheckoutClass.BuildTempColumns()
                Me.Session("products") = dtOrders
            Else
                dtOrders = CType(Me.Session("products"), Data.DataTable)
            End If

    End Sub

     
  • 1 year ago

    I think you need to test your datastore before your postback, try this method. I'll use my code, but the rule is basically the same:

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

            SetDataStore()

            If Not IsPostBack Then

                Do your populate here...

            Else

                 Do your volatilepop here...

            End If
        End Sub

    Private Sub SetDataStore()
            If Me.Session("products") Is Nothing Then
                dtOrders = CheckoutClass.BuildTempColumns()
                Me.Session("products") = dtOrders
            Else
                dtOrders = CType(Me.Session("products"), Data.DataTable)
            End If
        End Sub

    Hope this helps, but there may be other issues depending on the kind of retrieval your doing. Feel free to get back to me if other problems arise.

  • 1 year ago

    Not sure how that happened!

  • 1 year ago

    Hiya,

    Datastore is working fine as far as I can tell.  I've taken the debugger right through it, and all the values are extracted correctly.  The code more or less does what you're suggesting, StaticPop is called first time, followed by volatilePop.  Only volatilePop is called at postback.

     

    The staticPop happens once per new or repeat-redirected page, and this sets up the static data for session first time round, and subsequently uses it for the static DDLs.  That's working fine.


    The volatilePop calls its own getSession, which sets up its data first time in, and retrieves it from session each subsequent call.  Again, this all seems to work fine - the dynamic DDLs are being correctly populated.

     

    Using the debugger to step through, checking what's retrieved from session, everything seems to be OK.  The selection assignment appears to complete in the code, it's just when the page appears that the dynamic DDL selections are not set.  They are still fully populated.

     

  • 1 year ago

     RESOLVED!

     

    The page lifecycle concept is fine - the problem is in the nature of  the DropDownList control.  Fixed by closer RTFM!

     

    The DDL is a container for ListItems which represent the items in the droplist.  When one is selected, the selection information is stored as a boolean in that particular ListItem, not in the DDL container. 

     

    As I have several DDLs containing the same information, they all used the same instances of ListItems.  So when one DDL selection was made, the same selection reflected in the other DDLs.  The solution is to create  a new instance of each master ListItem and adding that to the DDL container, not the master ones themselves.  So each DDL has its own unique ListItem instances, albeit reflecting the same underlying data.

     

Post a reply

Enter your message below

Sign in or Join us (it's free).

We'd love to hear what you think! Submit ideas or give us feedback