Repeater and Custom Control - Dynamically adding to the collection and retaining values - c#

It has been so long since I've used Web Forms I find myself not remembering most of the perks.
I have a user control that has a button, a repeater and the ItemTemplate property of the repeater is another user control.
<asp:Button runat="server" ID="btnAdd" CssClass="btn" Text="Add" OnClick="btnAdd_Click"/>
<br/>
<asp:Repeater runat="server" ID="rptrRequests">
<ItemTemplate>
<uc1:ucRequest ID="ucNewRequest" runat="server" />
</ItemTemplate>
</asp:Repeater>
The idea is that when the user clicks on the Add button a new instance of the ucRequest is added to the collection. The code behind is as follows:
public partial class ucRequests : UserControl
{
public List<ucRequest> requests
{
get
{
return (from RepeaterItem item in rptrRequests.Items
select (ucRequest) (item.Controls[1])
).ToList();
}
set
{
rptrRequests.DataSource = value;
rptrRequests.DataBind();
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack) return;
requests = new List<ucRequest>();
}
protected void btnAdd_Click(object sender, EventArgs e)
{
var reqs = requests;
reqs.Add(new ucRequest());
requests = reqs;
}
}
After much googling I now remember that I should be binding the Repeater in the OnInit method in order for the ViewState to put the captured data of the controls within the ucRequest control on them between post backs but when I try to do that I will always have a single instance of the control on the Repeater since its Items collection is always empty.
How could I manage to do this?
Thanks in advance.

You just need control ids in view state stead of entire control collection.
<%# Control Language="C#" AutoEventWireup="true"
CodeBehind="ucRequests.ascx.cs"
Inherits="RepeaterWebApplication.ucRequests" %>
<asp:Button runat="server" ID="btnAdd" CssClass="btn" Text="Add"
OnClick="btnAdd_Click" />
<br /><asp:PlaceHolder runat="server" ID="PlaceHolder1"></asp:PlaceHolder>
<%# Control Language="C#" AutoEventWireup="true"
CodeBehind="ucRequest.ascx.cs"
Inherits="RepeaterWebApplication.ucRequest" %>
<asp:TextBox runat="server" ID="TextBox1"></asp:TextBox>
private List<int> _controlIds;
private List<int> ControlIds
{
get
{
if (_controlIds == null)
{
if (ViewState["ControlIds"] != null)
_controlIds = (List<int>) ViewState["ControlIds"];
else
_controlIds = new List<int>();
}
return _controlIds;
}
set { ViewState["ControlIds"] = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
foreach (int id in ControlIds)
{
Control ctrl = Page.LoadControl("ucRequest.ascx");
ctrl.ID = id.ToString();
PlaceHolder1.Controls.Add(ctrl);
}
}
}
protected void btnAdd_Click(object sender, EventArgs e)
{
var reqs = ControlIds;
int id = ControlIds.Count + 1;
reqs.Add(id);
ControlIds = reqs;
Control ctrl = Page.LoadControl("ucRequest.ascx");
ctrl.ID = id.ToString();
PlaceHolder1.Controls.Add(ctrl);
}

Try to get the ucRequests during the OnItemDatabound event, at that point you can edit the content of itemtemplate of the repeater. You can get there after the postback caused by the click on the add button. Here's a sample with a similar scenario

Related

ASP.NET - SelectedValue/Index in Listbox always changing after a postback

