FindControl in Custom ITemplate - c#

I have a custom UserControl which uses a simple ITemplate:
<asp:Panel runat="server" ID="pnlExpander" CssClass="expander">
<asp:HyperLink runat="server" ID="lnkExpand" Text="More Options" NavigateUrl="#" CssClass="lnkExpand"/>
<asp:Panel runat="server" ID="pnlContent" CssClass="expanderContent" style="display: none">
<asp:PlaceHolder runat="server" ID="plcContent"/>
</asp:Panel>
</asp:Panel>
The template is rendered with two simple properties:
public class Expander {
private ITemplate _contentTemplate;
public ITemplate ContentTemplate {
get { return _contentTemplate; }
set { _contentTemplate = value; }
}
protected override void OnPreRender(EventArgs e) {
if (ContentTemplate != null) {
ContentTemplate.InstantiateIn(plcContent);
}
}
Everything displays correctly, but I can't use FindControl within the template. I get a reference to my combobox from VS intellisense, but a compilation error that it's null whern I actually load the page.
To find the combobox in th the template, I'm using:
var cboFilterCriticality = AspNetUtils.FindControlRecursive(optionsExpander,"cboFilterCriticality") as DropDownList;
And the actual template looks like this on the page:
<l49:Expander runat="server" ID="optionsExpander">
<ContentTemplate>
... other controls
<asp:DropDownList ID="cboFilterCriticality" runat="server" ValidationGroup="filterGrid" DataTextField="Key" DataValueField="Value" />
</ContentTemplate>
</l49:Expander>

I resolved this by changing the UserControl which used an ITemplate. For some reason, it was calling InstantiateIn in OnPreRender, which is clearly way too late to render anything to be picked up by Page_Load in the page - see the Page LifeCycle and UserControls (half way down). I moved InstantiateIn to OnInit in the UserControl, and the problem solved itself.

The Asp.net's WebForm page:
<asp:Panel runat="server" ID="pnlExpander" CssClass="expander">
<asp:HyperLink runat="server" ID="lnkExpand" Text="More Options" NavigateUrl="#" CssClass="lnkExpand"/>
<asp:Panel runat="server" ID="pnlContent" CssClass="expanderContent" style="display: none">
<asp:PlaceHolder runat="server" ID="plcContent"/>
</asp:Panel>
</asp:Panel>
define the Expander class as following:
public class Expander {
public ITemplate ContentTemplate {get ;set;}
public HtmlGenericControl ContentTemplateContainer{get;set;}
protected override void OnInit(EventArgs e) {
this.ContentTemplateContainer = new HtmlGenericControl("div");
if (ContentTemplate != null) {
ContentTemplate.InstantiateIn(container);
}
plcContent.Controls.Add(container);
}
}
in OnInit of Page:
public override void OnInit(EventArgs e){
base.OnInit(e);
ViewState["ContentTemplateContainerID"] = ContentTemplateContainer.ClientID;
}
and finally in Javascript :
var containerID = ViewState("ContentTemplateContainerID");
var elID = $get(containerID)[0].id;
var expander = $find(elID);

Related

Add custom properties to ASP.net checkboxlist

Checkbox list items have default "Text", "Value", "Enabled" and "Selected" properties.
I need to add an "ImageUrl" property to each item in my list.
I use this code:
foreach (Zone zn in ZonesList)
{
ListItem item = new ListItem(zn.Name, zn.Id.ToString());
item.Attributes.Add("ImageUrl", zn.Image );
item.Selected = false;
visitPlaceList.Items.Add(item);
}
visitPlaceList.DataBind();
but it still doesn't show any properties other than the defaults.
How can this be achieved?
A property does get added, but as a span element surrounding the input and label. It looks like this
<span imageurl="www.google.nl">
<input id="ctl00_mainContentPane_visitPlaceList_1" type="checkbox" name="ctl00$mainContentPane$visitPlaceList$1" />
<label for="ctl00_mainContentPane_visitPlaceList_1">Name 1</label>
</span>
So if you need it with jQuery, get the correct element.
<asp:CheckBoxList ID="visitPlaceList" runat="server" ClientIDMode="Static"></asp:CheckBoxList>
<script>
$("#visitPlaceList input").change(function () {
var imageurl = $(this).closest('span').attr('imageurl');
console.log(imageurl);
});
</script>
Very interesting question! It exposes the limitations that web controls sometimes have.
I believe the proper way to solve it is by creating a custom (web) control. It is far from trivial though especially since both ListItem and CheckBoxList are sealed classes.
It can also be solved by creating a user control (ascx). The following can be improved but you get the idea:
ImageCheckBoxList.ascx
<asp:Repeater ID="Repeater1" runat="server">
<HeaderTemplate>
<table>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td>
<asp:CheckBox ID="CheckBox1" runat="server" /><asp:Image ID="Image1" runat="server" ImageUrl='<%# Eval("ImageUrl") %>' /><asp:Label ID="Label1" runat="server" AssociatedControlID="CheckBox1" Text='<%# Eval("Text") %>'></asp:Label>
</td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
ImageCheckBoxList.ascx.cs
public partial class ImageCheckBoxList : System.Web.UI.UserControl
{
public IList<ImageListItem> Items { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
Repeater1.DataSource = Items;
Repeater1.DataBind();
}
for (int i = 0; i < Repeater1.Items.Count; i++)
{
var checkBox = (CheckBox)Repeater1.Items[i].FindControl("CheckBox1");
if (checkBox != null)
{
Items[i].Checked = checkBox.Checked;
}
}
}
}
where ImageListItem is:
public class ImageListItem
{
public string Text { get; set; }
public string Value { get; set; }
public string ImageUrl { get; set; }
public bool Checked { get; set; }
public ImageListItem(string text, string value, string imageUrl)
{
Text = text;
Value = value;
ImageUrl = imageUrl;
Checked = false;
}
}
Here's how to use it in a Web Forms page:
ASPX
<%# Register TagPrefix="uc" TagName="ImageCheckBoxList" Src="ImageCheckBoxList.ascx" %>
<uc:ImageCheckBoxList ID="ImageCheckBoxList1" runat="server" />
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
Code-behind
protected void Page_Load(object sender, EventArgs e)
{
ImageCheckBoxList1.Items = new List<ImageListItem>()
{
new ImageListItem("Item 1", "Item1", "Images/1.png"),
new ImageListItem("Item 2", "Item2", "Images/2.png"),
new ImageListItem("Item 3", "Item3", "Images/3.png")
};
}
protected void Button1_Click(object sender, EventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach (ImageListItem item in ImageCheckBoxList1.Items)
{
if (item.Checked)
{
sb.AppendLine($"{item.Text} with value {item.Value} is checked.");
}
}
Label1.Text = sb.ToString();
}

