Dynamically create different kinds of web controls - c#

English is not my native language; please excuse typing errors.
I am creating a survey type application, and I'm not sure how I should approach so I've been doing some trials and errors.
I have a Question class
public class Question
{
public int QuestionID;
public string QuestionText;
public int InputTypeID;
public List<WebControl> Controls;
public Question()
{
//initialize fields;
}
internal Question(int id, string text, int inputTypeId)
{
//assign parameters to fields
switch(inputTypeId)
{
case 1:
//checkbox
break;
case 2:
//textbox
TextBox t = new TextBox();
Controls = new List<WebControl>();
Controls.Add(t);
break;
...
}
}
}
My Question.aspx looks like this:
<asp:Repeater ID="repeater" runat="server">
<ItemTemplate>
//Want to display a control dynamically here
</ItemTemplate>
</asp:Repeater>
I tried this but it obviously didn't work...
<asp:Repeater ID="repeater" runat="server">
<ItemTemplate>
<%# DataBinder.Eval(Container.DataItem, "Controls") %>
</ItemTemplate>
</asp:Repeater>
and I just get this.
System.Collections.Generic.List`1[System.Web.UI.WebControls.WebControl] System.Collections.Generic.List`1
One question could have
one textbox
radiobutton list
checkbox list
In this case, should my Question class have List<WebControl> or just WebControl?
Also, how can I render the webcontrol inside the repeater?

You should do this in CodeBehind, using the Repeater ItemDataBound() event. Your question class should have a List<Control> which is the base class for WebControl and other controls, allowing the flexibility for different kinds of controls.
Doesn't have to use Page_Load but just for example,
void Page_Load(Object Sender, EventArgs e)
{
Repeater1.ItemDataBound += repeater1_ItemDataBound;
Repeater1.DataSource = [your List<Control> containing controls to add];
Repeater1.DataBind();
}
void Repeater1_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
// Execute the following logic for Items and Alternating Items.
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
var controlToAdd = (Control)e.Item.DataItem;
((PlaceHolder)e.Item.FindControl("PlaceHolder1")).Controls.Add(controlToAdd);
}
}
And the ItemTemplate:
<ItemTemplate>
<asp:PlaceHolder id="PlaceHolder1" Runat="server" />
</ItemTemplate>

Related

Nested repeater will not display bound information from list

I am trying to get a page to display information in a row layout using repeaters. I have one working that allows me to dynamically create hyperlinks, however i cant get my nested repeater to work to display the date the file was created. Is it possible to use repeaters to dynamically display multiple variables from lists as i'm trying to do below?
.aspx
<asp:Repeater id="repLinks" runat="server">
<ItemTemplate>
<tr><td>
<asp:HyperLink runat="server" NavigateUrl='<%# Container.DataItem.ToString() %>' Text="<%# Container.DataItem.ToString().Split('\\').Last() %>" />
<td>
<asp:Repeater ID="Repeater2" runat="server" OnItemDataBound="Repeater2_ItemDataBound" >
<ItemTemplate>
<%# Container.DataItem.ToString()%>
</ItemTemplate>
</asp:Repeater>
</td>
<td>
Submitted By <!--add repeater-->
</td>
<td>
Mark as Billed <!--add repeater-->
</td>
</td></tr>
</ItemTemplate>
</asp:Repeater>
.aspx.cs
public List<string> CD = new List<string>();
protected void Page_Load(object sender, EventArgs e)
{
//Welcomes User
string Uname = Environment.UserName;
UserName.Font.Size = 17;
UserName.Text = "Welcome: " + Uname;
//gives path and constructs lists for directory paths and file links
string root = "C:\\Users\\James\\Documents\\Visual Studio 2015\\WebSites";
List<string> lLinks = new List<string>();
//adds files to list
foreach (var path in Directory.GetDirectories(#root))
{
foreach (var path2 in Directory.GetFiles(path))
{
lLinks.Add(path2);
CD.Add(File.GetCreationTime(path2).Date.ToString("yyyy-mm-dd"));
}
}
//Define your list contents here
repLinks.DataSource = lLinks;
repLinks.DataBind();
}
protected void Repeater2_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
Repeater Repeater2 = (Repeater)(e.Item.FindControl("Repeater2"));
Repeater2.DataSource = CD;
Repeater2.DataBind();
}
}
The issue with your code is that you are binding the nested repeater control (Repeater2) in the ItemDataBound event of Repeater2 repeater itself which will never get fired because ItemDataBound event is fired for each item in collection when it is bounded to the repeater control.
You should write the logic in ItemDataBound event of your parent repeater like this:-
<asp:Repeater id="repLinks" runat="server" OnItemDataBound="Repeater1_ItemDataBound">
Then, write the logic in this event handler:-
protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item ||
e.Item.ItemType == ListItemType.AlternatingItem)
{
Repeater Repeater2 = (Repeater)(e.Item.FindControl("Repeater2"));
Repeater2.DataSource = CD;
Repeater2.DataBind();
}
}
Also, In the Page_Load event you should bind the Parent Repeater Repeater1 on just the initial page load so wrap it inside !IsPostBack and populate your datasource lLinks & CD from a separate method intead of doing it in Page_Load event.

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

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