I've been wrestling with this problem for days and so far I haven't been able to find an answer that fits this specific issue. Here's the code to load the list in the PageLoad():
protected void Page_Load(object sender, EventArgs e)
{
if(!IsPostBack)
{
lstEmprendimientos.DataSource = Emprendimiento.DevolverEmprendimientosConEvaluacionesIncompletas();
lstEmprendimientos.DataValueField = "id";
lstEmprendimientos.DataTextField = "titulo";
lstEmprendimientos.DataBind();
pnlEvaluador.Visible = false;
}
}
The first method loads a list made up of 'Emprendimiento' objects, and on that list's SelectedIndexChanged I call another method to load a list through a method that uses the SelectedValue of the item selected.
My problem is that, no matter what I do, the SelectedIndex is always reset to 0 after a postback, so I can't load the second list using the SelectedValue properly. I've worked with lists for a long while now and I've never had this problem, so it's really baffling. I'd appreciate some help with this.
Here's the code for the whole page:
protected void Page_Load(object sender, EventArgs e)
{
if(!IsPostBack)
{
ddlEmprendimientos.DataSource = Emprendimiento.DevolverEmprendimientosConEvaluacionesIncompletas();
ddlEmprendimientos.DataValueField = "id";
ddlEmprendimientos.DataTextField = "titulo";
ddlEmprendimientos.DataBind();
pnlEvaluador.Visible = false;
}
}
protected void lstEmprendimientos_SelectedIndexChanged(object sender, EventArgs e)
{
}
protected void lstEvaluadores_SelectedIndexChanged(object sender, EventArgs e)
{
Evaluador ev = Evaluador.FindByID(lstEvaluadores.SelectedValue);
}
protected void btnAsignarEvaluador_Click(object sender, EventArgs e)
{
Emprendimiento emp = Emprendimiento.FindByID(Convert.ToInt32(ddlEmprendimientos.SelectedValue));
Evaluador ev = Evaluador.FindByID(lstEvaluadores.SelectedValue);
Evaluacion eva = new Evaluacion(emp, ev, 0, "justificacion", DateTime.Now, false);
if (eva != null)
{
if (eva.Insertar())
{
lblFeedback.Text = "Alta exitosa.";
emp.listaEvaluaciones.Add(eva);
lstEvaluadores.DataSource = emp.DevolverListaEvaluadoresQueNoEvaluanEmprendimiento();
lstEvaluadores.DataTextField = "Nombre";
lstEvaluadores.DataValueField = "Email";
lstEvaluadores.DataBind();
pnlEvaluador.Visible = true;
CargarEvaluadores();
}
else
{
lblFeedback.Text = "Error en el ingreso de datos.";
}
}
else
{
lblFeedback.Text = "Error en el ingreso de datos.";
}
}
protected void btnSeleccionarEmp_Click(object sender, EventArgs e)
{
CargarEvaluadores();
}
private void CargarEvaluadores()
{
Emprendimiento emp = Emprendimiento.FindByID(Convert.ToInt32(ddlEmprendimientos.SelectedIndex));
lstEvaluadores.DataSource = emp.DevolverListaEvaluadoresQueNoEvaluanEmprendimiento();
lstEvaluadores.DataTextField = "Nombre";
lstEvaluadores.DataValueField = "Email";
lstEvaluadores.DataBind();
pnlEvaluador.Visible = true;
}
protected void ddlEmprendimientos_SelectedIndexChanged(object sender, EventArgs e)
{
CargarEvaluadores();
}
Page markup:
<%Page Title="" Language="C#" MasterPageFile="~/masterPage.Master" AutoEventWireup="true" CodeBehind="asignarEvaluador.aspx.cs" Inherits="InterfazUsuario.asignarEvaluador">
<asp:DropDownList ID="ddlEmprendimientos" runat="server" OnSelectedIndexChanged="ddlEmprendimientos_SelectedIndexChanged">
</asp:DropDownList>
<br />
<br />
<asp:Button ID="btnSeleccionarEmp" runat="server" OnClick="btnSeleccionarEmp_Click" Text="Seleccionar emprendimiento" Width="195px" />
<br />
<br />
<asp:Panel ID="pnlEvaluador" runat="server">
<asp:ListBox ID="lstEvaluadores" runat="server" OnSelectedIndexChanged="lstEvaluadores_SelectedIndexChanged"></asp:ListBox>
<br />
<br />
<asp:Button ID="btnAsignarEvaluador" runat="server" OnClick="btnAsignarEvaluador_Click" Text="Asignar evaluador" Width="135px" />
<br />
<br />
<asp:Label ID="lblFeedback" runat="server"></asp:Label>
<br />
</asp:Panel>
You need to change your DropDownList and ListBox controls to AutoPostback
DropDownList
<asp:DropDownList ID="ddlEmprendimientos" runat="server"
OnSelectedIndexChanged="ddlEmprendimientos_SelectedIndexChanged"
AutoPostBack="True">
</asp:DropDownList>
ListBox
<asp:ListBox ID="lstEvaluadores" runat="server"
OnSelectedIndexChanged="lstEvaluadores_SelectedIndexChanged"
AutoPostBack="True">
</asp:ListBox>
AutoPostBack:
Set this property to true if the server needs to capture the selection
as soon as it is made. For example, other controls on the Web page can
be automatically filled depending on the user's selection from a list
control.
This property can be used to allow automatic population of other controls on > the Web page based on a user's selection from a list.
The value of this property is stored in view state.
https://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.listcontrol.autopostback(v=vs.110).aspx
I found the problem after checking everything out again.
The DataValueField in the list was 'id', and the id field in the 'Emprendimiento' class wasn't defined, so it was always returning a null int (0). Thank you kindly for the help, it was just a dumb mistake in the end.

