Get values of dynamically added controls in ListView - asp.net

I am having trouble getting the input values of dynamically created controls in a ListView.
Here is my ListView:
<asp:ListView ID="lvQuestions" runat="server" DataKeyNames="ProductQuestionId" onitemdatabound="lvQuestions_ItemDataBound">
<LayoutTemplate>
<table>
<tr runat="server" id="itemPlaceholder"></tr>
</table>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td><%# Eval("Question") %></td>
<td>
<asp:PlaceHolder ID="plControl" runat="server" />
<asp:HiddenField ID="hfQuestionId" runat="server" />
</td>
</tr>
</ItemTemplate>
</asp:ListView>
<asp:Button ID="btnSubmit" runat="server" Text="Submit" onclick="btnSubmit_Click" />
In my ItemDataBound Handler I am adding a TextBox or other control to the Placeholder. The control type is dependent on the item, but to keep it simple lets assume it is always a Textbox. The ID of the control is also dynamic.
// create a textbox control
TextBox txtbx = new TextBox();
txtbx.ID = "txtQuestion_" + productQuestionId.ToString(); //productQuestionId is the datakey value of this ListViewItem
placeholder.Controls.Add(txtbx);
When a user clicks on the button I need to be able to get the values they filled out.
In my research I found that I need to first recreate the dynamically added controls in order to get the values of them due to the Page Lifecycle.
Here is what I have in my button handler to recreate the controls:
foreach (ListViewDataItem item in lvQuestions.Items)
{
HiddenField hdField = (HiddenField)item.FindControl("hfQuestionId");
PlaceHolder plcHolder = (PlaceHolder)item.FindControl("plControl");
TextBox txtbx = new TextBox();
txtbx.ID = "txtQuestion_" + hdField.Value;
plcHolder.Controls.Add(txtbx);
}
then the next block of code in the same handler I re-iterate through the ListViewDataItems and find the control:
foreach (ListViewDataItem item in lvQuestions.Items)
{
HiddenField hdField = (HiddenField)item.FindControl("hfQuestionId");
PlaceHolder plcHolder = (PlaceHolder)item.FindControl("plControl");
TextBox txtbx = (TextBox)plcHolder.FindControl("txtQuestion_" + hdField.Value);
if (txtbx != null)
{
Response.Write("TextBox Found:" + txtbx.Text);
}
}
The textbox is found, but there is no value. It's like I just wrote over the textboxes with new ones in the previous block. If I remove the previous block of code no textboxes are ever found.
Can someone please help me out with what I am missing here?
Thank you.

As you've already discovered, this is a life cycle issue. Try creating your dynamic control in the ListView.ItemCreated event instead of the ListView.ItemDataBound event.

I think the issue here is that the lifecycle does not have a chance to populate the controls with their submitted values before you are trying to read those values.
Typically, if I was going to do something like this, I would be recreating the controls in the Page_Init event, which happens before the values are loaded into the controls. You could potentially do this in a particular control's Init event also, but that is where the additional controls need to be re-added to the page.

That didn't work for me, so I had to do this in the PreInit call
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
foreach (ListViewDataItem item in lvQuestions.Items)
{
HiddenField hdField = (HiddenField)item.FindControl("hfQuestionId");
PlaceHolder plcHolder = (PlaceHolder)item.FindControl("plControl");
if (hdField != null && plcHolder != null)
{
TextBox txtbx = new TextBox();
txtbx.ID = "txtQuestion_" + hdField.Value;
plcHolder.Controls.Add(txtbx);
}
}
}
And I moved this method back to the ItemDataBound event
protected void lvQuestions_ItemDataBound(object sender, ListViewItemEventArgs e)
{
object datakey = lvQuestions.DataKeys[e.Item.DataItemIndex].Value; //get datakey here
TextBox txtbx = new TextBox();
txtbx.EnableViewState = true;
txtbx.ID = "txtQuestion_" + datakey.ToString(); //productQuestionId is the datakey value of this ListViewItem
PlaceHolder pl = e.Item.FindControl("plControl") as PlaceHolder;
HiddenField hf = e.Item.FindControl("hfQuestionId") as HiddenField;
if (pl != null)
pl.Controls.Add(txtbx);
if (hf != null)
hf.Value = datakey.ToString();
}
Then it started working.