How to add web user controls to repeater

Scenario:
UsrControl: custom user control, which contains a textbox and a button, rederend horizontally (in one line).
UsrControlContainer: custom user control, which should be able to display multiple UsrControl objects (each object in seperate line, so the Seperator template will probably be <br />. This control also contains a button, which adds new UsrControl to the collection.
My code:
<asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click"/>
<asp:Repeater ID="rptExample" runat="server">
<ItemTemplate>
</ItemTemplate>
<SeparatorTemplate><br /></SeparatorTemplate>
</asp:Repeater>
And:
protected void Button1_Click(object sender, EventArgs e)
{
rptExample.DataSource = new List<UsrControl> {new UsrControl(), new UsrControl()};
rptExample.DataBind();
}
Simple question - what should I put in ItemTemplate to make this work?
Edit - I also want to pass some parameters to UsrControl before rendering it.
<asp:Repeater ID="rptExample" runat="server">
<ItemTemplate>
<uc:UsrControl runat="server" />
</ItemTemplate>
<SeparatorTemplate><br /></SeparatorTemplate>
</asp:Repeater>
protected void Button1_Click(object sender, EventArgs e)
{
rptExample.DataSource = Enumerable.Range(0, 2);
rptExample.DataBind();
}
Following your question in answer. You can catch every binding object in ItemDataBound event. So for example, as i used, set whole object as user control property.
protected void PersonesRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
PersonLine line = (PersonLine)e.Item.FindControl("Person1");
line.Person = e.Item.DataItem as Osoba;
}
}
Ofcourse, you have to add the event handler to your repeater:
<asp:Repeater runat="server" ID="PersonesRepeater" OnItemDataBound="PersonesRepeater_ItemDataBound"><ItemTemplate>
<my:Person ID="Person1" runat="server" />
</ItemTemplate>
</asp:Repeater>

asp:repeater events - how to postback

I have an ASP:Repeater Which I would like to display a list of check boxes in. These check boxes are related to a list of user preferences and the users resulting answer. See Code Bellow.
I would like to add do one of the following if possible
Option 1: It would be great if I could use the Event in the Repeater:OnItemCommand(...) to fire if any of the items change. It would seem to me that this event will only fire if there is a Button | LinkButton | ImageButton item in the list. IE it will not fire if I put in a check box with AutopostBack="True"
Option 2: Is there a way I could attach a method to an Event of CheckBox:CheckChanged I would need to pass this method a parameter saying which question/answer combo to change.
Option 3: Its your answer if you know an easier way that would be awesome.
The Code:
<asp:Repeater ID="RPTprefs" runat="server" DataSourceID="getAnswers" OnItemCommand="RPTprefs_ItemCommand">
<ItemTemplate>
<li><asp:CheckBox ID='questionID' runat="server"
Checked='<%# Eval("pr.up_is_selected") %>'
Text='<%# Eval("prp.prefs_question") %>'
AutoPostBack="true"
OnCheckedChanged="CheckChanged" /></li>
</ItemTemplate>
</asp:Repeater>
Thanks in advance
Here is what I came up with, which is basically your option #2.
In the ItemTemplate of the repeater, I use a Literal control (Visible set to false) which has the argument you wish to pass to the CheckedChanged function. The reason for using a control is because the control will retain its value in the ViewState after a post back, whereas the original data source for the Repeater will be lost.
In the OnItemCreated function, I bind the CheckChanged function for all of the check boxes to pass in the right argument. Here's my complete example. In this case, I want to pass the Id property of my data to the CheckChanged function.
Markup:
<asp:Repeater ID="Repeater1" runat="server" OnItemCreated="ItemCreated">
<ItemTemplate>
<asp:Literal ID="litArg" runat="server" Visible="false" Text='<%# Eval("Id") %>'>
</asp:Literal><%# Eval("Name") %>
<asp:CheckBox ID="chkCool" runat="server" AutoPostBack="true" Checked='<%# Eval("IsCool") %>' /><br />
</ItemTemplate>
</asp:Repeater>
Code behind:
public class SomeClass
{
public SomeClass(bool c, string n, int id)
{
IsCool = c;
Name = n;
Id = id;
}
public bool IsCool { get; set; }
public string Name { get; set; }
public int Id { get; set; }
}
.
.
.
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
List<SomeClass> people = new List<SomeClass>();
people.Add(new SomeClass(true, "Will", 666));
people.Add(new SomeClass(true, "Dan", 2));
people.Add(new SomeClass(true, "Lea", 4));
people.Add(new SomeClass(false, "Someone", 123));
Repeater1.DataSource = people;
Repeater1.DataBind();
}
}
private void CheckChanged(int id)
{
Response.Write("CheckChanged called for item #" + id.ToString());
}
protected void ItemCreated(object sender, RepeaterItemEventArgs e)
{
//this needs to be set again on post back
CheckBox chk = (CheckBox)e.Item.FindControl("chkCool");
Literal arg = (Literal)e.Item.FindControl("litArg");
Action<object, EventArgs> handler = (s, args) => CheckChanged(Convert.ToInt32(arg.Text));
chk.CheckedChanged += new EventHandler(handler);
}
Hope that helps.