persist ddl values and selected value in between postbacks

I have a save button and a drop down list on a page. Inside the page Load, the drop down list is populated if !Page.PostBack (AutoPostBack=false). So, the first time I load the page, the drop down list is populated. I also have a save method to go with the save button. When this button is clicked, it should do something with the selected value of the drop down list. My problem is that the drop down list has no value (is null) inside the button save method. How would you fix this?
Markup:
MyClass.aspx
<%# Page Language="C#" AutoEventWireup="true" Inherits="MyClass" %>
<asp:Content ID="Content3" ContentPlaceHolderID="MainRegion" runat="server">
<div>
<asp:DropDownList ID="myDdl" runat="server" OnSelectedIndexChanged="myDdlChange" ViewStateMode="Enabled" EnableViewState="true" />
</div>
<br />
<div style="min-width: 300px; max-width: 770px;">
<asp:TextBox id="txtBox" runat="server" TextMode="MultiLine" />
</div>
<div class="buttonContainer">
<span >
<asp:Button ID="btnSave" runat="server" Text="Save" onclick="btnSave_Click" />
</span>
</div>
</asp:Content>
Then, in the code behind:
MyClass.aspx.cs
public class MyClass
{
protected global::System.Web.UI.WebControls.DropDownList myDdl;
protected global::System.Web.UI.WebControls.TextBox txtBox;
protected global::System.Web.UI.WebControls.Button btnSave;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (this.Page.IsPostBack)
Session["selectedID"] = myDdl.SelectedValue; // my attempt to put the selected value from ddl in a session var, to use it later inside the save method but it didn't work
if (!Page.IsPostBack)
{
//create array1 here
myDdl.Items.Clear();
myDdl.Items.AddRange(array1);
Session["selectedID"] = myDdl.SelectedValue;
myDdlChange(null, null);
this.DataBind();
}
}
protected void btnSave_Click(object sender, EventArgs e)
{
//do something based on myDdl.SelectedValue (which shouldn't be null)
}
protected void myDdlChange(object source, EventArgs e)
{
txtBox.Text = myDdl.SelectedValue;
}
}
}
I think the main problem is you should be using Page_Load instead of OnLoad.
You don't need to use Session to remember the SelectedValue.
Try something like this which works for me...
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
PopulateDropdown();
}
private void PopulateDropdown()
{
myDdl.Items.Clear();
var array1 = new ListItem[3];
array1[0] = new ListItem("item1", "item1");
array1[1] = new ListItem("item2", "item2");
array1[2] = new ListItem("item3", "item3");
myDdl.Items.AddRange(array1);
myDdl.DataBind();
}
protected void btnSave_Click(object sender, EventArgs e)
{
var selectedVal = myDdl.SelectedValue; // putting a breakpoint here shows myDdl.SelectedValue is not null
}
protected void myDdlChange(object sender, EventArgs e)
{
}

C# Repeater focusing the first element after DataSource is changed