Related

ASP.NET - How to get selectedvalues from dynamicallyproduced dropdownlists inside Repeater Control

Hi have the following vb code in ASP.NET:
<asp:Repeater ID="Repeater1" runat="server" DataSourceID="SQL_SRC_DEV_HIERACHY">
<ItemTemplate>
<p class="input_title">Hierachy:
<asp:Literal ID="Literal1" runat="server" Text='<%# Eval("Hierarchy_SKey")%>'></asp:Literal><br />
</p>
<asp:DropDownList ID="DropDownList_H" runat="server" DataSourceID="SQL_SRC_DEV_GROUPINGDESCS" DataTextField="Description" DataValueField="FTE_PARENT_SKEY"></asp:DropDownList><br />
</ItemTemplate>
</asp:Repeater>
I now need to get the selectedvalues for each instance of dropdown created, and += to a variable so that I can built a SQL INSERT command based on the selected values.
Can anybody point me in the right direction to achieve this please? Thanks.
You can loop the items in the Repeater in code behind and use FindControl to get the SelectedValue of the DropDownList.
protected void Button1_Click(object sender, EventArgs e)
{
//loop all the items in the repeater
foreach (RepeaterItem item in Repeater1.Items)
{
//use findcontrol to locate the dropdownlist and cast it back to one
DropDownList drp = item.FindControl("DropDownList_H") as DropDownList;
//display the result
Label1.Text += drp.SelectedValue;
}
}
Thanks VDWWD - I have converted to VB and it work perfectly! Much appreciated.
VB version shown below:
Protected Sub GEN_CODE()
Dim txtField As DropDownList
Dim DDL_Values As String = ""
For Each item In Repeater1.Items
//use findcontrol to locate the dropdownlist and cast it back to one
txtField = item.FindControl("DropDownList_H")
//display the result
DDL_Values += " " + txtField.SelectedValue
Next
End Sub

Iterating through EditItemTemplate textboxes in asp:Gridview