define variable in repeater?

I like to handle and compare a lot of date times in my repeater even I have to work more than one time with the same.
It's a bit ugly, to cast everywhere the Eval("MyDate") like ((DateTime)Eval("MyDate")) to substract 2 datetimes or to compare it, even if you have to do this more than in one operation.
I thought of saving all the evals in a var at start of the repeater?
DateTime mydt1 = Eval("myDate");
DateTime mydt2 = Eval("mydate");
after that, it's easy to do any operations in the whole repeater. Hope you understand my idea. Is this possible? I tried short but everytime errors.
mydt1 - mydt2....
Thank you and best regards.
You could call a method on the code behind page from the repeater using the DateTimes as arguments. The casting logic can be done in the code behind if the goal it to create a cleaner looking aspx page.
Example ASPX:
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<asp:Literal
ID="Literal1"
runat="server"
Text='<%# DateFoo(Eval("myDate1"), Eval("myDate2")) %>' />
</ItemTemplate>
</asp:Repeater>
Example C# code behind:
protected string DateFoo(Object o1, Object o2)
{
DateTime? dt1 = o1 as DateTime?;
DateTime? dt2 = o2 as DateTime?;
// Do logic with DateTimes
return "string";
}
If you want to add more logic to your repeater I would suggest you move the binding logic to the code behind:
ASPX:
<asp:Repeater id="myRepeater" runat="server">
<ItemTemplate>
<asp:Literal id="myLiteral" runat="server" />
</ItemTemplate>
</asp:Repater>
CS:
protected override void OnInit(EventArgs e)
{
myRepeater.ItemDataBound += myRepeater_ItemDataBound;
base.OnInit(e);
}
void myRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
// this method will be invoked once for every item that is data bound
// this check makes sure you're not in a header or a footer
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
// this is the single item being data bound, for instance, a user, if
// the data source is a List<User>
User user = (User) e.Item.DataItem;
// e.Item is your item, here you can find the controls in your template
Literal myLiteral = (Literal) e.Item.FindControl("myLiteral");
myLiteral.Text = user.Username + ", " + user.LastLoginDate.ToShortDateString();
// you can add any amount of logic here
// if you need to use it, e.Item.ItemIndex will tell you what index you're at
}
}
I hate evals with a passion.
This is why I use this code to be rid of them forever and go back to strong typing:
public static class DataItemExtensions
{
public static T As<T>(this IDataItemContainer repeater) where T : class
{
return (T)repeater.DataItem;
}
public static dynamic AsDynamic(this IDataItemContainer repeater)
{
return repeater.DataItem;
}
}
Then use it like this:
<asp:Repeater runat="server" DataSource="<%# this.MyObjectCollection %>">
<ItemTemplate>
<%# Container.As<MyObject>().DateTime %>
</ItemTemplate>
</asp:Repeater>
Note that if you use the Datasource like I did, you need to use this.DataBind() on the page.

Categories