I have a Repeater with a certain DataSource (consisting of a list of images). The Repeater holds ImageButtons.
The aspx:
<asp:Panel ID="panSearch" runat="server" ScrollBars="Vertical" BorderColor="#333333" BorderStyle="Inset" Width="500" Height="200">
<asp:Repeater ID="Repeater" runat="server">
<ItemTemplate>
<asp:ImageButton OnClick="imgSearchResult_Click" BackColor="#333333" ID="imgSearchResult" height="32" width="32" runat="server" ImageUrl='<%# Eval("ImageUrl") %>'/>
</ItemTemplate>
</asp:Repeater>
</asp:Panel>
Additionally, I have a TextBox, which has a TextChanged-event in code-behind. I do a few things in there and at the end, my Repeater's DataSource will be overwritten with a new List of images (those images are put into the ImageButtons).
Repeater.DataSource = ImageList;
Repeater.DataBind();
My problem: Whenever my Repeater.DataSource is changed, it "clicks" the first ImageButton inside the Repeater. How do I prevent that from happening?
Full code:
My TextBox:
<asp:TextBox ID="textSearch" runat="server" Width="80" OnTextChanged="textSearch_TextChanged" ForeColor="Black" />
My TextChanged event:
protected void textSearch_TextChanged(object sender, EventArgs e)
{
string[] filesindirectory = Directory.GetFiles(Server.MapPath("~/Images/ORAS"));
List<System.Web.UI.WebControls.Image> ImageList = new List<System.Web.UI.WebControls.Image>(filesindirectory.Count());
foreach (string item in filesindirectory)
{
System.Web.UI.WebControls.Image myImage= new System.Web.UI.WebControls.Image();
myImage.ImageUrl = (String.Format("~/Images/ORAS/{0}", System.IO.Path.GetFileName(item)));
ImageList.Add(myImage);
}
Repeater.DataSource = ImageList;
Repeater.DataBind();
}
When I click on an ImageButton inside the Repeater (which is executed when the text in my TextBox is changed):
protected void imgSearchResult_Click(object sender, ImageClickEventArgs e)
{
var selectedImage = sender as ImageButton;
if (img1.ImageUrl == "~/Images/ORAS/Empty/000.png")
{
img1.ImageUrl = selectedImage.ImageUrl;
}
else if (img2.ImageUrl == "~/Images/ORAS/Empty/000.png")
{
img2.ImageUrl = selectedImage.ImageUrl;
}
else if (img3.ImageUrl == "~/Images/ORAS/Empty/000.png")
{
img3.ImageUrl = selectedImage.ImageUrl;
}
else if (img4.ImageUrl == "~/Images/ORAS/Empty/000.png")
{
img4.ImageUrl = selectedImage.ImageUrl;
}
else if (img5.ImageUrl == "~/Images/ORAS/Empty/000.png")
{
img5.ImageUrl = selectedImage.ImageUrl;
}
else if (img6.ImageUrl == "~/Images/ORAS/Empty/000.png")
{
img6.ImageUrl = selectedImage.ImageUrl;
}
else
{
ErrorMessage("Please remove one Image first!", true);
}
}
Pageload:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
img1.ImageUrl = "~/Images/ORAS/Empty/000.png";
img2.ImageUrl = "~/Images/ORAS/Empty/000.png";
img3.ImageUrl = "~/Images/ORAS/Empty/000.png";
img4.ImageUrl = "~/Images/ORAS/Empty/000.png";
img5.ImageUrl = "~/Images/ORAS/Empty/000.png";
img6.ImageUrl = "~/Images/ORAS/Empty/000.png";
LoadImages();
}
}
(LoadImages is almost 1:1 what's in my TextChanged function)
I really am not sure how (why) ASP.NET WebForms does it, but if you hit Enter and the form posts back, it will find the first control that implements IPostBackEventHandler and execute whatever event is bound to that. ImageButton implements it and so that's why it keeps firing the click event even though you didn't click on it. And, once again, only if you hit Enter.
I think that behaviour happens because the data posted back - __EVENTTARGET and __EVENTARGUMENT - are empty. Then ASP.NET goes bonkers.
You can solve it by putting a dummy button at the top of the page (or masterpage) and hide it using the style attribute. so:
<asp:Button ID="dummy" runat="server" style="display:none" />
Then in the init or load of your page (or masterpage) put
Form.DefaultButton = dummy.UniqueID;
That will force the button to capture the enter press instead of the arbitrary image button.

Values in dynamically added controls not retained on postback

I have a page with a number of controls of type MyControl added dynamically. The count is stored in the ViewState, and only incremented with a button click.
In MyControl, I have a TextBox control, and a Label control. When the text is changed in the textbox, the value is multiplied by 2 and displayed in the label control.
To do this, I have added an OnTextChanged event and set AutoPostBack to true.
My problem is this: when I have any number of MyControl's on the page, and change the text in any of the textboxes, the label is updated and the values are retained on postback.
However, if I click the increment button on the page, all the values in the textboxes and labels are lost.
My code:
Default.aspx
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Test.Default" EnableViewState="true" %>
<html>
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
Count:
<asp:Label ID="lblCount" runat="server"></asp:Label>
<asp:Button ID="btnAdd" runat="server" OnClick="btnAdd_Click" Text="+" />
<asp:Panel ID="pnlControls" runat="server"></asp:Panel>
</form>
</body>
</html>
Default.aspx.cs
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
int count = 0;
//if not postback, then set count and store in viewstate
if (!Page.IsPostBack)
{
count = 1;
ViewState["count"] = count;
}
LoadControls();
}
protected void btnAdd_Click(object sender, EventArgs e)
{
//increment count
ViewState["count"] = (int)ViewState["count"] + 1;
pnlControls.Controls.Clear();
LoadControls();
}
private void LoadControls()
{
//add controls to page
for (int i = 0; i < (int)ViewState["count"]; i++)
{
MyControl con = (MyControl)LoadControl("MyControl.ascx");
con.ID = i.ToString();
pnlControls.Controls.Add(con);
}
//set count label
lblCount.Text = ViewState["count"].ToString();
}
}
MyControl.ascx
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="MyControl.ascx.cs" Inherits="Test.MyControl" EnableViewState="true" %>
<div>
<asp:TextBox ID="txtField" runat="server" OnTextChanged="txtField_TextChanged" AutoPostBack="true"></asp:TextBox>
<asp:Label ID="lblAnswer" runat="server" Text="answer:"></asp:Label>
</div>
MyControl.ascx.cs
public partial class MyControl : System.Web.UI.UserControl
{
public string Text;
protected void Page_Load(object sender, EventArgs e)
{
}
protected void txtField_TextChanged(object sender, EventArgs e)
{
lblAnswer.Text = (int.Parse(txtField.Text) * 2).ToString();
}
}
Am I missing something obvious? How can I keep the values when the button is clicked?
The reason you lose the info of MyControl(s..) is when you click the button you clear them:
pnlControls.Controls.Clear();
If you want to keep the values I recomended you to use Session variables, for example an array when you fire "txtField_TextChanged" to save "lblAnswer.Text", be carefull with the ID's to differentiate from each other in the Session variable.
Finally, I'd put "LoadControls();" inside Page_Load, outside I think It's redundant.

