how to reference dynamically created control in asp.net 3.5 with VB

Simon picture Simon · Mar 20, 2011 · Viewed 8.4k times · Source

I'm writing a program that inserts controls onto a webform dynamically. Depending on a variable, I add either a textbox, a set of radio buttons, or a set of checkboxes. Later, after a user clicks a submit button I need to use the controls to determine if the user is submitting the correct answer, but when I try to reference the id of a control, I get "txtAnser is not declared. It may be inaccessible due to it's protections level.

Here is the .aspx page (it's the standard content page of a master page):

<%@ Page Title="" Language="VB" MasterPageFile="~/top.master" AutoEventWireup="false" 
CodeFile="test_page.aspx.vb" Inherits="Default2" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="PageContent" Runat="Server">
    <asp:SqlDataSource ID="sdsQuestionPuller" runat="server" 
    ConnectionString="<%$ ConnectionStrings:SchoolhouseConnectionString6 %>" 
    SelectCommand="SELECT QuestionText, AnswerType, PossibleAnswers,    CorrectAnswers        
    FROM Questions WHERE (SubjectID = @SubjectID) AND (QuestionOrder = @QuestionOrder)">
    <SelectParameters>
        <asp:SessionParameter Name="SubjectID" SessionField="SubjectID" />
        <asp:SessionParameter Name="QuestionOrder" SessionField="PageNumber" />
    </SelectParameters>
</asp:SqlDataSource>
    <asp:SqlDataSource ID="sdsMaxQuestions" runat="server" 
        ConnectionString="<%$ ConnectionStrings:SchoolhouseConnectionString7 %>" 
        SelectCommand="SELECT MAX(QuestionOrder) AS LastQuestion FROM Questions GROUP     BY SubjectID HAVING (SubjectID = @SubjectID)">
        <SelectParameters>
            <asp:SessionParameter Name="SubjectID" SessionField="SubjectID" />
        </SelectParameters>
    </asp:SqlDataSource>
<div>
    <asp:Label ID="lblInstructionHolder" runat="server"></asp:Label>
</div>
<div>
    <asp:Label ID="lblQuestionHolder" runat="server"></asp:Label>
</div>
    <asp:PlaceHolder ID="plhQuestionHolder" runat="server"></asp:PlaceHolder>
<div>
    <asp:Button ID="btnSubmit" Text="Submit" runat="server" />
</div>
</asp:Content>

And here is the code behind, I'm running into problems in the submit button click event where I try to reference the txtAnswer control in the select case statement:

Imports System.Data

Partial Class Default2
    Inherits System.Web.UI.Page

    Dim intMaxPage As Integer
    'QuestionText, AnswerType, PossibleAnswers, CorrectAnswers
    Dim strQuestion As String
    Dim strQuestionType As String
    Dim astrPossibleAnswers() As String
    Dim intNumberOfPossAnswers As Integer
    Dim astrCorrectAnswers() As String
    Dim intNumberOfCorrectAnswers As Integer

    Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init

    End Sub

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        'Load up the variables(make sure data is retrieved, create a view from the source, retrieve the only row, assing the item(s) from the row to variables)
        sdsMaxQuestions.DataBind()
        Dim dtvLastPageView As DataView = CType(sdsMaxQuestions.Select(DataSourceSelectArguments.Empty), DataView)
        Dim dtrLastPageRow As DataRowView = dtvLastPageView.Item(0)
        intMaxPage = CInt(Trim(dtrLastPageRow.Item(0).ToString))

        Dim dtvQuestionsView As DataView = CType(sdsQuestionPuller.Select(DataSourceSelectArguments.Empty), DataView)
        Dim dtrQuestionsRow As DataRowView = dtvQuestionsView.Item(0)
        strQuestion = Trim(dtrQuestionsRow.Item(0).ToString)
        strQuestionType = Trim(dtrQuestionsRow.Item(1).ToString)
        astrPossibleAnswers = Split(Trim(dtrQuestionsRow.Item(2).ToString), ";")
        intNumberOfPossAnswers = astrPossibleAnswers.Count
        astrCorrectAnswers = Split(Trim(dtrQuestionsRow.Item(3).ToString), ";")
        intNumberOfCorrectAnswers = astrCorrectAnswers.Count

        'Finish loading controls
        lblQuestionHolder.Text = strQuestion
        Select Case strQuestionType
            Case "checkbox"
                lblInstructionHolder.Text = "Choose the correct answer(s)."
            Case "radio"
                lblInstructionHolder.Text = "Choose the best answer."
            Case "fillintheblank"
                lblInstructionHolder.Text = "Please fill in the blank, case doesn't count, spelling does."
            Case Else
                lblInstructionHolder.Text = "Somethings wrong, contact admin"
        End Select

        'Generate the controls for answers...
        GenerateControls()

    End Sub

    Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSubmit.Click
        'Declare the variables, get any existing values, then check to see if the answer is correct, display a message and go to the next page or store the score.
        Dim intNumberRight As Integer = 0
        Dim intNumberTotal As Integer = 0

        If Not (Session("NumberRight") = Nothing) Then
            intNumberRight = CInt(Session("NumberRight"))
        End If
        If Not (Session("NumberTotal") = Nothing) Then
            intNumberTotal = CInt(Session("NumberTotal"))
        End If

        Select Case strQuestionType
            Case "checkbox"

            Case "radio"

            Case "fillintheblank"
                'Here is where I am having issues
                If txtAnswer Is Not Nothing Then

                End If
                If txtAnswer.text.ToLower = astrCorrectAnswers(0).ToLower Then

                End If
            Case Else
                MsgBox("Somethings wrong, contact admin")
        End Select

        If intMaxPage = CType(Session("PageNumber"), Integer) Then
            Response.Redirect("user_landing.aspx")
        Else
            Session("PageNumber") = CType(Session("PageNumber"), Integer) + 1
            Response.Redirect("test_page.aspx")
        End If
    End Sub

    Private Sub GenerateControls()
        'Make the correct controls depending on the type
        Dim conDivHolder As HtmlGenericControl = New HtmlGenericControl("div")

        Select Case strQuestionType
            Case "checkbox"
                For i As Integer = 0 To intNumberOfPossAnswers - 1
                    Dim chkAnswer As CheckBox = New CheckBox
                    With chkAnswer
                        .ID = "chkAnswer" + i.ToString
                        .Text = astrPossibleAnswers(i)
                    End With
                    conDivHolder.Controls.Add(chkAnswer)
                Next

            Case "radio"
                For i As Integer = 0 To intNumberOfPossAnswers - 1
                    Dim rdoAnswer As RadioButton = New RadioButton
                    With rdoAnswer
                        .ID = "rdoAnswer" + i.ToString
                        .Text = astrPossibleAnswers(i)
                        .GroupName = "rdoGroup"
                    End With
                    conDivHolder.Controls.Add(rdoAnswer)
                Next

            Case "fillintheblank"
                Dim txtAnswer As TextBox = New TextBox
                txtAnswer.ID = "txtAnswer"
                conDivHolder.Controls.Add(txtAnswer)

            Case Else
                Dim lblOops As Label = New Label
                lblOops.Text = "Oops, contact admin"
                conDivHolder.Controls.Add(lblOops)
        End Select

        plhQuestionHolder.Controls.Add(conDivHolder)
    End Sub

End Class

If anyone could point out what I need to do, I would appreciate it.

Thanks, Simon

Answer

Kon picture Kon · Mar 20, 2011

First of all, you're generating your controls on Page_Load (PostBack or not), so whatever the state of txtAnswer was before the submit button was clicked, it's gone now - txtAnswer has been regenerated again with an initial state. You should dynamically render your controls only if IsPostBack is false.

Secondly, you should probably be accessing the txtAnswer's Text property within the Is Not Nothing if statement block. Otherwise you may get a null reference exception.

Thirdly, to get a reference to your TextBox control you can try to find the control by ID within its placeholder, with something like this (I'm doing this in C# - you can translate to VB on your own):

Define function to look for a control recursively:

protected Control FindControl(Control parent, string controlID)
{
  Control control = parent.FindControlByID("controlID");

  if (control != null) 
  {
    return control;
  }

  foreach(Control child in control.Controls)
  {
    Control childControl = FindControl(child , controlID);

    if (childControl != null) 
    {
      return childControl;
    }
  }

  return null;
}

Call your recursive find function:

Control ctrl = FindControl(plhQuestionHolder, "txtAnswer");

None of this code is actually tested, so don't yell at me if it doesn't work as is. This is merely a guide.