There are lots of examples on this, and I'm pretty sound with the concept of using recursion to find the control. Once the control has been found on postback, it can be interacted with, etc.
I have an empty asp:table in my HTML markup:
<asp:Table ID="editDataTable" runat="server">
</asp:Table>
And on Page_Load the table is populated with many rows and many columns (I am quite proud that I figured that out). Inside some of the table cells there is a <asp:TextBox />.
You've guessed it, I need to get the value of these text boxes!
(I've got the code for recursion and checked it, and it seems good.)
My table has two columns. The left one contains titles like "Company Name, Telephone", etc. and the right column contains text boxes with its respective title's value. So a user can edit the text box (if the telephone number has change for example) and submit the changes.
Obviously the rows are dynamically added depending on the user.
The problem I'm having is: You need to ADD the control to the page when populating the table. Something along the lines of:
myTable.Control.Add(new TextBox());
In my case, my Table is called editDataTable. So in my code where I add the Rows, I've added the control too, as follows.
for (int i = 0; i < rowCount; i++)
{
editDataTable.Rows.Add(tblRow[j]); // This is where I add the ROW to my sexy table
editDataTable.Controls.Add(new TextBox()); // This is where I add the control
}
Those awake amongst you will know that you can't add a text box control to a table!
So finally, my questions are:
How do I add the control for The Text Boxes in my table?
Where do I add them?
Is there any extra advice to help me fulfill my quest of retrieving the text values of my dynamically added text boxes?
Here's my recursive code just in case you were curious:
private void getControls(Control parent)
{
foreach (Control c in parent.Controls)
{
if (c is TextBox && c.ID == null)
{
//Stuff
}
if (c.Controls.Count > 0)
{
getControls(c);
}
}
}
Here is an example of building a dynamic table in ASP.NET during PageLoad, and reading in the values through a postback. Since the table is dynamic, it will NOT be rebuilt when the page is postback to the server. If you want to rebuild the table, you'll need to render it again and use the values you pull in from Request.Form to re-populate it.
HTML Markup
<asp:Table ID="editDataTable" runat="server">
</asp:Table>
<asp:Button runat="server" Text="Submit" OnClick="Submit_Click" />
Code Markup
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
string[] dynamicTable = { "First Name", "Last Name", "Address", "Phone" };
foreach (string s in dynamicTable)
{
TableRow row = new TableRow();
// Add First Cell with Text to Row
row.Cells.Add(new TableCell() { Text = s });
// Create Second Cell
TableCell textBoxCell = new TableCell();
// Add Textbox Control to Second Cell
textBoxCell.Controls.Add(new TextBox() { ID = "Dynamic_" + s.Replace(" ","_") });
// Add Second Cell to Row
row.Cells.Add(textBoxCell);
// Add New Row to Table
editDataTable.Rows.Add(row);
}
}
}
protected void Submit_Click(object sender, EventArgs e)
{
for (int i = 0; i < Request.Form.Count; i++)
{
string key = Request.Form.GetKey(i);
if (key.Contains("Dynamic_"))
{
Response.Write("<p><strong>" + (key.Remove(0,8)).Replace("_"," ") + "</strong> :: " + Request.Form[i] + "</p>");
}
}
}
You need to add a TableCell to the TableRow.Cells collection, and add the TextBox to the TableCell.Controls collection:
TableCell cell = new TableCell();
cell.Controls = new TextBox();
tblRow[j].Cells.Add(cell);
editDataTable.Rows.Add(tblRow[j]);
The most direct way to access the textboxes is to keep them all in a list:
List<TextBox> textBoxes = new List<TextBox>();
and replace cell.Controls = new TextBox(); above with this:
TextBox tb = new TextBox();
textBoxes.Add(tb);
cell.Controls = tb;
And then you can iterate through the textBoxes afterward without having to search for them in a tree.
Related
I have a Table dynamically generated and inside of 2 columns there are TextBoxes. During the postback the Textboxes are always empty even if I fill them.
The table is builted in this way:
protected void ddlScalaTaglie_SelectedIndexChanged(object sender, EventArgs e)
{
DataTable dtRighe = oTab.getScaleTaglieRighe(ddlScalaTaglie.SelectedValue);
//dt is datatable object which holds DB results.
Table tbl = new Table();
tbl.CssClass = "table table-striped table-bordered table-responsive";
TableRow tr;
TableCell tcEU, tcUS, tcUK, tcQty, tcEAN;
Label lbEU, lbUS, lbUK, lbQty, lbEAN;
TextBox tbEU, tbUS, tbUK, tbQty, tbEAN, tbToFocus = null;
foreach (DataRow dr in dtRighe.Rows)
{
tr = new TableRow();
//ean
tcEAN = new TableCell();
tbEAN = new TextBox();
tbEAN.ID = "txtEAN" + dr[0].ToString();
tbEAN.Width = new Unit(100, UnitType.Percentage);
tbEAN.Columns = 15;
tcEAN.Controls.Add(tbEAN);
tr.Controls.Add(tcEAN);
//Qty
tcQty = new TableCell();
tbQty = new TextBox();
tbQty.ID = "txtQty" + dr[0].ToString();
tbQty.TextMode = TextBoxMode.Number;
tcQty.Controls.Add(tbQty);
tr.Controls.Add(tcQty);
tbl.Controls.Add(tr);
}
Session["tbl"] = tbl;
divTaglieRighe.Controls.Add(tbl);
}
When I click the button SAVE, I have to loop throug my table and save all the TextBox.Text...
this is what I wrote:
ArrayList arrScarpaFigli = new ArrayList();
Table tbl = (Table)Session["tbl"];
foreach (TableRow row in tbl.Rows)
{
foreach (TableCell cell in row.Cells)
{
foreach (Control ctrl in cell.Controls)
{
//CONTROL IS TEXBOXT: EXTRACT VALUES//
if (ctrl is TextBox)
{
TextBox txt = (TextBox)ctrl;
arrScarpaFigli.Add(txt.Text);
}
}
}
}
The problem is that, also if I fill the textboxes, in my Text there is always an empty string.
The only moment when I fill the table is on the selectionChange of a Dropdown.
What I'm doing wrong?
Thanks in advance
You have to recreate dynamic controls on every page load to make sure their values are retained after a PostBack. So you need to create a new Method that handles the creation of the Table every time the page is loaded.
protected void Page_Load(object sender, EventArgs e)
{
//always create the controls on every page load if there is a value selected in ddlScalaTaglie
if (!string.IsNullOrEmpty(ddlScalaTaglie.SelectedValue))
{
createTable(ddlScalaTaglie.SelectedValue);
}
}
protected void Button1_Click(object sender, EventArgs e)
{
//use findcontrol to find the Table inside the placeholder
Table tbl = Page.FindControl("divTaglieRighe").FindControl("Table1") as Table;
//loop all rows and cells in the Table
foreach (TableRow row in tbl.Rows)
{
foreach (TableCell cell in row.Cells)
{
foreach (Control ctrl in cell.Controls)
{
//the control is a textbox
if (ctrl is TextBox)
{
//cast the control back to a textbox
TextBox tb = ctrl as TextBox;
//does the checkbox have a value, if so append the label
if (!string.IsNullOrEmpty(tb.Text))
{
Label1.Text += tb.Text + "<br>";
}
}
}
}
}
}
protected void ddlScalaTaglie_SelectedIndexChanged(object sender, EventArgs e)
{
//no need to create the Table dynamically, that will be handled in Page_Load
//this method is just a dummy to trigger a PostBack
//you could remove this method and the OnSelectedIndexChanged from the DropDown
//and just keep the AutoPostBack="true", that will also work
}
private void createTable(string value)
{
DataTable dtRighe = Common.LoadFromDB();
//create a new table WITH id, that is needed for findcontrol
Table tbl = new Table();
tbl.ID = "Table1";
//loop all rows in the datatable
foreach (DataRow dr in dtRighe.Rows)
{
TableRow tr = new TableRow();
//ean
TableCell tcEAN = new TableCell();
TextBox tbEAN = new TextBox();
tbEAN.ID = "txtEAN" + dr[0].ToString();
tbEAN.Width = new Unit(100, UnitType.Percentage);
tbEAN.Columns = 15;
tcEAN.Controls.Add(tbEAN);
tr.Controls.Add(tcEAN);
//Qty
TableCell tcQty = new TableCell();
TextBox tbQty = new TextBox();
tbQty.ID = "txtQty" + dr[0].ToString();
tbQty.TextMode = TextBoxMode.Number;
tcQty.Controls.Add(tbQty);
tr.Controls.Add(tcQty);
tbl.Controls.Add(tr);
}
//add the table to the placeholder
divTaglieRighe.Controls.Add(tbl);
}
The aspx to make the example complete
<asp:DropDownList ID="ddlScalaTaglie" runat="server" OnSelectedIndexChanged="ddlScalaTaglie_SelectedIndexChanged" AutoPostBack="true">
<asp:ListItem Text="Select..." Value=""></asp:ListItem>
<asp:ListItem Text="Option 1" Value="1"></asp:ListItem>
<asp:ListItem Text="Option 2" Value="2"></asp:ListItem>
</asp:DropDownList>
<br />
<br />
<asp:PlaceHolder ID="divTaglieRighe" runat="server"></asp:PlaceHolder>
<br />
<br />
<asp:Button ID="Button1" runat="server" Text="Save" OnClick="Button1_Click" />
<br />
<br />
<asp:Label ID="Label1" runat="server" Text=""></asp:Label>
Storing an instance of Table control in Session is a terrible idea and is just plain wrong. Controls belong to their parent page - they should be added to it as soon as they are created and they should die with their parent page.
The thing with dynamically created controls in ASP.NET is that you have to store an indicator of the fact that they were created (together with relevant information needed to create them again) and recreate them on all subsequent postbacks - no later than in Page_Load. Only then these controls will have the chance of getting their values from Request and you will have the chance of obtaining these values from controls.
In your code, the instance of Table stored in Session (together with all its rows, cells and textboxes) will never reflect any client-side data changes, nor will it belong to Pages that will be created to process subsequent postback requests.
Update
The important fact to understand here is that, after processing the postback caused by ddlScalaTaglie change and returning hew html to the client, the page instance is gone. On next request a new instance of your page is created - and it does not know anything about the fact that the previous instance had table added to its control tree. It is your code in Page_Load that must discover that the form must have this table, and create the table in exactly the same way it was created the first time - with rows, cells and textboxes with exactly the same IDs.
Then, after you add this newly created table to divTaglieRighe.Controls, the textboxes will be able to extract their client-side values from Request.Form collection.
Make sure the textbox has a "name" for the post back to the controller.
If it does not it grabs a null for the parameter.
Check Request.Form for data returned to controller.
You've created a Table called tbl and added two TextBox, then you store Table control in session Session["tbl"] and added to divTaglieRighe control. Finally you are trying to get TextBox data from your session Session["tbl"] table not the ViewState control that added in divTaglieRighe control. You have to get data from you Table tbl control that added in divTaglieRighe control.
For the life of me I cannot seem to figure this out. I have a long DataGridView (that does not allow MultiSelect) and when a user commits a change to the data, the data from the grid is purged and redrawn (because changes can affect multiple rows, this was the simpler approach). However, when I try to select the row programmatically, it does not also fire the DataGridView.SelectionChanged event, which I use to display data from an array which is correlated to the DataGridView current cell index. When doMagicStuff executes, the values for the wrong index (specifically, index 0) is show.
private void doMagicStuff()
{
int selRow = myDGV.CurrentCell.RowIndex;
myDGV.Rows.Clear();
/*Perform Task, Redraw data*/
myDGV.CurrentCell = myDGV[selRow, 0];
}
private void myDGV_SelectionChanged(object sender, EventArgs e)
{
Label1.Text = myDisplayValue1[myDGV.CurrentCell.RowIndex];
Label2.Text = myDisplayValue2[myDGV.CurrentCell.RowIndex];
TextBox1.Text = myEditValue1[myDGV.CurrentCell.RowIndex];
TextBox2.Text = myEditValue2[myDGV.CurrentCell.RowIndex];
}
Make sure that your client settings and OnSelectedIndexChanged is set like so: (ASP.NET AJAX)
.aspx page
<telerik:RadGrid ID="Grid1" runat="server" OnSelectedIndexChanged="Grid1_SelectedIndexChanged" OnItemDataBound="Grid1_ItemDataBound" OnPreRender="Grid1_PreRender">
<ClientSettings EnablePostBackOnRowClick="true">
<Selecting AllowRowSelect="true"></Selecting>
</ClientSettings>
</telerik:RadGrid>
aspx.cs page
protected void Grid1_SelectedIndexChanged(object sender, EventArgs e)
{
string value = null;
foreach(GridDataItem item in Grid1.SelectedItems)
{
//column name is in doub quotes
value = item["Name"].Text;
}
}
Add a button click to the form to test the selected values in the DataGridView.. double click that button then paste this code in there
foreach (DataGridViewRow row in myDGV.SelectedRows)
{
Label1.Text = //This should be hard coded the only thing that should change dynamically is the TextBox Values
Label2.Text = //This should be hard coded the only thing that should change dynamically is the TextBox Values
TextBox1.Text = row.Cells[0].Value.ToString();//change the 0 or 1 to fit your column Index position
TextBox2.Text = row.Cells[2].Value.ToString();
}
also if you have 4 columns and 4 text boxes then you will assign all of the textbox.Text values within the foreach loop just follow the pattern and increase the index by 1 so 2 textboxes means row.Cells[0] is the first column row.Cells[1] is the second column ...etc
I have an ASP table, with a dropdownlist and text box in its 1st row. I have a button on click of which a similar row should be added. I have the following function in its button click event.
public void addRow()
{
int flag = Table1.Rows.Count;
DropDownList ddl = new DropDownList();
ddl.ID = "ddl" + (flag + 1).ToString();
TextBox tBox = new TextBox();
tBox.ID = "txt" + (flag + 1).ToString();
TableCell tCell1 = new TableCell();
TableCell tCell2 = new TableCell();
tCell1.Controls.Add(ddl);
tCell2.Controls.Add(tBox);
TableRow tRow = new TableRow();
tRow.Cells.Add(tCell1);
tRow.Cells.Add(tCell2);
Table1.Rows.Add(tRow);
}
This code works fine when adding 2nd row. When I click the button again, the value of flag which should have the value of number of rows of the table is not getting incremented. So a new row doesn't get created. Can you please tell me why the value of flag doesn't get incremented ? Or is their an easier way to do this ? Direct answer or link would be helpful.
Mark up code for Table:
<asp:Table ID="Table1" runat="server" Width="410px">
<asp:TableRow>
<asp:TableCell>
<asp:DropDownList runat="server" ID="ddl1"></asp:DropDownList>
</asp:TableCell>
<asp:TableCell>
<asp:TextBox runat="server" ID="txt1"></asp:TextBox>
</asp:TableCell>
</asp:TableRow>
</asp:Table>
Warning - Big ugly block of text
I'm going to try and explain why what you're trying to do is not as simple as it sounds (or as simple as I'd like it to be) and hopfully give you an alterantive approach, which while it
may not be straight forward, it should be "better".
It looks a high level overview of what you are trying to do is the following:
Give the user the oppotunity to add one or more records to a collection (database) before finalising the save.
It is possible to do this using postback to generate the additional rows/items beofore clicking the final save button. Hover this may not be the easiest, or best, way to do this.
From a technical viewpoint adding dynamic controls has to be handeld in a very specific manner at a specific point in the ASP.net page lifecylce. Dynamic controls also have to be recreated on every post back, so every time a user clicks the "add" button, you will have to re-reate the rows you already have in addition to the new row. This is why you only get the one row in this example. Thats just the adding of the dynaimc controls. Wait till you get to getting data from your dynamic text boxes and drop downs!
From an end users perspective, this approach is also not ideal as every time the user clicks the "add" button the page cycles through the request/response process, which at the minumun cuases a bit of an ugly flash.
Update
But how? I hear you ask. Well here it is (I've simplified it to one column for this example):
ASPX
<asp:Table ID="Table1" runat="server">
<asp:TableRow>
<asp:TableCell><asp:TextBox ID="txt1" runat="server"> </asp:TextBox></asp:TableCell>
</asp:TableRow>
</asp:Table>
<asp:Button ID="btnAdd" runat="server" Text="Add" onclick="Button1_Click" />
<asp:HiddenField ID="hdnRowCount" runat="server" />
<asp:Button ID="btnSave" runat="server" onclick="btnSave_Click" Text="Save" />
<div>
<asp:Label ID="lblResult" runat="server" Visible="false"></asp:Label>
</div>
C#
private int rowCount = 1;
private bool rowNeeded = false;
protected override void OnPreInit(EventArgs e)
{
//Pre-emptively create additional row on post back.
//We'll remove it if we don't need it later
//Controls Have to be added at pre-init to maintain their view state
//Adding a row in the button click event will result in any data
//entered in that row dissapearing from viewstate on next post back
if (IsPostBack)
{
//Get number of additional rows from hidden field
//We're using a hidden form field and Request.Form
//instead of ViewsState and hdnRowCount.value
//because ViewState has not been loaded at this stage
//of the page life cycle
if (!string.IsNullOrEmpty(Request.Form["hdnRowCount"]))
{
rowCount = int.Parse(Request.Form["hdnRowCount"]);
}
for (int i = 0; i < rowCount; i++)
{
TableRow tr = new TableRow();
TableCell tc = new TableCell();
TextBox tb = new TextBox();
// +2 as there are existing controls with "1", eg txt1
tb.ID = string.Format("txt{0}",i+2);
tc.Controls.Add(tb);
tr.Cells.Add(tc);
Table1.Rows.Add(tr);
}
}
base.OnPreInit(e);
}
//Add Row Click
protected void Button1_Click(object sender, EventArgs e)
{
//Hold number of additional rows in hidne field
hdnRowCount.Value = (++rowCount).ToString();
//Let the page know we need to keep the row
rowNeeded = true;
}
//An Exmple of how to get your data out
protected void btnSave_Click(object sender, EventArgs e)
{
string s = "";
//Iterate through the table rows finding the controls
//Using count -1 as we still have the pre-emptive row
for(int i = 0; i < Table1.Rows.Count-1; i++)
{
TextBox tb = (TextBox)Table1.Rows[i].FindControl(string.Format("txt{0}",i+1));
s += "," + tb.Text;
}
lblResult.Text = s;
lblResult.Visible = true;
}
protected override void OnLoadComplete(EventArgs e)
{
//Get rid of pre-emptive row if we don't need it
if (IsPostBack)
{
if (!rowNeeded)
{
Table1.Rows.RemoveAt(Table1.Rows.Count - 1);
}
}
base.OnLoadComplete(e);
}
End Update
So, how would I do it? Client side, is the answer to that question. I would use jQuery to create a clone of the first row. You then would need to adjust the id and name attributes of your form fields before appending your new row to the table. You would also need a hidden form field to keep track of the number of hidden rows. When your user finaly hits the "save" button, on the C# side, get the number of additional rows from you hidden field the go old school and use Request.Form[] to get the value of each of your added fields.
This, in my opionion, will be a smoother experience for the end user.
Another, somewhat hacky option,if you have a defined maximum number of additional rows. For this have the mamimum rows, with form controls in the table. You can then either use visible="false" in the aspx and use post back to change that to visible="true" when the user clicks "add". The other option there is to give the row a CSS class that hides the row and then use javascript/jquery to remove the class to make the row visible.
The issue here is that you are not understanding the ASP.Net Page Lifecycle and more importantly how it interacts with dynamic controls (your added controls.)
Unfortunately, we can not answer this question without understanding whether the values in these "dynamically" created controls are used server-side or client-side.
I can guess that:
1) If they are for client side consumption/usage, you are doing wrong. If you need more info ask another (more specific) question.
2) If they are for server side consumption/usage, you must only allow one new record per postback, and should include a "Save" button that will save the current "new" row and cause a page refresh that the user can add a new entry to.
3) See rule #0 (Every rule has exceptions. {This rule applies in so far that you understand the problem at hand.})
Monk
I am a litle confused with ASP.NET lifecycle event. I have a checkbox when it is checked, it will dynamically create labels and text boxes. This is done in checkbox oncheckchanged event.I have Ajax enabled on the checkbox without full postback.
Now in the newly created text boxes I am entering the values and when I click on the save button, in the button click event it would not even find the controls created. So how does the page viewstate remember the dynamic controls created in the checkbox events and then access it's values in the button save event?
Mark up:
<tr> <td> <asp:CheckBox ID="chkType" runat="server" Text="Medical Procedure" OnCheckedChanged="ChkMedicalProc_Clicked"></td></tr>
<tr><td colspan="2">
<asp:PlaceHolder ID="dyna" EnableViewState="true" runat="server"></asp:PlaceHolder>
</td></tr>
Code behind in the checkedchanged event:
TableRow tr = new TableRow();
TableCell tc1 = new TableCell();
TableCell tc2 = new TableCell();
Label lbl = new Label();
lbl.Text = string.Empty;
lbl.Text = (_queryParam[i].Param_Name + " (" + _queryParam[i].Param_Type + ") (" + _queryParam[i].Param_Length + ")").ToString();
lbl.Style.Add("font-size", "11px");
lbl.Style.Add("font-family", "Arial");
_txtBox = new TextBox();
_txtBox.ID = ctrlId;
_txtBox.CssClass = "textEntry";
_txtBox.Text = string.Empty;
_txtBox.Text = _queryParam[i].Param_Value;
tc1.Style.Add("width", "21.8%");
tc1.Controls.Add(lbl);
tc2.Controls.Add(_txtBox);
tr.Cells.Add(tc1);
tr.Cells.Add(tc2);
_tbl.Rows.Add(tr);
this.Master.FindControl("pagecontent1").FindControl("dyna").Controls.Add(_tbl);
Save button click event:
for (int i = 0; i < box.Count; i++)
{
TextBox boxValue= this.Page.Master.FindControl("pagecontent1").FindControl("dyna").FindControl("txtBoxParams-" + i) as TextBox;
//I get object reference error on boxValue
}
Dynamic controls are lost on postback, so on every page request you must dynamically add them again.
Though for your example, it might be easier to always have the label / textbox on the page, but contain it in an asp:panel which you toggle the Visible property on it, or show/hide it via javascript.
As for the values of these dynamic controls during postback, if you re-create them using the same ID, asp.net will automatically re-initialize them to the proper inputted values using the viewstate information.
For more information about dealing with dynamic controls, this website seems to be fairly accurate: https://web.archive.org/web/20211020131055/https://www.4guysfromrolla.com/articles/081402-1.aspx
Using the following recursive function you will be able to retrieve any control inside the dynamic table
public static Control DeepFindControl(Control c, string id)
{
if (c.ID == id)
{
return c;
}
if (c.HasControls())
{
Control temp;
foreach (var subcontrol in c.Controls)
{
temp = DeepFindControl((Control)subcontrol, id);
if (temp != null)
{
return temp;
}
}
}
return null;
}
to receive the values of the control after finding it you should know the name of the control and then you will receive the values in another new created control with the same type... "cast the control returned from the DeepFindControl" e.g....
Control C1 = DeepFindControl(DynamicTableName, ControlNAme);
TextBox _txtBox = (TextBox)C1;
I am trying to get an application to allow a user to select something for a number rows and then do something on the server side once the user submits the form. The problem I'm having is that if I reload the table, I just get the default values back and if I don't, the table is empty. Here is the code:
<asp:Table ID="tbl" runat="server">
<asp:TableRow>
<asp:TableHeaderCell>Question</asp:TableHeaderCell>
<asp:TableHeaderCell>Answer</asp:TableHeaderCell>
</asp:TableRow>
</asp:Table>
c#:
protected System.Web.UI.WebControls.Table tbl1;
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
init_tbl();
}
}
protected void init_tbl()
{
tbl1.BorderWidth = 1;
TableRow tr = new TableRow();
tc = new TableCell();
tc.Text = "text";
tc.BorderWidth = 1;
tr.Cells.Add(tc);
ddl = new DropDownList();
ddl.ID = "r" + index;
ddl.Attributes["runat"] = "server";
ListItem item;
for (int i = 1; i <= 10; i++)
{
item = new ListItem();
item.Text = i.ToString();
item.Value = i.ToString();
if (i.ToString().Equals(r.Trim()))
{
item.Selected = true;
}
ddl.Items.Add(item);
}
list.Add(ddl);
tc.Controls.Add(ddl);
tc.ID = "tblr" + index;
tr.Cells.Add(tc);
tbl1.Rows.Add(tr);
}
your problem is with the convoluted way asp.net deals with dynamic controls, you need to create the dynamic control on page init, before the view state is set so state of the controls is maintained on the post, see this article http://geekswithblogs.net/shahed/archive/2008/06/26/123391.aspx.
You should definatley look at the gridview or at the very least one of the repeater controls.
I would just make a list of questions and DataBind them to a repeater/gridview/datagrid (as #Cybernate said), then add an Event method to the OnItemDataBound of the databinder.
In the ItemDataBound event I would get a list of answers for each question DataItem and add them to a DropDownList like you were doing above.
When the user fills out all of the answers you just need to go through the Request.Form array and find all the answers that will be passed back.
You stored the Table in the ViewState and Use it as you want.