Saturday, March 24, 2012

trouble with click events on dynamically created link buttons

I've read quite a few different message on various boards and for some
reason I'm still having trouble wrapping my head around this viewstate
maintenance and trying to get these dynamically created link buttons
to stay wired up to their click events.
I have what is basically a simply survey question generation page. The
page first displays a few static fields and a dropdownlist of various
options for the user to select. When the user selects an option from
the list the page will generate a new table with 5 rows of textboxes,
drop down lists, and link buttons (to delete a row if desired). There
is also a static insert button to allow users to add additional rows
if needed.
Saving the data in the fields during postback isn't an issue, but I'm
stuck in two situations depending on how I adjust the code. First is
that I put the rebuilding of the controls in the Page_load and users
are forced to click twice on the static Insert Row button to add a row
or they have to click twice on a dynamic Delete Row link button to
remove a row. If I take the rebuilding of the controls out of the
Page_Load then the Insert Row button works fine, but clicking on a
Delete Row link button causes the click event to not fire and all the
dynamic controls disappear from the page.
Does anyone have any suggestions on what I need to do to fix this so
it's written correctly and will operate as intended? (if you need more
detail or code please ask)
Thank you for your help.
--Code Snippets (this setup requires 2 clicks on a button before the
click event appears to do anything--
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
If Not IsPostBack Then
LoadQuestionTypes()
End If
RebuildControls()
End Sub
--
Private Sub ddlQuestionType_SelectedIndexChanged(ByV
al sender As
System.Object, ByVal e As System.EventArgs) Handles
ddlQuestionType.SelectedIndexChanged
...
BuildEmptyFive()
...
End Sub
--
Private Sub BuildEmptyFive()
Dim IDArray As New ArrayList
Dim tblAnswers As New Table
Dim x As Integer
For x = 1 To 5
Dim row As New TableRow
Dim ID As String
ID = Left(System.Guid.NewGuid.ToString, 8)
Dim cell1 As New TableCell
cell1.Controls.Add(BuildTextBox("txtChoice-" & ID, 140))
Dim cell2 As New TableCell
cell2.Controls.Add(BuildDropDownList("ddlFamily-" & ID, 150,
"Family"))
Dim cell3 As New TableCell
cell3.Controls.Add(BuildDropDownList("ddlAttribute-" & ID, 150,
"Attributes"))
Dim cell4 As New TableCell
cell4.Controls.Add(BuildTextBox("txtScore-" & ID, 40))
Dim cell5 As New TableCell
cell5.Controls.Add(BuildLinkButton("lnkDelete-" & ID))
row.Cells.Add(cell1)
row.Cells.Add(cell2)
row.Cells.Add(cell3)
row.Cells.Add(cell4)
row.Cells.Add(cell5)
tblAnswers.Rows.Add(row)
IDArray.Add(ID)
Next
plhDynControls.Controls.Add(tblAnswers)
'Insert Array containing ID of each row
If IsNothing(ViewState.Item("IDArray")) Then
ViewState.Add("IDArray", IDArray)
Else
ViewState.Item("IDArray") = IDArray
End If
End Sub
--
Private Sub btnInsert_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnInsert.Click
'Add a new ID to the viewstate which will cause a new row to be
inserted when the viewstate is rebuilt
Dim IDArray As ArrayList
IDArray = CType(ViewState.Item("IDArray"), ArrayList)
IDArray.Add(Left(Guid.NewGuid.ToString, 8))
ViewState.Item("IDArray") = IDArray
'RebuildControls() 'unremark this and remove from page_load to get
insert button to work perfectly (delete no workie though)
End If
End Sub
--
Private Sub lnkDelete_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs)
Dim IDArray As ArrayList
IDArray = CType(ViewState.Item("IDArray"), ArrayList)
Dim ID As String = Right(CType(sender, LinkButton).ID.ToString, 8)
IDArray.RemoveAt(IDArray.IndexOf(ID))
ViewState.Item("IDArray") = IDArray
End Sub
--
this is how I generate the link button dynamically
Private Function BuildLinkButton(ByVal name As String) As LinkButton
Dim lnkLink As New LinkButton
lnkLink.ID = name
lnkLink.Text = "Delete"
AddHandler lnkLink.Click, AddressOf lnkDelete_Click
Return lnkLink
End Function
--
Private Sub RebuildControls()
If IsNothing(ViewState.Item("IDArray")) Then
Exit Sub
End If
Dim IDArray As ArrayList
IDArray = CType(ViewState.Item("IDArray"), ArrayList)
Dim tblAnswers As New Table
Dim x As Integer
For x = 0 To IDArray.Count - 1
Dim row As New TableRow
Dim cell1 As New TableCell
cell1.Controls.Add(BuildTextBox("txtChoice-" &
Convert.ToString(IDArray.Item(x)), 140, Request.Form.Item("txtChoice-"
& Convert.ToString(IDArray.Item(x)))))
Dim cell2 As New TableCell
cell2.Controls.Add(BuildDropDownList("ddlFamily-" &
Convert.ToString(IDArray.Item(x)), 150, "Family",
Request.Form.Item("ddlFamily-" & Convert.ToString(IDArray.Item(x)))))
Dim cell3 As New TableCell
cell3.Controls.Add(BuildDropDownList("ddlAttribute-" &
Convert.ToString(IDArray.Item(x)), 150, "Attributes",
Request.Form.Item("ddlAttribute-" &
Convert.ToString(IDArray.Item(x)))))
Dim cell4 As New TableCell
cell4.Controls.Add(BuildTextBox("txtScore-" &
Convert.ToString(IDArray.Item(x)), 40, Request.Form.Item("txtScore-" &
Convert.ToString(IDArray.Item(x)))))
Dim cell5 As New TableCell
cell5.Controls.Add(BuildLinkButton("lnkDelete-" &
Convert.ToString(IDArray.Item(x))))
row.Cells.Add(cell1)
row.Cells.Add(cell2)
row.Cells.Add(cell3)
row.Cells.Add(cell4)
row.Cells.Add(cell5)
tblAnswers.Rows.Add(row)
Next
plhDynControls.Controls.Add(tblAnswers)
End Sub
--
Let me know if seeing anything else might help. Thanks again.Amoril
You cannot use NewGuid function for ids because it'll generate different id
on every call (it means also on every postback) so events for all dynamicall
y
created controls will not be fired. And you want be able to find a value
entered by the user. Use x (loop counter) with contact prefix instead. Have
also in mind you should recreate controls in page_init (but do not access
viewstate at this stage because it’s simply not collected yet) as they wil
l
automatically recreate their state.
Hope it helps
"Amoril" wrote:

> I've read quite a few different message on various boards and for some
> reason I'm still having trouble wrapping my head around this viewstate
> maintenance and trying to get these dynamically created link buttons
> to stay wired up to their click events.
> I have what is basically a simply survey question generation page. The
> page first displays a few static fields and a dropdownlist of various
> options for the user to select. When the user selects an option from
> the list the page will generate a new table with 5 rows of textboxes,
> drop down lists, and link buttons (to delete a row if desired). There
> is also a static insert button to allow users to add additional rows
> if needed.
> Saving the data in the fields during postback isn't an issue, but I'm
> stuck in two situations depending on how I adjust the code. First is
> that I put the rebuilding of the controls in the Page_load and users
> are forced to click twice on the static Insert Row button to add a row
> or they have to click twice on a dynamic Delete Row link button to
> remove a row. If I take the rebuilding of the controls out of the
> Page_Load then the Insert Row button works fine, but clicking on a
> Delete Row link button causes the click event to not fire and all the
> dynamic controls disappear from the page.
> Does anyone have any suggestions on what I need to do to fix this so
> it's written correctly and will operate as intended? (if you need more
> detail or code please ask)
> Thank you for your help.
> --Code Snippets (this setup requires 2 clicks on a button before the
> click event appears to do anything--
> Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
> System.EventArgs) Handles MyBase.Load
> If Not IsPostBack Then
> LoadQuestionTypes()
> End If
> RebuildControls()
> End Sub
> --
> Private Sub ddlQuestionType_SelectedIndexChanged(ByV
al sender As
> System.Object, ByVal e As System.EventArgs) Handles
> ddlQuestionType.SelectedIndexChanged
> ...
> BuildEmptyFive()
> ...
> End Sub
> --
> Private Sub BuildEmptyFive()
> Dim IDArray As New ArrayList
> Dim tblAnswers As New Table
> Dim x As Integer
> For x = 1 To 5
> Dim row As New TableRow
> Dim ID As String
> ID = Left(System.Guid.NewGuid.ToString, 8)
> Dim cell1 As New TableCell
> cell1.Controls.Add(BuildTextBox("txtChoice-" & ID, 140))
> Dim cell2 As New TableCell
> cell2.Controls.Add(BuildDropDownList("ddlFamily-" & ID, 150,
> "Family"))
> Dim cell3 As New TableCell
> cell3.Controls.Add(BuildDropDownList("ddlAttribute-" & ID, 150,
> "Attributes"))
> Dim cell4 As New TableCell
> cell4.Controls.Add(BuildTextBox("txtScore-" & ID, 40))
> Dim cell5 As New TableCell
> cell5.Controls.Add(BuildLinkButton("lnkDelete-" & ID))
> row.Cells.Add(cell1)
> row.Cells.Add(cell2)
> row.Cells.Add(cell3)
> row.Cells.Add(cell4)
> row.Cells.Add(cell5)
> tblAnswers.Rows.Add(row)
> IDArray.Add(ID)
> Next
> plhDynControls.Controls.Add(tblAnswers)
> 'Insert Array containing ID of each row
> If IsNothing(ViewState.Item("IDArray")) Then
> ViewState.Add("IDArray", IDArray)
> Else
> ViewState.Item("IDArray") = IDArray
> End If
> End Sub
> --
> Private Sub btnInsert_Click(ByVal sender As System.Object, ByVal e As
> System.EventArgs) Handles btnInsert.Click
> 'Add a new ID to the viewstate which will cause a new row to be
> inserted when the viewstate is rebuilt
> Dim IDArray As ArrayList
> IDArray = CType(ViewState.Item("IDArray"), ArrayList)
> IDArray.Add(Left(Guid.NewGuid.ToString, 8))
> ViewState.Item("IDArray") = IDArray
> 'RebuildControls() 'unremark this and remove from page_load to get
> insert button to work perfectly (delete no workie though)
> End If
> End Sub
> --
> Private Sub lnkDelete_Click(ByVal sender As System.Object, ByVal e As
> System.EventArgs)
> Dim IDArray As ArrayList
> IDArray = CType(ViewState.Item("IDArray"), ArrayList)
> Dim ID As String = Right(CType(sender, LinkButton).ID.ToString, 8)
> IDArray.RemoveAt(IDArray.IndexOf(ID))
> ViewState.Item("IDArray") = IDArray
> End Sub
> --
> this is how I generate the link button dynamically
> Private Function BuildLinkButton(ByVal name As String) As LinkButton
> Dim lnkLink As New LinkButton
> lnkLink.ID = name
> lnkLink.Text = "Delete"
> AddHandler lnkLink.Click, AddressOf lnkDelete_Click
> Return lnkLink
> End Function
> --
> Private Sub RebuildControls()
> If IsNothing(ViewState.Item("IDArray")) Then
> Exit Sub
> End If
> Dim IDArray As ArrayList
> IDArray = CType(ViewState.Item("IDArray"), ArrayList)
> Dim tblAnswers As New Table
> Dim x As Integer
> For x = 0 To IDArray.Count - 1
> Dim row As New TableRow
> Dim cell1 As New TableCell
> cell1.Controls.Add(BuildTextBox("txtChoice-" &
> Convert.ToString(IDArray.Item(x)), 140, Request.Form.Item("txtChoice-"
> & Convert.ToString(IDArray.Item(x)))))
> Dim cell2 As New TableCell
> cell2.Controls.Add(BuildDropDownList("ddlFamily-" &
> Convert.ToString(IDArray.Item(x)), 150, "Family",
> Request.Form.Item("ddlFamily-" & Convert.ToString(IDArray.Item(x)))))
> Dim cell3 As New TableCell
> cell3.Controls.Add(BuildDropDownList("ddlAttribute-" &
> Convert.ToString(IDArray.Item(x)), 150, "Attributes",
> Request.Form.Item("ddlAttribute-" &
> Convert.ToString(IDArray.Item(x)))))
> Dim cell4 As New TableCell
> cell4.Controls.Add(BuildTextBox("txtScore-" &
> Convert.ToString(IDArray.Item(x)), 40, Request.Form.Item("txtScore-" &
> Convert.ToString(IDArray.Item(x)))))
> Dim cell5 As New TableCell
> cell5.Controls.Add(BuildLinkButton("lnkDelete-" &
> Convert.ToString(IDArray.Item(x))))
> row.Cells.Add(cell1)
> row.Cells.Add(cell2)
> row.Cells.Add(cell3)
> row.Cells.Add(cell4)
> row.Cells.Add(cell5)
> tblAnswers.Rows.Add(row)
> Next
> plhDynControls.Controls.Add(tblAnswers)
> End Sub
> --
> Let me know if seeing anything else might help. Thanks again.
>
The only place that I use NewGuid to assign the ID's is in the
BuildEmptyFive sub (only fired after the user selects an item from the
drop down), for RebuildingControls sub I pull the ID's out of the
IDArray in the ViewState, so that shouldn't be an issue.
Moving the RebuildControls() sub from Page_Load to Page_Init actually
made the issue worse, now when I click on the static Insert Row button
or the dynamics link buttons to delete a row, all the dynamic controls
disappear. The static button fires it's event, but the link buttons
don't. Perhaps I'm not understanding what you mean by that since
without accessing the IDArray in the viewstate I won't know how many
controls need to be recreated.
Any more detail you could provide would be appreciated.
Hi again,
Oh yes, you're right but no need for that. it's easier to use row index and
a constant prefix for a particular control type (attribute, score,etc). I'll
try to provide a fully working example later on today.
take care
--
Milosz
"Amoril" wrote:

> The only place that I use NewGuid to assign the ID's is in the
> BuildEmptyFive sub (only fired after the user selects an item from the
> drop down), for RebuildingControls sub I pull the ID's out of the
> IDArray in the ViewState, so that shouldn't be an issue.
> Moving the RebuildControls() sub from Page_Load to Page_Init actually
> made the issue worse, now when I click on the static Insert Row button
> or the dynamics link buttons to delete a row, all the dynamic controls
> disappear. The static button fires it's event, but the link buttons
> don't. Perhaps I'm not understanding what you mean by that since
> without accessing the IDArray in the viewstate I won't know how many
> controls need to be recreated.
> Any more detail you could provide would be appreciated.
>
Hi again,
Actually we have to use guid because you can delete row, which i didn't pick
up before. Anyway, i created fully working example for you. You should be
fine from this point
-- begin aspx code --
<%@. Page Language="VB" AutoEventWireup="false" CodeFile="Survey.aspx.vb"
Inherits="Survey" %>
<!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:DropDownList runat="server" ID="questions" AutoPostBack="true">
<asp:ListItem Text="Please Select a Question..." />
<asp:ListItem Text="What are your names?" />
<asp:ListItem Text="Name all girlfriends you have had in your life" />
</asp:DropDownList>
<asp:Panel runat="server" ID="container" />
<asp:Panel runat="server" ID="surveyOptions">
<asp:Button ID="btnAddRow" runat="server" Text="Add row" />
<asp:Button ID="btnSubmit" runat="server" Text="Submit Survey"/>
</asp:Panel>
</div>
</form>
</body>
</html>
-- end aspx code --
-- begin vb.net code --
Partial Class Survey
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Me.Load
RecreateRows()
End Sub
Protected Sub questions_SelectedIndexChanged(ByVal sender As Object, ByVal
e As System.EventArgs) Handles questions.SelectedIndexChanged
Const DefaultRowCount As Integer = 5
' clear everything
IDs.Clear()
If CType(sender, DropDownList).SelectedIndex >= 0 Then
' create x default empty rows
For i As Integer = 1 To DefaultRowCount
IDs.Add(GenerateId())
Next
End If
RecreateRows()
End Sub
Private Sub RecreateRows()
container.Controls.Clear()
For Each id As String In IDs
AddAnswerRow(id)
Next
surveyOptions.Visible = IDs.Count > 0
End Sub
Private Const RowIdPrefix As String = "row"
Private Const TextBoxIdPrefix As String = "txt"
Private Const DropDownListIdPrefix As String = "ddl"
Private Sub AddAnswerRow(ByVal id As String)
Dim panel As Panel
Dim textBox As TextBox
Dim linkButton As LinkButton
Dim dropDownList As DropDownList
' row panel
panel = New Panel()
panel.ID = RowIdPrefix & id
' answer text box
textBox = New TextBox()
textBox.ID = TextBoxIdPrefix & id
' delete button
linkButton = New LinkButton()
linkButton.ID = "btn" & id
linkButton.Text = "delete"
linkButton.CommandArgument = id
AddHandler linkButton.Command, New CommandEventHandler(AddressOf
DeleteAnswerRow)
dropDownList = New DropDownList()
dropDownList.ID = DropDownListIdPrefix & id
dropDownList.Items.Add(New ListItem("Value0", "0"))
dropDownList.Items.Add(New ListItem("Value1", "1"))
dropDownList.Items.Add(New ListItem("Value2", "2"))
panel.Controls.Add(textBox)
panel.Controls.Add(dropDownList)
panel.Controls.Add(linkButton)
container.Controls.Add(panel)
End Sub
Private Sub DeleteAnswerRow(ByVal source As Object, ByVal e As
CommandEventArgs)
Dim id As String = CType(e.CommandArgument, String)
Dim control As Control = container.FindControl(RowIdPrefix & id)
If (Not control Is Nothing) Then
container.Controls.Remove(control)
Dim index As Integer = IDs.IndexOf(id)
If index <> -1 Then
IDs.RemoveAt(index)
End If
End If
End Sub
Private ReadOnly Property IDs() As ArrayList
Get
Dim value As Object = ViewState("IDs")
If value Is Nothing Then
value = New ArrayList()
ViewState("IDs") = value
End If
Return value
End Get
End Property
Private Function GenerateId() As String
Return Guid.NewGuid().ToString("N")
End Function
Protected Sub btnAddRow_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles btnAddRow.Click
Dim id As String = GenerateId()
IDs.Add(id)
AddAnswerRow(id)
End Sub
Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles btnSubmit.Click
' obtain results
Dim textBox As TextBox
Dim dropDownList As DropDownList
For Each id As String In IDs
'
' text box value
'
textBox = CType(container.FindControl(TextBoxIdPrefix & id), TextBox)
If (Not textBox Is Nothing) Then
Dim textBoxValue As String = textBox.Text
End If
'
' drop down list selected value
'
dropDownList = CType(container.FindControl(DropDownListIdPrefix & id),
DropDownList)
If (Not dropDownList Is Nothing) Then
Dim dropDownListValue As String = dropDownList.SelectedValue
End If
Next
End Sub
End Class
-- end vb.net code --
Milosz
"Milosz Skalecki [MCAD]" wrote:
> Hi again,
> Oh yes, you're right but no need for that. it's easier to use row index an
d
> a constant prefix for a particular control type (attribute, score,etc). I'
ll
> try to provide a fully working example later on today.
> take care
> --
> Milosz
>
> "Amoril" wrote:
>
Excellent, thank you very much for your help, it's working great.

0 comments:

Post a Comment