I have a gridview displaying data within a template field which requires more information about the record to be displayed by clicking on a linkbutton. Right now, I have the "details" linkbutton display the information by calling the edit command on the gridview so that it switches to the EditItemTemplate. Within the EditItemTemplate I have a linkbutton for cancel and then an edit button that, when clicked, displays the update button with the update command, but I need it to iterate through that row and set all the textboxes within the EditItemTemplate to ReadOnly=false so as to allow them to be edited before the update command is selected. Here is a summary of the code:
<ItemTemplate>
*basic information displayed*
<asp:LinkButton runat="server" CommandName="Edit" Text="Details"></asp:LinkButton>
</ItemTemplate>
<EditItemTemplate>
*A bunch of readonly textboxes that display the extra information*
<asp:LinkButton runat="server" CommandName="Update" Text="Update" Visible="false"></asp:LinkButton>
<asp:LinkButton runat="server" Text="Edit" OnClick="editButton_Click"></asp:LinkButton>
</EditItemTemplate>
And the code for the event which makes the buttons appear the way I want, but I'm not sure how to iterate through the EditItemTemplate, or even if this is what I should do:
Protected Sub editButton_Click(sender As Object, e As EventArgs)
sender.FindControl("updateButton").Visible = True
sender.FindControl("editbutton").Visible = False
For Each t In ?EditItemTemplate?
Dim textbox = TryCast(t, System.Web.UI.WebControls.TextBox)
If textbox IsNot Nothing Then
textbox.ReadOnly = False
End If
Next
End Sub
So my question is either how to get this to work, or how I should set up the GridViewCommands otherwise
You should use a PlaceHolder in your EditItemTemplate. Place all of your Controls/LinkButtons inside this placeholder.
<EditItemTemplate>
<asp:PlaceHolder ID="TestPlaceHolder" runat="server">
// Sample Link Buttons
<asp:LinkButton runat="server" CommandName="Update" Text="Update"
Visible="false"></asp:LinkButton>
<asp:LinkButton runat="server" Text="Edit" OnClick="editButton_Click"></asp:LinkButton>
// Sample Text Box
<asp:TextBox runat="server" ID="FirstName" ...>...</TextBox>
</asp:PlaceHolder>
</EditItemTemplate>
Handle the RowEditing event of GridView. Inside the edit event handler, first find the Placeholder, then use the PlaceHolder's Controls property to iterate over the Controls...
protected void GridView1_RowEditing(object sender, GridViewEditEventArgs e)
{
// Get the Placeholder for the row being edited.
PlaceHolder testPlacehldr =
GridView.Rows[e.NewEditIndex].FindControl("TestPlaceholder") as PlaceHolder;
// Iterate over the controls
if(testPlacehldr.Controls.Count > 0)
{
foreach (Control ctrl in testPlacehldr.Controls)
{
if (ctrl is LinkButton)
{
LinkButton lnkBtn = ctrl as LinkButton
if(lnkBtn.Text== "Update")
{
lnkBtn.Visible = false;
}
// More IF conditions follow....
}
if (ctrl is TextBox)
{
TextBox txtBox = ctrl as TextBox;
if(txtBox.ID == "FirstName")// use any property of TexBox you prefer
{
txtBox.ReadOnly= true;
}
// More IF conditions follow....
}
}
}
//At the End, set the EditIndex and Bind the data
GridView1.EditIndex = e.NewEditIndex;
BindGridViewData();
}
I hope you can workout the logic yourself now for hiding/showing the controls.
So I figured out how to do it (needed it in vb) by using the placeholder within the EditItemTemplate, here's the code behind it:
Protected Sub editButton_Click(sender As Object, e As EventArgs)
sender.FindControl("editbutton").Visible = False
sender.FindControl("updateButton").Visible = True
Dim testPlacehldr As PlaceHolder = sender.FindControl("TestPlaceholder")
If testPlacehldr.Controls.Count > 0 Then
Dim btn As LinkButton = sender.FindControl("editButton")
If btn.Visible = False Then
For Each ctrl As Control In testPlacehldr.Controls
If ctrl.GetType Is GetType(TextBox) Then
Dim box As TextBox = TryCast(ctrl, TextBox)
box.ReadOnly = False
End If
Next
End If
End If
End Sub
This works fine for what I need it to do. Credit to the user R.C. for the idea about placeholders

DetailsView: How to set a HiddenField value using the CommandArgument on a New command?