Find Control Inside ListView Control

I want to find "Label" control with ID = "Label" inside the "ListView" control. I was trying to do this with the following code:
((Label)this.ChatListView.FindControl("Label")).Text = "active";
But I am getting this exception: Object reference not set to an instance of an object .
What is wrong here ?
This is aspx code:
<asp:ListView ID="ChatListView" runat="server" DataSourceID="EntityDataSourceUserPosts">
<ItemTemplate>
<div class="post">
<div class="postHeader">
<h2><asp:Label ID="Label1" runat="server"
Text= '<%# Eval("Title") + " by " + this.GetUserFromPost((Guid?)Eval("AuthorUserID")) %>' ></asp:Label></h2>
<asp:Label ID="Label" runat="server" Text="" Visible="True"></asp:Label>
<div class="dateTimePost">
<%# Eval("PostDate")%>
</div>
</div>
<div class="postContent">
<%# Eval("PostComment") %>
</div>
</div>
</ItemTemplate>
</asp:ListView>
Listview is a databound control; so controls inside it will have different ids for different rows. You have to first detect the row, then grab the control. Best to grab such controls is inside an event like OnItemDataBound. There, you can do this to grab your control:
protected void myListView_ItemDataBound(object sender, ListViewItemEventArgs e)
{
if (e.Item.ItemType == ListViewItemType.DataItem)
{
var yourLabel = e.Item.FindControl("Label1") as Label;
// ...
}
}
If you want to grab it in Page_Load, you will have to know specific row and retrieve the control as:
var theLabel = this.ChatListView.Items[<row_index>].FindControl("Label1") as Label;
This function will get Author Name from a database, you just need to call your method to get Author Name and then return it
protected string GetUserFromPost(Guid? x)
{
// call your function to get Author Name
return "User Name";
}
And to bind label in the list view you have to do it in list view ItemDataBound Event
protected void ChatListView_ItemDataBound(object sender, ListViewItemEventArgs e)
{
if (e.Item.ItemType == ListViewItemType.DataItem)
{
Label lbl = e.Item.FindControl("Label") as Label;
lbl.Text = "Active";
}
}
Here are the list view aspx code changes (just add onitemdatabound="ChatListView_ItemDataBound"):
asp:ListView
ID="ChatListView"
runat="server"
DataSourceID="EntityDataSourceUserPosts"
onitemdatabound="ChatListView_ItemDataBound"
It should be Label1 in the arguement:
((Label)this.ChatListView.FindControl("Label1")).Text = "active";
This should be in a databound event.
http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.listview.itemdatabound.aspx
Try it:
protected void ChatListView_ItemDataBound(object sender, ListViewItemEventArgs e)
{
if (e.Item is ListViewDataItem)
{
var yourLabel = e.Item.FindControl("Label1") as Label;
// ...
}
}
One simple solution to this problem, which avoids the FindControl code is to place OnInit on your label.
This would change your page code to this: <asp:Label ID="Label" runat="server" Text="" Visible="True" OnInit="Label_Init"></asp:Label>
And in your code behind you will now have a function like this:
protected void Label_Init(object sender, EventArgs e)
{
Label lblMyLabel = (Label)sender;
lblMyLabel.Text = "My Text";
}

Categories