UserControl doesn't display the values of the exposed controls

I have a user control:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs" Inherits="StratPlan.Main.UserCons.WebUserControl1" %>
<div>
<table>
<tr>
<td>title: </td>
<td>
<asp:TextBox ID="TitleTextBox" runat="server"/>
</td>
</tr>
<tr>
<td>strategy id: </td>
<td>
<asp:TextBox ID="StrategyIdTextBox" runat="server"/>
</td>
</tr>
<tr>
<td>company: </td>
<td>
<asp:TextBox ID="CompanyTextBox" runat="server"/>
</td>
</tr>
</table>
</div>
In its code behind:
public partial class WebUserControl1 : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
TitleTextBox.Text = ExpTitle;
StrategyIdTextBox.Text = ExpStrategyId;
CompanyTextBox.Text = ExpCompany;
}
public string ExpTitle
{
get { return this.TitleTextBox.Text; }
}
public string ExpStrategyId
{
get { return this.StrategyIdTextBox.Text; }
}
public string ExpCompany
{
get { return this.CompanyTextBox.Text; }
}
}
Then in my page, I have a list view:
<div>
<asp:ListView ID="ListView1" runat="server">
<ItemTemplate>
<uc1:WebUserControl1 runat="server" ID="WebUserControl1" ExpStrategyId='<%# Bind(StrategyId) %>' ExpTitle='<%# Bind(Title) %>' ExpCompany='<%# Bind(CompanyName) %>'/>
</ItemTemplate>
</asp:ListView>
</div>
And I bind the datasource like this in code behind:
public void LoadGridView()
{
localVm.EntityList = localVm.RetrieveMany(localVm.SearchItem);
ItemsGridView.AutoGenerateColumns = false;
ListView1.DataSource = localVm.EntityList;
ListView1.DataBind();
}
But whenever I go to my page, it doesn't give the value to the user control. what am I doing wrong?
The pages Load event is called before data bound controls are bound to their data. Change it to PreRenderComplete or Render like this
public partial class WebUserControl1 : System.Web.UI.UserControl
{
protected void Page_PreRenderComplete(object sender, EventArgs e)
{
TitleTextBox.Text = ExpTitle;
StrategyIdTextBox.Text = ExpStrategyId;
CompanyTextBox.Text = ExpCompany;
}
...
}
have a look at ASP.NET Page Life Cycle Overview