I have inherited some code that has a GridView and a DetailsView in a webpart control.
The DetailsView is able to create two different kinds of an object e.g. TypeA and TypeB.
There's a dropdown list that filters the GridView by object type and the DetailsView has an automatically generated Insert button.
<asp:DetailsView ID="myDetailsView"
AutoGenerateInsertButton="True"
AutoGenerateEditButton="True"
AutoGenerateRows="false"
OnItemUpdating="OnItemUpdating"
DefaultMode="ReadOnly"
OnDataBound="OnDetailsViewBound"
OnItemInserting="OnItemInserting"
OnModeChanging="OnDetailsViewModeChanging"
runat="server">
I have been asked to:
remove the filter on the GridView; and
split the New buttons/links into two so there's a separate button for creating each type of object.
Removing the filter means that I need some other way to track what kind of object we're creating.
I have split the New links by changing the above to:
<asp:DetailsView ID="myDetailsView"
AutoGenerateInsertButton="False"
AutoGenerateEditButton="True"
AutoGenerateRows="false"
OnItemUpdating="OnItemUpdating"
DefaultMode="ReadOnly"
OnDataBound="OnDetailsViewBound"
OnItemInserting="OnItemInserting"
OnModeChanging="OnDetailsViewModeChanging"
runat="server">
and adding
<FooterTemplate>
<asp:TemplateField>
<ItemTemplate>
<asp:LinkButton runat="server" ID="lnkCreateNewTypeA" CommandName="New" CommandArgument="TypeA" CssClass="buttonlink">New Type A</asp:LinkButton>
<asp:LinkButton runat="server" ID="lnkCreateNewTypeB" CommandName="New" CommandArgument="TypeB" CssClass="buttonlink">New Type B</asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
</FooterTemplate>
I haven't yet removed the filter so the changes currently function the same as before, as I'm using the New command.
What I was hoping to be able to do is somehow capture the New event so I can put the CommandArgument value into a hidden field, that the DetailsView would then use to determine which type of object it's creating and also to show/hide fields.
When I put breakpoints in all of the event handlers in my code, the first one to break is OnDetailsViewModeChanging, which doesn't have access to the CommandArgument.
OnItemCommand (if it's hooked up) is triggered when any button within the DetailsView is pressed and does give me access to the CommandArgument but I'm not sure what exactly needs to be done within this method to mimic the chain of events that occurs when you use automatically generated buttons.
Is my only option for retrieving the CommandArgument to capture it in the OnItemCommand event handler or is there some other event that is triggered on the New command?
Can anyone explain to me the sequence of events that occurs when the New command is triggered?
I read somewhere that it changes the mode to Insert but I don't know what else it does. I believe the OnItemInserting method isn't called until the "Insert" link is clicked.
Any help would be gratefully received!!
Edit:
I have found this link on DetailsView events but it hasn't answered my question.
http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.detailsview_events.aspx
Edit:
I have tried adding the following:
in ascx:
<asp:DetailsView ID="myDetailsView"
...
OnItemCommand="OnItemCommand"
...
runat="server">
...
<asp:TemplateField HeaderText="Object Type" HeaderStyle-CssClass="columnHeader">
<ItemTemplate>
<asp:HiddenField runat="server" ID="hidObjectType" Value=""/>
<asp:Label runat="server" ID="lblObjectType"></asp:Label>
</ItemTemplate>
</asp:TemplateField>
in code behind:
protected void OnItemCommand(object sender, DetailsViewCommandEventArgs e)
{
if (e.CommandName.Equals("New"))
{
var objectType = e.CommandArgument.ToString();
HiddenField typeHidden = this.myDetailsView.FindControl("hidObjectType") as HiddenField;
if (typeHidden != null)
{
typeHidden.Value = objectType;
}
Label typeLabel = this.myDetailsView.FindControl("lblObjectType") as Label;
if (typeLabel != null)
{
typeLabel.Text = objectType;
}
}
}
I found that I didn't need to set the mode (this.myDetailsView.ChangeMode(DetailsViewMode.Insert);) in this method, as the OnDetailsViewModeChanging event handler still triggered.
This finds the controls and sets the values on them correctly. If I check the values again in OnDetailsViewModeChanging, their values are still set but as part of the logic in this method, there is a call to
this.myDetailsView.DataBind()
which causes a postback and at this point, the values are lost. I tried adding
EnableViewState="True"
but this made no difference. I've reviewed the page lifecycle (http://spazzarama.files.wordpress.com/2009/02/aspnet_page-control-life-cycle.pdf) and thought that maybe this.EnsureChildControls() would help but it's also made no difference.
An alternative would be to store the value in the session but I'd rather not.
As far as I can tell there is no event for capturing the "New" command aside from OnItemCommand, which captures all commands. (NOTE: You will need to make sure that CausesValidation="False" is set on your LinkButton or the code won't break into OnItemCommand).
When stepping through the code, the following occurred:
After the linkbutton was pressed, OnItemCommand is triggered. CommandName = "New" and here I could retrieve the CommandArgument
Next OnModeChanging is triggered. e.NewMode = "Insert". From all examples I've seen, here you call ChangeMode on the DetailsView and then call Databind() on it
Next OnDataBound is triggered as a result of calling Databind()
I didn't find a way to retain the value of the hidden variable between the various events so I ended up using a session variable. Code is below in case anyone wants it.
DetailsView declaration in the ASCX:
<asp:DetailsView ID="myDetailsView"
AutoGenerateInsertButton="False"
AutoGenerateEditButton="True"
AutoGenerateRows="false"
OnItemInserting="OnItemInserting"
OnItemUpdating="OnItemUpdating"
OnItemCommand="OnItemCommand"
DefaultMode="ReadOnly"
OnDataBound="OnDetailsViewBound"
OnModeChanging="OnDetailsViewModeChanging"
runat="server">
In the code-behind:
constant declarations...
private const string SESSIONKEY_MYVALUE = "MyValue";
private const string DEFAULT_OBJECTTYPE = "TypeA";
OnItemCommand event handler...
protected void OnItemCommand(object sender, DetailsViewCommandEventArgs e)
{
if (e.CommandName.Equals("New", StringComparison.InvariantCultureIgnoreCase))
{
var objectType = e.CommandArgument.ToString();
HiddenField typeHidden = this.myDetailsView.FindControl("hidObjectType") as HiddenField;
if (typeHidden != null)
{
typeHidden.Value = objectType;
}
HttpContext.Current.Session[SESSIONKEY_MYVALUE] = objectType;
}
}
OnModeChanging event handler....
protected void OnDetailsViewModeChanging(Object sender, DetailsViewModeEventArgs e)
{
if (e.NewMode == DetailsViewMode.Insert)
{
this.myDetailsView.ChangeMode(DetailsViewMode.Insert);
this.myDetailsView.DataBind();
}
}
OnDataBound event handler...
protected void OnDetailsViewBound(object sender, EventArgs e)
{
if (this.myDetailsView.CurrentMode == DetailsViewMode.Insert)
{
var sessionVar = HttpContext.Current.Session[SESSIONKEY_MYVALUE];
var objectType = sessionVar == null ?
DEFAULT_OBJECTTYPE :
sessionVar.ToString();
var typeHidden = this.myDetailsView.FindControl("hidObjectType") as HiddenField;
if (typeHidden != null)
{
typeHidden.Value = objectType;
}
}
}

FindControl() return null

I trying to create application whad add controlls dynamicaly. I have masterpage, my asp:Content is here:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<asp:ScriptManager ID="scriptManager1" runat="server">
</asp:ScriptManager>
<div style="margin: 10px">
<asp:UpdatePanel ID="updatePanel1" runat="server">
<ContentTemplate>
<asp:PlaceHolder runat="server" ID="myPlaceHolder" />
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btnAdd" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
</div>
<asp:Button ID="btnAdd" runat="server" Text="Add" />
After click in btnAdd I want to add two textboxes. I trying do it like in http://jagdeepmankotia.wordpress.com/2010/01/30/dynamically-add-controls-in-asp-net-c/
This is my code:
static int myCount = 1;
private TextBox[] color;
private TextBox[] text;
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
color = new TextBox[myCount];
text = new TextBox[myCount];
for (int i = 0; i < myCount; i++)
{
TextBox tbColor = new TextBox();
tbColor.ID = "colorTextBox" + i.ToString();
myPlaceHolder.Controls.Add(tbColor);
color[i] = tbColor;
TextBox tbText = new TextBox();
tbText.ID = "textTextBox" + i.ToString();
myPlaceHolder.Controls.Add(tbText);
text[i] = tbText;
LiteralControl literalBreak = new LiteralControl("<br />");
myPlaceHolder.Controls.Add(literalBreak);
}
}
public Control GetPostBackControl(Page page)
{
Control control = null;
string ctrlname = page.Request.Params.Get("__EVENTTARGET");
if (ctrlname != null && ctrlname != string.Empty)
{
control = page.FindControl(ctrlname);
}
else
{
foreach (string ctl in page.Request.Form)
{
Control mycontrol = page.FindControl(ctl);
if (mycontrol is System.Web.UI.WebControls.Button)
{
control = mycontrol;
// This gives you ID of which button caused postback
break;
}
}
}
return control;
}
protected void Page_PreInit(object sender, EventArgs e)
{
Control myControl = GetPostBackControl(this.Page);
if (myControl != null)
if (myControl.ClientID.ToString() == "btnAdd")
myCount = myCount + 1;
}
protected void btnAdd_Click(object sender, EventArgs e)
{
//handled in PreInit
}
When in function GetPostBackControl() in loap foreach looking for my btnAdd, for example in first iteration for ctr "ctl00$MainContent$scriptManager1", myControl is null... In next iterations also... So my function always return null. What can be reason?
FindControl only searches direct children of the container. Since you are starting off at the page level, you will need to recurse through the child UpdatePanel control to get to your btnAdd control.
Have a look here for an example how to do do this.
Edit:
I'm not sure I understand why you are 'looking' for your button in this manner, since there is only one static button on the screen - you wouldn't need to use FindControl in this case.
<asp:Button ID="btnAdd" runat="server" Text="Add" onclick="btnAdd_Click" />
(or in code, btnAdd.OnClick += new EventHandler(btnAdd_Click);)
Even if you had multiple Buttons in your form added dynamically, you could wire ALL of them up to the same Button Click handler, in which case sender would then contain the Button Control which was clicked. You would typically use FindControl to scrape the data out of the dynamically added Input controls (text box etc), rather than to see which control caused the Postback (as 'sender' in an appropriate event handler would be easier)
Edit 2:
You can add the buttons dynamically just like your other controls
Button myButton = new Button();
myButton.Text = "Click Me";
myButton.Click += new EventHandler(btnAdd_Click);
myPlaceHolder.Controls.Add(myButton);
If you want all the controls that you've added already to 'stay' in between postbacks then enable viewstate on the page and on the controls, and then make sure that you only add the controls once without postback, in OnInit:
base.OnInit(e);
if (!IsPostBack)
{ // ... Add controls here
You can keep the state of 'mycount' in a hidden field (in the same updatepanel, and with viewstate enabled) - you'll need to parse it to an int each time. Or you can use SessionState to track it.

Finding in which row a Button was clicked in a GridView/Repeater

I am binding a Repeater. Each row (is that the right word?) in the Repeater has a Button and a HiddenField. How do I determine the Value of the HiddenField based on which Button was clicked?
Code behind for Button's OnClick event:
protected void btnButton1_Click(object sender, EventArgs e)
{
Button btnButton1 = (Button)sender;
// how do i get this row's HiddenField Value?
}
edit: the CommandArgument suggestion from Pleun works but I am still having issues. I need to find the row(?) in the Repeater that the Button belongs to as there is also a TextBox in each row and I would need its value. So ideally I want to get that row and go FindControl("TextBox1") etc etc. Sorry, should have stated that in my initial question
What I like to do is add a CommandArgument to the button. In this code its an imagebutton but the idea is the same. So also no need for an extra hidden field.
<asp:ImageButton ID="btnMail" ImageUrl="~/imgnew/prof/sendlink.png"
CommandArgument='<%# Eval("id")%>'
And in the _Click event do
string id = ((ImageButton)sender).CommandArgument;
Update:
If you need all the data, you need a different event. The data in the repeater is available as Item in
RepeaterCommandEventArgs
in the Command event (RepeaterCommandEventArgs)
for handling the Command event see this example
http://www.asp.net/data-access/tutorials/custom-buttons-in-the-datalist-and-repeater-cs
or
http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.repeatercommandeventargs.aspx
If you using Repeater you can identify elements in each row in the ItemDataBound.
If you using gridview use RowDataBound
Hope this helps
You can traverse upwards by getting the button's Parent, then doing a FindControl() on that control.
Row parentRow = (Row)((Button)sender).Parent;
var tBox = (System.Web.UI.WebControls.TextBox)parentRow.FindControl("myTextBox")
You may have to play around to see how deep the button is nested and with what control types to get to the appropriate parent.
My markup code:
<asp:Repeater ID="RptFiles" runat="server">
<HeaderTemplate>
<table>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td>--</td>
<td><%#Eval("title")%></td>
<td>
<asp:FileUpload ID="fuVersion" runat="server" />
<asp:Button ID="btnUploadVersion" Text="Last opp" runat="server" OnClick="btnUploadVersion_Click" CommandArgument='<%#Eval("Id") %>' />
</td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
My code behind
protected void Upload_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
var documentId = btn.CommandArgument;
//Get the Repeater Item reference
RepeaterItem item = btn.NamingContainer as RepeaterItem;
var fuVersion = (FileUpload)item.FindControl("fuVersion");
var filename = fuVersion.PostedFile.FileName
}

Resources