UpdatePanel Not Displaying Dynamically Added UserControl

I have an UpdatePanel with two HiddenField elements to determine which UserControl to swap into the PlaceHolder element. On initial load, one is placed in (this works fine). There are two buttons, and depending on which one is clicked, a PostBack occurs and is supposed to be swapping in the selected control.
When a button is clicked, the PostBack occurs, but in the network call, I do not see any HTML being returned, and nothing renders on the page, obviously. I don't know too much about ASP.NET and search results have yet to lead me to a solution, or a proper how-to that works for me.
Update Panel
<!-- Update Panel -->
<asp:UpdatePanel ID="PendingApprovalList" UpdateMode="Conditional" runat="server">
<ContentTemplate>
<!-- Page Navigation Bar -->
<div style="margin-top: 100px;">
<!-- Secret Sauce -->
<asp:HiddenField ID="hf_onChartsPage" runat="server" value="true" />
<asp:HiddenField ID="hf_onTablesPage" runat="server" value="false" />
<!-- Navigation Clickables -->
<a ID="ChartsTab" class="googleAnalyticsNav" runat="server" onserverclick="ChartsClicked">Chart View</a>
<a ID="TablesTab" class="googleAnalyticsNav" runat="server" onserverclick="TablesClicked">Table View</a>
</div>
<!-- Page Content PlaceHolder -->
<asp:PlaceHolder ID="AnalyticsContent" runat="server"></asp:PlaceHolder>
</ContentTemplate>
</asp:UpdatePanel>
Main Page Code Behind
public partial class LocalGoogleReports : System.Web.UI.UserControl {
//Constants
private const string CHARTS_PATH = #"~/userctrls/Admin/Dashboard/Local/AnalyticsCharts.ascx";
private const string TABLES_PATH = #"~/userctrls/Admin/Dashboard/Local/AnalyticsTables.ascx";
//Properties
public int CENTER_NUM;
public string CENTER_NAME;
public bool GoToChartsPage {
get {
return bool.Parse(hf_onChartsPage.Value);
}
set {
hf_onChartsPage.Value = value.ToString();
}
}
public bool GoToTablesPage {
get {
return bool.Parse(hf_onTablesPage.Value);
}
set {
hf_onTablesPage.Value = value.ToString();
}
}
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack) {
CENTER_NUM = SessionManager.Center.CenterNumber;
CENTER_NAME = SessionManager.Center.CenterName.ToLower();
AnalyticsContent.Controls.Clear();
UserControl chartsControl = (UserControl)LoadControl(CHARTS_PATH);
AnalyticsContent.Controls.Add(chartsControl);
} else {
if (GoToChartsPage) {
LoadChartsUserControl();
} else {
LoadTablesUserControl();
}
}
}
protected void ChartsClicked(object sender, EventArgs e) {
GoToChartsPage = true;
GoToTablesPage = false;
}
protected void TablesClicked(object sender, EventArgs e) {
GoToTablesPage = true;
GoToChartsPage = false;
}
private void LoadChartsUserControl() {
AnalyticsContent.Controls.Clear();
UserControl chartsControl = (UserControl)LoadControl(CHARTS_PATH);
AnalyticsContent.Controls.Add(chartsControl);
ScriptManager.RegisterStartupScript(PendingApprovalList, GetType(), Page.UniqueID,
"getAnalyticData();", true);
}
private void LoadTablesUserControl() {
AnalyticsContent.Controls.Clear();
UserControl tablesControl = (UserControl)LoadControl(TABLES_PATH);
AnalyticsContent.Controls.Add(tablesControl);
}
}
Thanks in advance for any help!
Try to use PendingApprovalList.UpdatePanel(); after loading Chart or Table

ScriptManager not found by UpdatePanel if loaded in BasePage

Platform: asp.net 4.0
I load scriptmanager from a baseclass for custom cdn handling and scripts inserting in pages and other custom things.
The problem is that when I insert an UpdatePanel it doesn't find the script manager because updatepanel search for it to early.
Is there a solution that does not imply removing ScriptManager from the basePage.
this class is from our custom utility dll
public abstract class OurFrameworkBasePage:Page
{
protected override void OnInit(EventArgs e)
{
CurrentScriptManager = BuildScriptManager();
Form.Controls.AddAt(0, CurrentScriptManager);
base.OnInit(e);
}
private ScriptManager BuildScriptManager()
{
return new ScriptManager
{
//some scriptmanager settings
};
}
protected ScriptManager CurrentScriptManager { get; set; }
}
this is site specific basepage
public abstract class SiteBasePage:OurFrameworkBasePage
{
//some custom methods and utility for a specific site
}
the default.aspx page
<html>
<head runat="server"><title></title></head>
<body>
<form id="form1" runat="server">
<div>
<asp:UpdatePanel runat="server">
<ContentTemplate>
<asp:Literal runat="server" ID="ltr"></asp:Literal>
<asp:Button runat="server" OnClick="btnOkClick" ID="btnOk" Text="ok"/>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
</body>
</html>
Hopefully you have control over your framework class, as this will fix your issue:
protected override ControlCollection CreateControlCollection()
{
CurrentScriptManager = BuildScriptManager();
ControlCollection pageControls = base.CreateControlCollection();
pageControls.AddAt(0, CurrentScriptManager);
return pageControls;
}
protected override void OnInit(EventArgs e)
{
Form.Controls.AddAt(0, CurrentScriptManager);
base.OnInit(e);
}
Needs to be in both places. First, in CreateControlCollection so that it is created along with all the other controls. Second, in OnInit, because the ScriptManager needs to reside in a form with runat="server"
GuthMD's solution is a great one.
in the meantime i found another solution that accomplish different needs and i write there for reference.
My solution imply that if you want to handle postback with updatePanel you have to put scriptmanager tag in aspx page otherwise scriptmanger will be inserted programmatically for scripts references
protected override void OnInit(EventArgs e)
{
CurrentScriptManager = BuildScriptManager();
base.OnInit(e);
}
private ScriptManager BuildScriptManager()
{
ScriptManager rtn;
var script = Items[typeof (ScriptManager)];
if (script == null)
{
rtn = new ScriptManager
{
EnablePartialRendering = false
};
Form.Controls.AddAt(0, rtn);
}
else
{
rtn = (ScriptManager) script;
}
rtn.EnablePageMethods = false;
rtn.AjaxFrameworkMode = AjaxFrameworkMode.Disabled;
rtn.EnableCdn = true;
return rtn;
}

How to create a custom control with INamingContainer?

I am trying the following:
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateContainer(typeof(TemplateContainer))]
public virtual ITemplate LayoutTemplate { get; set; }
protected void Page_Init(object sender, EventArgs e)
{
this.Controls.Clear();
if (LayoutTemplate != null)
{
var data = Enumerable.Range(0, 10);
foreach (int index in data)
{
TemplateContainer container = new TemplateContainer(index);
LayoutTemplate.InstantiateIn(container);
this.Controls.Add(container);
}
}
}
My container class:
public class TemplateContainer : Control, INamingContainer
{
public int Index { get; set; }
internal TemplateContainer(int index)
{
this.Index = index;
}
}
And my markup:
<uc:TemplateControl ID="ucTemplateControl" runat="server">
<LayoutTemplate>
<b>Index:</b>
<asp:TextBox ID="Literal1" runat="server"
Text='<%# Container.Index %>'
ReadOnly="true" />
<br />
</LayoutTemplate>
</uc:TemplateControl>
But for some reason Container.Index is not rendering any value, just empty. 10 controls are being created, but none shows a value.
What did I do wrong? How can I fix it so it will show the Index value?
I tried something similar to MSDN example:
How to: Create Templated ASP.NET User Controls
To bind the value you need to call the DataBind method of the custom control.
Calling
ucTemplateControl.DataBind();
from the page made the data to be bound on the template.

Categories